]> git.sur5r.net Git - fstl/commitdiff
New upstream version 0.10.0 upstream
authorJakob Haufe <sur5r@debian.org>
Wed, 29 Jun 2022 14:02:12 +0000 (16:02 +0200)
committerJakob Haufe <sur5r@debian.org>
Wed, 29 Jun 2022 14:02:12 +0000 (16:02 +0200)
20 files changed:
CMakeLists.txt
README.md
app/package.sh
gl/colored_lines.frag [new file with mode: 0644]
gl/colored_lines.vert [new file with mode: 0644]
gl/gl.qrc
gl/mesh_surfaceangle.frag [new file with mode: 0644]
qt/fstl.pro [deleted file]
src/app.cpp
src/axis.cpp [new file with mode: 0644]
src/axis.h [new file with mode: 0644]
src/canvas.cpp
src/canvas.h
src/loader.cpp
src/loader.h
src/main.cpp
src/mesh.cpp
src/mesh.h
src/window.cpp
src/window.h

index 66cc168d1d090826a3c631b563494bf51ba91d28..0628afa4479e189e46941453ce4548c1de78fa90 100644 (file)
@@ -4,6 +4,8 @@
 
 cmake_minimum_required(VERSION 3.3)
 
+project(fstl)
+
 # Setting -std=c++11
 set(CMAKE_CXX_STANDARD 11)
 # Setting standard to required, as requisted by DeveloperPaul123 on github
@@ -11,11 +13,11 @@ set(CXX_STANDARD_REQUIRED ON)
 
 # Set the version number
 set (FSTL_VERSION_MAJOR "0")
-set (FSTL_VERSION_MINOR "9")
-set (FSTL_VERSION_PATCH "4")
+set (FSTL_VERSION_MINOR "10")
+set (FSTL_VERSION_PATCH "0")
 set (PROJECT_VERSION "${FSTL_VERSION_MAJOR}.${FSTL_VERSION_MINOR}.${FSTL_VERSION_PATCH}")
 
-project(fstl)
+message(STATUS "Version: ${PROJECT_VERSION}")
 
 set(CMAKE_AUTOMOC ON)
 set(CMAKE_INCLUDE_CURRENT_DIR ON)
@@ -24,6 +26,7 @@ set(CMAKE_AUTOUIC ON)
 #set project sources
 set(Project_Sources src/app.cpp
 src/backdrop.cpp
+src/axis.cpp
 src/canvas.cpp
 src/glmesh.cpp
 src/loader.cpp
@@ -34,6 +37,7 @@ src/window.cpp)
 #set project headers. 
 set(Project_Headers src/app.h
 src/backdrop.h
+src/axis.h
 src/canvas.h
 src/glmesh.h
 src/loader.h
@@ -48,7 +52,7 @@ set(Icon_Resource exe/fstl.rc)
 set(OpenGL_GL_PREFERENCE GLVND)
 
 #find required packages. 
-find_package(Qt5 REQUIRED COMPONENTS Core Gui Widgets OpenGL)
+find_package(Qt5 5.14 REQUIRED COMPONENTS Core Gui Widgets OpenGL)
 find_package(OpenGL REQUIRED)
 find_package(Threads REQUIRED)
 
@@ -61,13 +65,18 @@ set_property(SOURCE ${Project_Resources_RCC} PROPERTY SKIP_AUTOGEN ON)
 #include opengl files. 
 include_directories(${QT_QTOPENGL_INCLUDE_DIR} ${OPENGL_INCLUDE_DIR} )
 
-add_executable(fstl WIN32 ${Project_Sources} ${Project_Headers} ${Project_Resources_RCC} ${Icon_Resource})
-target_link_libraries(fstl Qt5::Widgets Qt5::Core Qt5::Gui Qt5::OpenGL ${OPENGL_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
 if(WIN32)
+  add_executable(fstl WIN32 ${Project_Sources} ${Project_Headers} ${Project_Resources_RCC} ${Icon_Resource})
   set(Fstl_LINK_FLAGS ${CMAKE_CURRENT_SOURCE_DIR}/${Icon_Resource})
   set_target_properties(fstl PROPERTIES LINK_FLAGS ${Fstl_LINK_FLAGS})
+elseif(APPLE)
+  add_executable(fstl MACOSX_BUNDLE ${Project_Sources} ${Project_Headers} ${Project_Resources_RCC} ${Icon_Resource})
+else()
+  add_executable(fstl ${Project_Sources} ${Project_Headers} ${Project_Resources_RCC} ${Icon_Resource})
 endif(WIN32)
 
+target_link_libraries(fstl Qt5::Widgets Qt5::Core Qt5::Gui Qt5::OpenGL ${OPENGL_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
+
 # Add version definitions to use within the code. 
 target_compile_definitions(fstl PRIVATE -DFSTL_VERSION="${PROJECT_VERSION}")
 
@@ -79,69 +88,69 @@ set(CPACK_PACKAGE_VERSION_PATCH ${FSTL_VERSION_PATCH})
 
 if(WIN32)
 
-       set(QT_USE_QTMAIN true)
-       
-       if(MSVC)
-               set_source_files_properties(fstl PROPERTIES LINKER_LANGUAGE "CXX")
-               set_target_properties(fstl PROPERTIES LINK_FLAGS "/SUBSYSTEM:WINDOWS")
-
-               install(TARGETS fstl DESTINATION bin COMPONENT all)
-               
-               install(FILES
-                       $<TARGET_FILE:Qt5::Gui_EGL>
-                       $<TARGET_FILE:Qt5::Gui_GLESv2>
-                       $<TARGET_FILE:Qt5::Core>
-                       $<TARGET_FILE:Qt5::Gui> 
-                       $<TARGET_FILE:Qt5::OpenGL>
-                       $<TARGET_FILE:Qt5::Widgets>
-                       DESTINATION bin COMPONENT all)
-               
-               #install file in the platforms directory.
-               install (FILES
-                       ${Qt5Core_DIR}/../../../plugins/platforms/qwindows.dll
-                       DESTINATION bin/platforms COMPONENT all
-               )
-
-               #custom commands based on: https://gist.github.com/Rod-Persky/e6b93e9ee31f9516261b
-               add_custom_command(TARGET fstl POST_BUILD 
-                       COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:Qt5::Core>       $<TARGET_FILE_DIR:${PROJECT_NAME}>
-                       COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:Qt5::Gui>        $<TARGET_FILE_DIR:${PROJECT_NAME}>
-                       COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:Qt5::Widgets>    $<TARGET_FILE_DIR:${PROJECT_NAME}>
-                       COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:Qt5::OpenGL>     $<TARGET_FILE_DIR:${PROJECT_NAME}>
-               )       
-       endif(MSVC)
-
-       # windows specific installer generation information
-       set(CPACK_GENERATOR NSIS)
-       set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL OFF)
-       set(CPACK_NSIS_MODIFY_PATH ON)
-       set(CPACK_NSIS_MUI_FINISHPAGE_RUN ${PROJECT_NAME})
-       set(CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME}")
-       set(CPACK_NSIS_INSTALLED_ICON_NAME "bin\\\\fstl.exe")
-       set(CPACK_NSIS_URL_INFO_ABOUT "https://github.com/mkeeter/fstl")
-       set(CPACK_NSIS_DISPLAY_NAME "fstl ${FSTL_VERSION}")
-       set(CPACK_NSIS_MUI_ICON "${CMAKE_CURRENT_SOURCE_DIR}/exe/fstl.ico")
-       set(CPACK_NSIS_MUI_UNIICON  "${CMAKE_CURRENT_SOURCE_DIR}/exe/fstl.ico")
-       set(CPACK_NSIS_CREATE_ICONS_EXTRA
-               "CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\fstl.lnk' '$INSTDIR\\\\bin\\\\fstl.exe'")
-       set(CPACK_COMPONENTS_ALL all)
-       if (CMAKE_CL_64)
-               set(CPACK_NSIS_INSTALL_ROOT "$PROGRAMFILES64")
-       else (CMAKE_CL_64)
-               set(CPACK_NSIS_INSTALL_ROOT "$PROGRAMFILES")
-       endif (CMAKE_CL_64)
+    set(QT_USE_QTMAIN true)
+    
+    if(MSVC)
+        set_source_files_properties(fstl PROPERTIES LINKER_LANGUAGE "CXX")
+        set_target_properties(fstl PROPERTIES LINK_FLAGS "/SUBSYSTEM:WINDOWS")
+
+        install(TARGETS fstl DESTINATION bin COMPONENT all)
+        
+        install(FILES
+            $<TARGET_FILE:Qt5::Gui_EGL>
+            $<TARGET_FILE:Qt5::Gui_GLESv2>
+            $<TARGET_FILE:Qt5::Core>
+            $<TARGET_FILE:Qt5::Gui> 
+            $<TARGET_FILE:Qt5::OpenGL>
+            $<TARGET_FILE:Qt5::Widgets>
+            DESTINATION bin COMPONENT all)
+        
+        #install file in the platforms directory.
+        install (FILES
+            ${Qt5Core_DIR}/../../../plugins/platforms/qwindows.dll
+            DESTINATION bin/platforms COMPONENT all
+        )
+
+        #custom commands based on: https://gist.github.com/Rod-Persky/e6b93e9ee31f9516261b
+        add_custom_command(TARGET fstl POST_BUILD 
+            COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:Qt5::Core>       $<TARGET_FILE_DIR:${PROJECT_NAME}>
+            COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:Qt5::Gui>        $<TARGET_FILE_DIR:${PROJECT_NAME}>
+            COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:Qt5::Widgets>    $<TARGET_FILE_DIR:${PROJECT_NAME}>
+            COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:Qt5::OpenGL>     $<TARGET_FILE_DIR:${PROJECT_NAME}>
+        )      
+    endif(MSVC)
+
+    # windows specific installer generation information
+    set(CPACK_GENERATOR NSIS)
+    set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL OFF)
+    set(CPACK_NSIS_MODIFY_PATH ON)
+    set(CPACK_NSIS_MUI_FINISHPAGE_RUN ${PROJECT_NAME})
+    set(CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME}")
+    set(CPACK_NSIS_INSTALLED_ICON_NAME "bin\\\\fstl.exe")
+    set(CPACK_NSIS_URL_INFO_ABOUT "https://github.com/fstl-app/fstl")
+    set(CPACK_NSIS_DISPLAY_NAME "fstl ${FSTL_VERSION}")
+    set(CPACK_NSIS_MUI_ICON "${CMAKE_CURRENT_SOURCE_DIR}/exe/fstl.ico")
+    set(CPACK_NSIS_MUI_UNIICON  "${CMAKE_CURRENT_SOURCE_DIR}/exe/fstl.ico")
+    set(CPACK_NSIS_CREATE_ICONS_EXTRA
+        "CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\fstl.lnk' '$INSTDIR\\\\bin\\\\fstl.exe'")
+    set(CPACK_COMPONENTS_ALL all)
+    if (CMAKE_CL_64)
+        set(CPACK_NSIS_INSTALL_ROOT "$PROGRAMFILES64")
+    else (CMAKE_CL_64)
+        set(CPACK_NSIS_INSTALL_ROOT "$PROGRAMFILES")
+    endif (CMAKE_CL_64)
 elseif(APPLE)
-       set(CPACK_GENERATOR "DragNDrop")
-       set(CPACK_DMG_FORMAT "UDBZ")
-       set(CPACK_DMG_VOLUME_NAME "${PROJECT_NAME}")
-       set(CPACK_SYSTEM_NAME "OSX")
-       set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}")
+    set(CPACK_GENERATOR "DragNDrop")
+    set(CPACK_DMG_FORMAT "UDBZ")
+    set(CPACK_DMG_VOLUME_NAME "${PROJECT_NAME}")
+    set(CPACK_SYSTEM_NAME "OSX")
+    set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}")
     set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/app/fstl.icns")
 else()
-       install(TARGETS fstl RUNTIME DESTINATION bin)
+    install(TARGETS fstl RUNTIME DESTINATION bin)
 
-       set(CPACK_GENERATOR "DEB;RPM")
-       set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}")
+    set(CPACK_GENERATOR "DEB;RPM")
+    set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}")
 endif(WIN32)
 
 include(CPack)
index a8a3d18422b7cb11b477bc29b4b6c197fecaee0a..c58eeae8f676ff86cb557d9048fa1644352f8b77 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1,6 +1,9 @@
 # fstl
 
-`fstl` is the fastest [.stl file](http://en.wikipedia.org/wiki/STL_\(file_format\)) viewer.
+`fstl` is a very fast [.stl file](http://en.wikipedia.org/wiki/STL_\(file_format\)) viewer.
+
+It was originally written by [Matt Keeter](https://mattkeeter.com),
+and is now primarily maintained by [@DeveloperPaul123](https://github.com/DeveloperPaul123).
 
 It is designed to quickly load and render very high-polygon models;
 showing 2 million triangles at 60+ FPS on a mid-range laptop.
@@ -11,29 +14,75 @@ Issues and minor pull requests are welcome;
 the project is under 1K lines of code and should be fairly approachable.
 
 ## Screenshot
+
 ![Eiffel tower](http://mattkeeter.com/projects/fstl/eiffel.png)
 (credit to [Pranav Panchal](https://grabcad.com/pranav.panchal))
 
+## Setting `fstl` as the Default STL Viewer
+
+### Windows
+
+1. Right-click an STL file
+2. Select `Open With` >>> `Choose another app`
+3. Select `More Apps` and `Look for another app on this PC`
+4. Enter the path to the `fstl` EXE file
+
+### MacOS
+
+1. Ctrl+click an STL file
+2. Select `Get Info`
+3. Navigate to the `Open with` section
+4. Select `fstl` in the dropdown
+5. Click `Change All`
+
+### Linux
+
+If `mimeopen` is available on your system, it can be used to set `fstl` as the default viewer for STL files.
+Run the following in your terminal:
+
+```bash
+# replace example.stl with an actual file
+mimeopen -d example.stl
+```
+
+The following output will result:
+
+```
+Please choose a default application for files of type model/stl
+
+       1) Other...
+
+use application #
+```
+
+Select the `Other` option and type `fstl` as the desired command to open STL files.
+This will now become the system default, even when opening files from the file manager.
+
 ## Building
 
-The only dependency for `fstl` is [Qt 5](https://www.qt.io).
+The only dependency for `fstl` is [Qt 5](https://www.qt.io),
+plus [`cmake`](https://cmake.org/) for building.
 
 ### macOS
 
-Install Qt from their website or [Homebrew](brew.sh),
-making sure `qmake` is on your shell's path.
+Install Qt from their website or [Homebrew](brew.sh).
+
+Install `cmake` through Homebrew or equivalent.
 
 Then, run through the following set of commands in a shell:
+
 ```
-git clone https://github.com/mkeeter/fstl
+git clone https://github.com/fstl-app/fstl
 cd fstl
 mkdir build
 cd build
-qmake ../qt/fstl.pro
+cmake -DCMAKE_PREFIX_PATH=/usr/local/Cellar/qt/5.15.0/ ..
 make -j8
 ./fstl.app/Contents/MacOS/fstl
 ```
 
+You may need to edit the Qt path depending on your installation.
+
 To package a standalone app, go to the app directory and run `package.sh`
 
 ```
@@ -50,16 +99,13 @@ This should produce two new files in the root directory:
 Install Qt with your distro's package manager (required libraries are Core, Gui,
 Widgets and OpenGL, e.g. `qt5-default` and `libqt5opengl5-dev` on Debian).
 
-You can build fstl with qmake (in some distros qmake-qt5) or with CMake:
+You can build fstl with CMake:
 ```
-git clone https://github.com/mkeeter/fstl
+git clone https://github.com/fstl-app/fstl
 cd fstl
 mkdir build
 cd build
-
-qmake ../qt/fstl.pro  # For qmake build
-cmake ..              # For CMake build
-
+cmake ..
 make -j8
 ./fstl
 ```
index bdb4999a91d18306e453a66f17056394719f1abd..6cf065beba3ec8adf5c497a42571fc52a76c2a28 100755 (executable)
@@ -12,6 +12,7 @@ APP=fstl
 MACDEPLOYQT=`otool -L $APP.app/Contents/MacOS/fstl | sed -n -e "s:\(.*\)lib/QtCore.*:\1/bin/macdeployqt:gp"`
 
 $MACDEPLOYQT $APP.app
+cp ../app/Info.plist $APP.app/Contents/
 
 # Delete unused Qt plugins
 cd fstl.app/Contents/PlugIns
@@ -41,7 +42,6 @@ do
 done
 
 cd ../Resources
-rm empty.lproj
 
 # Create a disk image
 cd ../../..
diff --git a/gl/colored_lines.frag b/gl/colored_lines.frag
new file mode 100644 (file)
index 0000000..1c02e1b
--- /dev/null
@@ -0,0 +1,7 @@
+#version 120
+
+varying vec3 frag_color;
+
+void main() {
+    gl_FragColor = vec4(frag_color, 1.0);
+}
diff --git a/gl/colored_lines.vert b/gl/colored_lines.vert
new file mode 100644 (file)
index 0000000..2100403
--- /dev/null
@@ -0,0 +1,14 @@
+#version 120
+attribute vec3 vertex_position;
+attribute vec3 vertex_color;
+
+uniform mat4 transform_matrix;
+uniform mat4 view_matrix;
+
+varying vec3 frag_color;
+
+void main() {
+    gl_Position = view_matrix*transform_matrix*
+        vec4(vertex_position, 1.0);
+    frag_color = vertex_color;
+}
index 776226c0d2b069bc4d4d380d496627ba5376e82d..6e8baa90cd9b64fa52a89c0f0eae0a482915767c 100644 (file)
--- a/gl/gl.qrc
+++ b/gl/gl.qrc
@@ -3,8 +3,11 @@
         <file>mesh.frag</file>
         <file>mesh.vert</file>
         <file>mesh_wireframe.frag</file>
+        <file>mesh_surfaceangle.frag</file>
         <file>quad.frag</file>
         <file>quad.vert</file>
+        <file>colored_lines.frag</file>
+        <file>colored_lines.vert</file>
         <file>sphere.stl</file>
     </qresource>
 </RCC>
diff --git a/gl/mesh_surfaceangle.frag b/gl/mesh_surfaceangle.frag
new file mode 100644 (file)
index 0000000..3ee94dd
--- /dev/null
@@ -0,0 +1,17 @@
+#version 120
+
+uniform float zoom;
+
+varying vec3 ec_pos;
+
+void main() {
+    vec3 ec_normal = normalize(cross(dFdx(ec_pos), dFdy(ec_pos)));
+    ec_normal.z *= zoom;
+    ec_normal = normalize(ec_normal);
+    //rotated 10deg around the red axis for better color match
+    float x = dot(ec_normal, vec3(1.0, 0.0, 0.0));
+    float y = dot(ec_normal, vec3(0.0, 0.985, 0.174));
+    float z = dot(ec_normal, vec3(0.0, -0.174, 0.985));
+
+    gl_FragColor = vec4(0.5-0.5*x, 0.5-0.5*y, 0.5+0.5*z, 1.0);
+}
diff --git a/qt/fstl.pro b/qt/fstl.pro
deleted file mode 100644 (file)
index 5d8613d..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-QT += core gui opengl widgets
-
-TARGET = fstl
-TEMPLATE = app
-
-# Bump optimization up to -O3 in release builds
-QMAKE_CXXFLAGS_RELEASE -= -O2
-QMAKE_CXXFLAGS_RELEASE += -O3
-
-SOURCES += \
-    ../src/app.cpp\
-    ../src/main.cpp\
-    ../src/canvas.cpp \
-    ../src/mesh.cpp \
-    ../src/glmesh.cpp \
-    ../src/loader.cpp \
-    ../src/window.cpp \
-    ../src/backdrop.cpp
-
-HEADERS  += \
-    ../src/app.h\
-    ../src/canvas.h \
-    ../src/mesh.h \
-    ../src/glmesh.h \
-    ../src/loader.h \
-    ../src/window.h \
-    ../src/backdrop.h
-
-CONFIG += c++11
-
-RESOURCES += \
-    qt.qrc \
-    ../gl/gl.qrc
-
-macx {
-    QMAKE_INFO_PLIST = ../app/Info.plist
-    ICON = ../app/fstl.icns
-}
-
-win32 {
-    RC_FILE = ../exe/fstl.rc
-}
-
-linux {
-    target.path = /usr/bin
-    INSTALLS += target
-}
-
-static {
-    CONFIG += static
-}
index 982d0d00185a8bcc0510303c35858a2e7344219d..a76a7dafdc9d11f25bc60d5fc4178d827ec11882 100644 (file)
@@ -7,10 +7,6 @@
 App::App(int& argc, char *argv[]) :
     QApplication(argc, argv), window(new Window())
 {
-    QCoreApplication::setOrganizationName("mkeeter");
-    QCoreApplication::setOrganizationDomain("https://github.com/mkeeter/fstl");
-    QCoreApplication::setApplicationName("fstl");
-
     if (argc > 1)
         window->load_stl(argv[1]);
     else
@@ -20,7 +16,7 @@ App::App(int& argc, char *argv[]) :
 
 App::~App()
 {
-       delete window;
+    delete window;
 }
 
 bool App::event(QEvent* e)
diff --git a/src/axis.cpp b/src/axis.cpp
new file mode 100644 (file)
index 0000000..831f592
--- /dev/null
@@ -0,0 +1,163 @@
+#include "axis.h"
+
+const float xLet[] = {
+    -0.1, -0.2, 0,
+    0.1, 0.2, 0,
+    0.1, -0.2, 0,
+    -0.1, 0.2, 0
+};
+const float yLet[] = {
+    0, -0.2, 0,
+    0, 0, 0,
+    0, 0, 0,
+    0.1, 0.2, 0,
+    0, 0, 0,
+    -0.1, 0.2, 0
+};
+const float zLet[] = {
+    -0.1, -0.2, 0,
+    0.1, -0.2, 0,
+    0.1, -0.2, 0,
+    -0.1, 0.2, 0,
+    -0.1, 0.2, 0,
+    0.1, 0.2, 0
+};
+const int axisSegCount[] = {2, 3, 3};
+const float* axisLabels[] = {xLet, yLet, zLet};
+
+Axis::Axis()
+{
+    initializeOpenGLFunctions();
+
+    shader.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/gl/colored_lines.vert");
+    shader.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/gl/colored_lines.frag");
+    shader.link();
+    const int ptSize = 6*sizeof(float);
+    for(int lIdx = 0; lIdx < 3; lIdx++)
+    {
+        const float* l = axisLabels[lIdx];
+        const int ptCount = axisSegCount[lIdx]*2;
+        float c[3] = {0.0};
+        c[lIdx] = 1.0;//set color
+        QOpenGLBuffer b = flowerLabelVertices[lIdx];
+        b.create();
+        b.bind();
+        b.allocate(ptCount*ptSize);
+        for(int pIdx = 0; pIdx < ptCount; pIdx++)
+        {
+            b.write(pIdx*ptSize, &(l[pIdx*3]), ptSize/2);//write coords
+            b.write(pIdx*ptSize + ptSize/2, c, ptSize/2);//write color
+        }
+        b.release();
+    }
+    //Axis buffer: 6 floats per vertex, 2 vert per line, 3 lines
+    float aBuf[6*2*3] = {0.0};
+    for(int aIdx = 0; aIdx < 3; aIdx++)
+    {
+        aBuf[(2*aIdx)*6+3+aIdx] = 1.0;//Set color (last 3 floats)
+        aBuf[(2*aIdx+1)*6+3+aIdx] = 1.0;//Set color (last 3 floats)
+        aBuf[(2*aIdx+1)*6+aIdx] = 1.0;//Extend line in axis
+    }
+    //The lines which form the 'axis-flower' in the corner
+    flowerAxisVertices.create();
+    flowerAxisVertices.bind();
+    flowerAxisVertices.allocate(aBuf, sizeof(aBuf));
+    flowerAxisVertices.release();
+    //The lines which form the model-space axes
+    vertices.create();
+    vertices.bind();
+    vertices.allocate(aBuf, sizeof(aBuf));
+    vertices.release();
+}
+void Axis::setScale(QVector3D min, QVector3D max)
+{
+    //Max function. not worth importing <algorithm> just for max
+    auto Max = [](float a, float b)
+    {
+        return (a > b) ? a : b;
+    };
+    //This is how much the axes extend beyond the model
+    //We want it to be dependent on the model's size, but uniform on all axes.
+    const float axismargin = 0.25*Max(Max(max[0]-min[0], max[1]-min[1]), max[2]-min[2]);
+    vertices.bind();
+    //Manually rewrite coordinates to control axis draw lengths
+    float s = sizeof(float);
+    //aIdx*12+aIdx gets us to the set of 2 points of the axis line, plus the offset for that dimension
+    //+6 gets us to the other end of the line in that dimension
+    for(int aIdx = 0; aIdx < 3; aIdx++)
+    {
+        float t = min[aIdx]-axismargin;
+        vertices.write(s*(aIdx*12+aIdx), &t, s);
+        t = max[aIdx]+axismargin;
+        vertices.write(s*(aIdx*12+aIdx+6), &t, s);
+    }
+    vertices.release();
+}
+void Axis::draw(QMatrix4x4 transMat, QMatrix4x4 viewMat,
+    QMatrix4x4 orientMat, QMatrix4x4 aspectMat, float aspectRatio)
+{
+    shader.bind();
+    vertices.bind();
+    // Load the transform and view matrices into the shader
+    auto loadMatrixUniforms = [&](QMatrix4x4 transform, QMatrix4x4 view)
+    {
+        glUniformMatrix4fv(
+                    shader.uniformLocation("transform_matrix"),
+                    1, GL_FALSE, transform.data());
+        glUniformMatrix4fv(
+                    shader.uniformLocation("view_matrix"),
+                    1, GL_FALSE, view.data());
+    };
+    const GLuint vp = shader.attributeLocation("vertex_position");
+    const GLuint vc = shader.attributeLocation("vertex_color");
+    glEnableVertexAttribArray(vp);
+    glEnableVertexAttribArray(vc);
+    auto loadAttribPtr = [&]()
+    {
+        glVertexAttribPointer(vp, 3, GL_FLOAT, false,
+                        6 * sizeof(GLfloat), 0);
+        glVertexAttribPointer(vc, 3, GL_FLOAT, false,
+                        6 * sizeof(GLfloat),
+                        (GLvoid*)(3 * sizeof(GLfloat)));
+    };
+    loadMatrixUniforms(transMat, viewMat);
+    loadAttribPtr();
+
+    glDrawArrays(GL_LINES, 0, 3*6);
+
+    vertices.release();
+    //Next, we draw the hud axis-flower
+    flowerAxisVertices.bind();
+    glClear(GL_DEPTH_BUFFER_BIT);//Ensure hud draws over everything
+    const float hudSize = 0.2;
+    QMatrix4x4 hudMat;
+    //Move the hud to the bottom left corner with margin
+    if (aspectRatio > 1.0)
+    {
+        hudMat.translate(aspectRatio-2*hudSize, -1.0+2*hudSize, 0);
+    }
+    else
+    {
+        hudMat.translate(1.0-2*hudSize, -1.0/aspectRatio+2*hudSize, 0);
+    }
+    //Scale the hud to be small
+    hudMat.scale(hudSize, hudSize, 1);
+    loadMatrixUniforms(orientMat, aspectMat*hudMat);
+    loadAttribPtr();
+    glDrawArrays(GL_LINES, 0, 3*6);
+    flowerAxisVertices.release();
+    for(int aIdx = 0; aIdx < 3; aIdx++){
+        QVector3D transVec = QVector3D();
+        transVec[aIdx] = 1.25;//This is how far we want the letters to be extended out
+        QOpenGLBuffer b = flowerLabelVertices[aIdx];
+        //The only transform we want is to translate the letters to the ends of the axis lines
+        QMatrix4x4 labelTransMat = QMatrix4x4();
+        labelTransMat.translate(orientMat * transVec);
+        b.bind();
+        loadMatrixUniforms(labelTransMat, aspectMat * hudMat);
+        loadAttribPtr();
+        glDrawArrays(GL_LINES, 0, axisSegCount[aIdx]*2*6);
+        b.release();
+    }
+    shader.release();
+}
diff --git a/src/axis.h b/src/axis.h
new file mode 100644 (file)
index 0000000..7d0a991
--- /dev/null
@@ -0,0 +1,22 @@
+#ifndef AXIS_H
+#define AXIS_H
+
+#include <QOpenGLBuffer>
+#include <QOpenGLShaderProgram>
+#include <QOpenGLFunctions>
+
+class Axis : protected QOpenGLFunctions
+{
+public:
+    Axis();
+    void setScale(QVector3D min, QVector3D max);
+    void draw(QMatrix4x4 transMat, QMatrix4x4 viewMat,
+        QMatrix4x4 orientMat, QMatrix4x4 aspectMat, float aspectRatio);
+private:
+    QOpenGLShaderProgram shader;
+    QOpenGLBuffer vertices, //GL Buffer for model-space coords
+        flowerAxisVertices; //GL Buffer for hud-space axis lines
+    QOpenGLBuffer flowerLabelVertices[3];//Buffer for hud-space label lines
+};
+
+#endif // AXIS_H
index dd188fffb7e26a82a9841d4437926fe0c134ff23..4da41a7e0cc17c8e049182fdb2c287205c2a7c9d 100644 (file)
@@ -4,15 +4,20 @@
 
 #include "canvas.h"
 #include "backdrop.h"
+#include "axis.h"
 #include "glmesh.h"
 #include "mesh.h"
 
+const float Canvas::P_PERSPECTIVE = 0.25f;
+const float Canvas::P_ORTHOGRAPHIC = 0.0f;
+
 Canvas::Canvas(const QSurfaceFormat& format, QWidget *parent)
     : QOpenGLWidget(parent), mesh(nullptr),
       scale(1), zoom(1), tilt(90), yaw(0),
-      perspective(0.25), anim(this, "perspective"), status(" ")
+      anim(this, "perspective"), status(" "),
+      meshInfo("")
 {
-       setFormat(format);
+    setFormat(format);
     QFile styleFile(":/qt/style.qss");
     styleFile.open( QFile::ReadOnly );
     setStyleSheet(styleFile.readAll());
@@ -22,9 +27,12 @@ Canvas::Canvas(const QSurfaceFormat& format, QWidget *parent)
 
 Canvas::~Canvas()
 {
-       makeCurrent();
-       delete mesh;
-       doneCurrent();
+    makeCurrent();
+    delete mesh;
+    delete mesh_vertshader;
+    delete backdrop;
+    delete axis;
+    doneCurrent();
 }
 
 void Canvas::view_anim(float v)
@@ -34,34 +42,37 @@ void Canvas::view_anim(float v)
     anim.start();
 }
 
-void Canvas::view_orthographic()
-{
-    view_anim(0);
-}
-
-void Canvas::view_perspective()
-{
-    view_anim(0.25);
+void Canvas::view_perspective(float p, bool animate){
+    if(animate)
+    {
+        view_anim(p);
+    }
+    else
+    {
+        set_perspective(p);
+    }
 }
 
-void Canvas::draw_shaded()
+void Canvas::draw_axes(bool d)
 {
-    set_drawMode(0);
+    drawAxes = d;
+    update();
 }
 
-void Canvas::draw_wireframe()
+void Canvas::invert_zoom(bool d)
 {
-    set_drawMode(1);
+    invertZoom = d;
+    update();
 }
 
 void Canvas::load_mesh(Mesh* m, bool is_reload)
 {
+    delete mesh;
     mesh = new GLMesh(m);
-
+    QVector3D lower(m->xmin(), m->ymin(), m->zmin());
+    QVector3D upper(m->xmax(), m->ymax(), m->zmax());
     if (!is_reload)
     {
-        QVector3D lower(m->xmin(), m->ymin(), m->zmin());
-        QVector3D upper(m->xmax(), m->ymax(), m->zmax());
         center = (lower + upper) / 2;
         scale = 2 / (upper - lower).length();
 
@@ -70,7 +81,9 @@ void Canvas::load_mesh(Mesh* m, bool is_reload)
         yaw = 0;
         tilt = 90;
     }
-
+    meshInfo = QStringLiteral("Triangles: %1\nX: [%2, %3]\nY: [%4, %5]\nZ: [%6, %7]").arg(m->triCount());
+    for(int dIdx = 0; dIdx < 3; dIdx++) meshInfo = meshInfo.arg(lower[dIdx]).arg(upper[dIdx]);
+    axis->setScale(lower, upper);
     update();
 
     delete m;
@@ -88,7 +101,7 @@ void Canvas::set_perspective(float p)
     update();
 }
 
-void Canvas::set_drawMode(int mode)
+void Canvas::set_drawMode(enum DrawMode mode)
 {
     drawMode = mode;
     update();
@@ -104,45 +117,58 @@ void Canvas::initializeGL()
 {
     initializeOpenGLFunctions();
 
-    mesh_shader.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/gl/mesh.vert");
+    mesh_vertshader = new QOpenGLShader(QOpenGLShader::Vertex);
+    mesh_vertshader->compileSourceFile(":/gl/mesh.vert");
+    mesh_shader.addShader(mesh_vertshader);
     mesh_shader.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/gl/mesh.frag");
     mesh_shader.link();
-    mesh_wireframe_shader.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/gl/mesh.vert");
+    mesh_wireframe_shader.addShader(mesh_vertshader);
     mesh_wireframe_shader.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/gl/mesh_wireframe.frag");
     mesh_wireframe_shader.link();
+    mesh_surfaceangle_shader.addShader(mesh_vertshader);
+    mesh_surfaceangle_shader.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/gl/mesh_surfaceangle.frag");
+    mesh_surfaceangle_shader.link();
 
     backdrop = new Backdrop();
+    axis = new Axis();
 }
 
 
 void Canvas::paintGL()
 {
-       glClearColor(0.0, 0.0, 0.0, 0.0);
-       glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-       glEnable(GL_DEPTH_TEST);
-
-       backdrop->draw();
-       if (mesh)  draw_mesh();
-
-       if (status.isNull())  return;
-
-       QPainter painter(this);
-       painter.setRenderHint(QPainter::Antialiasing);
-       painter.drawText(10, height() - 10, status);
+    glClearColor(0.0, 0.0, 0.0, 0.0);
+    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+    glEnable(GL_DEPTH_TEST);
+    backdrop->draw();
+    if (mesh)  draw_mesh();
+    if (drawAxes) axis->draw(transform_matrix(), view_matrix(),
+        orient_matrix(), aspect_matrix(), width() / float(height()));
+
+    QPainter painter(this);
+    painter.setRenderHint(QPainter::Antialiasing);
+    float textHeight = painter.fontInfo().pointSize();
+    if (drawAxes) painter.drawText(QRect(10, textHeight, width(), height()), meshInfo);
+    painter.drawText(10, height() - textHeight, status);
 }
 
 void Canvas::draw_mesh()
 {
     QOpenGLShaderProgram* selected_mesh_shader = NULL;
-    // Set gl draw mode
-    if(drawMode == 1)
+    if(drawMode == wireframe)
     {
         selected_mesh_shader = &mesh_wireframe_shader;
         glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
     }
     else
     {
-        selected_mesh_shader = &mesh_shader;
+        if(drawMode == shaded)
+        {
+            selected_mesh_shader = &mesh_shader;
+        }
+        else
+        {
+            selected_mesh_shader = &mesh_surfaceangle_shader;
+        }
         glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
     }
 
@@ -173,18 +199,23 @@ void Canvas::draw_mesh()
     glDisableVertexAttribArray(vp);
     selected_mesh_shader->release();
 }
-
-QMatrix4x4 Canvas::transform_matrix() const
+QMatrix4x4 Canvas::orient_matrix() const
 {
     QMatrix4x4 m;
     m.rotate(tilt, QVector3D(1, 0, 0));
     m.rotate(yaw,  QVector3D(0, 0, 1));
-    m.scale(-scale, scale, -scale);
+    //We want the x axis to the right, and the z axis up
+    m.scale(-1, 1, -1);
+    return m;
+}
+QMatrix4x4 Canvas::transform_matrix() const
+{
+    QMatrix4x4 m = orient_matrix();
+    m.scale(scale);
     m.translate(-center);
     return m;
 }
-
-QMatrix4x4 Canvas::view_matrix() const
+QMatrix4x4 Canvas::aspect_matrix() const
 {
     QMatrix4x4 m;
     if (width() > height())
@@ -195,6 +226,11 @@ QMatrix4x4 Canvas::view_matrix() const
     {
         m.scale(-1, width() / float(height()), 0.5);
     }
+    return m;
+}
+QMatrix4x4 Canvas::view_matrix() const
+{
+    QMatrix4x4 m = aspect_matrix();
     m.scale(zoom, zoom, 1);
     m(3, 2) = perspective;
     return m;
@@ -246,21 +282,27 @@ void Canvas::wheelEvent(QWheelEvent *event)
 {
     // Find GL position before the zoom operation
     // (to zoom about mouse cursor)
-    auto p = event->pos();
+    auto p = event->position();
     QVector3D v(1 - p.x() / (0.5*width()),
                 p.y() / (0.5*height()) - 1, 0);
     QVector3D a = transform_matrix().inverted() *
                   view_matrix().inverted() * v;
 
-    if (event->delta() < 0)
+    if (event->angleDelta().y() < 0)
     {
-        for (int i=0; i > event->delta(); --i)
-            zoom *= 1.001;
+        for (int i=0; i > event->angleDelta().y(); --i)
+            if (invertZoom)
+                zoom /= 1.001;
+            else 
+                zoom *= 1.001;
     }
-    else if (event->delta() > 0)
+    else if (event->angleDelta().y() > 0)
     {
-        for (int i=0; i < event->delta(); ++i)
-            zoom /= 1.001;
+        for (int i=0; i < event->angleDelta().y(); ++i)
+            if (invertZoom) 
+                zoom *= 1.001;
+            else 
+                zoom /= 1.001;
     }
 
     // Then find the cursor's GL position post-zoom and adjust center.
index 7fa80c8f163bca4f09673f5373b1e81259102553..0fdff64cb501cc98c88e44294edc150823ced4b4 100644 (file)
@@ -8,19 +8,25 @@
 class GLMesh;
 class Mesh;
 class Backdrop;
+class Axis;
+
+enum DrawMode {shaded, wireframe, surfaceangle, DRAWMODECOUNT};
 
 class Canvas : public QOpenGLWidget, protected QOpenGLFunctions
 {
     Q_OBJECT
 
 public:
-       explicit Canvas(const QSurfaceFormat& format, QWidget* parent=0);
+    explicit Canvas(const QSurfaceFormat& format, QWidget* parent=0);
     ~Canvas();
 
-    void view_orthographic();
-    void view_perspective();
-    void draw_shaded();
-    void draw_wireframe();
+    const static float P_PERSPECTIVE;
+    const static float P_ORTHOGRAPHIC;
+
+    void view_perspective(float p, bool animate);
+    void draw_axes(bool d);
+    void invert_zoom(bool d);
+    void set_drawMode(enum DrawMode mode);
 
 public slots:
     void set_status(const QString& s);
@@ -28,31 +34,34 @@ public slots:
     void load_mesh(Mesh* m, bool is_reload);
 
 protected:
-       void paintGL() override;
-       void initializeGL() override;
-       void resizeGL(int width, int height) override;
+    void paintGL() override;
+    void initializeGL() override;
+    void resizeGL(int width, int height) override;
 
     void mousePressEvent(QMouseEvent* event) override;
     void mouseReleaseEvent(QMouseEvent* event) override;
     void mouseMoveEvent(QMouseEvent* event) override;
     void wheelEvent(QWheelEvent* event) override;
     
-       void set_perspective(float p);
-    void set_drawMode(int mode);
+    void set_perspective(float p);
     void view_anim(float v);
 
 private:
     void draw_mesh();
 
+    QMatrix4x4 orient_matrix() const;
     QMatrix4x4 transform_matrix() const;
+    QMatrix4x4 aspect_matrix() const;
     QMatrix4x4 view_matrix() const;
 
+    QOpenGLShader* mesh_vertshader;
     QOpenGLShaderProgram mesh_shader;
     QOpenGLShaderProgram mesh_wireframe_shader;
-       QOpenGLShaderProgram quad_shader;
+    QOpenGLShaderProgram mesh_surfaceangle_shader;
 
     GLMesh* mesh;
     Backdrop* backdrop;
+    Axis* axis;
 
     QVector3D center;
     float scale;
@@ -61,12 +70,15 @@ private:
     float yaw;
 
     float perspective;
-    int drawMode;
+    enum DrawMode drawMode;
+    bool drawAxes;
+    bool invertZoom;
     Q_PROPERTY(float perspective MEMBER perspective WRITE set_perspective);
     QPropertyAnimation anim;
 
     QPoint mouse_pos;
     QString status;
+    QString meshInfo;
 };
 
 #endif // CANVAS_H
index b5a2f04b9a43580cfbfda4ede4bd08c40549df22..38bd2ddc66796ba7498eb3ece007e445d2f564ce 100644 (file)
@@ -127,14 +127,12 @@ Mesh* Loader::load_stl()
             file.seek(0);
             return read_stl_ascii(file);
         }
-        confusing_stl = true;
-    }
-    else
-    {
-        confusing_stl = false;
+        // Otherwise, this STL is a binary stl but contains 'solid' as
+        // the first five characters.  This is a bad life choice, but
+        // we can gracefully handle it by falling through to the binary
+        // STL reader below.
     }
 
-    // Otherwise, skip the rest of the header material and read as binary
     file.seek(0);
     return read_stl_binary(file);
 }
@@ -161,7 +159,7 @@ Mesh* Loader::read_stl_binary(QFile& file)
     QVector<Vertex> verts(tri_count*3);
 
     // Dummy array, because readRawData is faster than skipRawData
-    std::unique_ptr<uint8_t> buffer(new uint8_t[tri_count * 50]);
+    std::unique_ptr<uint8_t[]> buffer(new uint8_t[tri_count * 50]);
     data.readRawData((char*)buffer.get(), tri_count * 50);
 
     // Store vertices in the array, processing one triangle at a time.
@@ -171,7 +169,7 @@ Mesh* Loader::read_stl_binary(QFile& file)
         // Load vertex data from .stl file into vertices
         for (unsigned i=0; i < 3; ++i)
         {
-            memcpy(&v[i], b, 3*sizeof(float));
+            qFromLittleEndian<float>(b, 3, &v[i]);
             b += 3 * sizeof(float);
         }
 
@@ -179,11 +177,6 @@ Mesh* Loader::read_stl_binary(QFile& file)
         b += 3 * sizeof(float) + sizeof(uint16_t);
     }
 
-    if (confusing_stl)
-    {
-        emit warning_confusing_stl();
-    }
-
     return mesh_from_verts(tri_count, verts);
 }
 
index 006f1d1d1469a5524c4d2fdc0a1c7168aad4639d..6f5942f0bf9733322208f14e01401ff7755580dd 100644 (file)
@@ -26,16 +26,11 @@ signals:
 
     void error_bad_stl();
     void error_empty_mesh();
-    void warning_confusing_stl();
     void error_missing_file();
 
 private:
     const QString filename;
     bool is_reload;
-
-    /*  Used to warn on binary STLs that begin with the word 'solid'" */
-    bool confusing_stl;
-
 };
 
 #endif // LOADER_H
index 4b76222110f3f3fa97c1793b78626e2f6db32537..4ec872275b0eef795c7c68c6751392babe3be565 100644 (file)
@@ -4,6 +4,10 @@
 
 int main(int argc, char *argv[])
 {
+    QCoreApplication::setOrganizationName("fstl-app");
+    QCoreApplication::setOrganizationDomain("https://github.com/fstl-app/fstl");
+    QCoreApplication::setApplicationName("fstl");
+    QCoreApplication::setApplicationVersion(FSTL_VERSION);
     App a(argc, argv);
     return a.exec();
 }
index b971029bdbb4aea31b4e023bfbd6161e07897642..2c44a2c5e19fc4c70f40bf3a3ba59637a53fbef4 100644 (file)
@@ -42,6 +42,10 @@ float Mesh::max(size_t start) const
     return v;
 }
 
+int Mesh::triCount() const
+{
+    return indices.size()/3;
+}
 bool Mesh::empty() const
 {
     return vertices.size() == 0;
index c4e888bd8a405e91fdc07e3edbc3359aaf36a58f..9e2ab0c96c3ce3f8231b63f52964233a11ff86c5 100644 (file)
@@ -21,6 +21,7 @@ public:
     float ymax() const { return max(1); }
     float zmax() const { return max(2); }
 
+    int triCount() const;
     bool empty() const;
 
 private:
index 09d74eddad444f5fb811d500a570d901b85fb8c4..e9f3f94ebf2d980e1e088d876d8acf53ae89c699 100644 (file)
@@ -5,6 +5,12 @@
 #include "loader.h"
 
 const QString Window::RECENT_FILE_KEY = "recentFiles";
+const QString Window::INVERT_ZOOM_KEY = "invertZoom";
+const QString Window::AUTORELOAD_KEY = "autoreload";
+const QString Window::DRAW_AXES_KEY = "drawAxes";
+const QString Window::PROJECTION_KEY = "projection";
+const QString Window::DRAW_MODE_KEY = "drawMode";
+const QString Window::WINDOW_GEOM_KEY = "windowGeometry";
 
 Window::Window(QWidget *parent) :
     QMainWindow(parent),
@@ -12,12 +18,16 @@ Window::Window(QWidget *parent) :
     about_action(new QAction("About", this)),
     quit_action(new QAction("Quit", this)),
     perspective_action(new QAction("Perspective", this)),
-    orthogonal_action(new QAction("Orthographic", this)),
+    orthographic_action(new QAction("Orthographic", this)),
     shaded_action(new QAction("Shaded", this)),
     wireframe_action(new QAction("Wireframe", this)),
+    surfaceangle_action(new QAction("Surface Angle", this)),
+    axes_action(new QAction("Draw Axes", this)),
+    invert_zoom_action(new QAction("Invert Zoom", this)),
     reload_action(new QAction("Reload", this)),
     autoreload_action(new QAction("Autoreload", this)),
     save_screenshot_action(new QAction("Save Screenshot", this)),
+    hide_menuBar_action(new QAction("Hide Menu Bar", this)),
     recent_files(new QMenu("Open recent", this)),
     recent_files_group(new QActionGroup(this)),
     recent_files_clear_action(new QAction("Clear recent files", this)),
@@ -44,14 +54,14 @@ Window::Window(QWidget *parent) :
     open_action->setShortcut(QKeySequence::Open);
     QObject::connect(open_action, &QAction::triggered,
                      this, &Window::on_open);
+    this->addAction(open_action);
 
     quit_action->setShortcut(QKeySequence::Quit);
     QObject::connect(quit_action, &QAction::triggered,
                      this, &Window::close);
+    this->addAction(quit_action);
 
     autoreload_action->setCheckable(true);
-    autoreload_action->setChecked(true);
-    autoreload_action->setEnabled(false);
     QObject::connect(autoreload_action, &QAction::triggered,
             this, &Window::on_autoreload_triggered);
 
@@ -86,14 +96,13 @@ Window::Window(QWidget *parent) :
     auto view_menu = menuBar()->addMenu("View");
     auto projection_menu = view_menu->addMenu("Projection");
     projection_menu->addAction(perspective_action);
-    projection_menu->addAction(orthogonal_action);
+    projection_menu->addAction(orthographic_action);
     auto projections = new QActionGroup(projection_menu);
-    for (auto p : {perspective_action, orthogonal_action})
+    for (auto p : {perspective_action, orthographic_action})
     {
         projections->addAction(p);
         p->setCheckable(true);
     }
-    perspective_action->setChecked(true);
     projections->setExclusive(true);
     QObject::connect(projections, &QActionGroup::triggered,
                      this, &Window::on_projection);
@@ -101,27 +110,78 @@ Window::Window(QWidget *parent) :
     auto draw_menu = view_menu->addMenu("Draw Mode");
     draw_menu->addAction(shaded_action);
     draw_menu->addAction(wireframe_action);
+    draw_menu->addAction(surfaceangle_action);
     auto drawModes = new QActionGroup(draw_menu);
-    for (auto p : {shaded_action, wireframe_action})
+    for (auto p : {shaded_action, wireframe_action, surfaceangle_action})
     {
         drawModes->addAction(p);
         p->setCheckable(true);
     }
-    shaded_action->setChecked(true);
     drawModes->setExclusive(true);
     QObject::connect(drawModes, &QActionGroup::triggered,
                      this, &Window::on_drawMode);
+    view_menu->addAction(axes_action);
+    axes_action->setCheckable(true);
+    QObject::connect(axes_action, &QAction::triggered,
+            this, &Window::on_drawAxes);
+
+    view_menu->addAction(invert_zoom_action);
+    invert_zoom_action->setCheckable(true);
+    QObject::connect(invert_zoom_action, &QAction::triggered,
+            this, &Window::on_invertZoom);       
+
+    view_menu->addAction(hide_menuBar_action);
+    hide_menuBar_action->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_C);
+    hide_menuBar_action->setCheckable(true);
+    QObject::connect(hide_menuBar_action, &QAction::toggled,
+            this, &Window::on_hide_menuBar);
+    this->addAction(hide_menuBar_action);
 
     auto help_menu = menuBar()->addMenu("Help");
     help_menu->addAction(about_action);
 
+    load_persist_settings();
+}
+
+void Window::load_persist_settings(){
+    QSettings settings;
+    bool invert_zoom = settings.value(INVERT_ZOOM_KEY, false).toBool();
+    canvas->invert_zoom(invert_zoom);
+    invert_zoom_action->setChecked(invert_zoom);
+
+    autoreload_action->setChecked(settings.value(AUTORELOAD_KEY, true).toBool());
+
+    bool draw_axes = settings.value(DRAW_AXES_KEY, false).toBool();
+    canvas->draw_axes(draw_axes);
+    axes_action->setChecked(draw_axes);
+
+    QString projection = settings.value(PROJECTION_KEY, "perspective").toString();
+    if(projection == "perspective"){
+        canvas->view_perspective(Canvas::P_PERSPECTIVE, false);
+        perspective_action->setChecked(true);
+    }else{
+        canvas->view_perspective(Canvas::P_ORTHOGRAPHIC, false);
+        orthographic_action->setChecked(true);
+    }
+
+    DrawMode draw_mode = (DrawMode)settings.value(DRAW_MODE_KEY, DRAWMODECOUNT).toInt();
+    
+    if(draw_mode >= DRAWMODECOUNT)
+    {
+        draw_mode = shaded;
+    }
+    canvas->set_drawMode(draw_mode);
+    QAction* (dm_acts[]) = {shaded_action, wireframe_action, surfaceangle_action};
+    dm_acts[draw_mode]->setChecked(true);
+
     resize(600, 400);
+    restoreGeometry(settings.value(WINDOW_GEOM_KEY).toByteArray());
 }
 
 void Window::on_open()
 {
     QString filename = QFileDialog::getOpenFileName(
-                this, "Load .stl file", QString(), "*.stl");
+                this, "Load .stl file", QString(), "STL files (*.stl, *.STL)");
     if (!filename.isNull())
     {
         load_stl(filename);
@@ -131,11 +191,11 @@ void Window::on_open()
 void Window::on_about()
 {
     QMessageBox::about(this, "",
-        "<p align=\"center\"><b>fstl</b></p>"
+        "<p align=\"center\"><b>fstl</b><br>" FSTL_VERSION "</p>"
         "<p>A fast viewer for <code>.stl</code> files.<br>"
-        "<a href=\"https://github.com/mkeeter/fstl\""
-        "   style=\"color: #93a1a1;\">https://github.com/mkeeter/fstl</a></p>"
-        "<p>© 2014-2017 Matthew Keeter<br>"
+        "<a href=\"https://github.com/fstl-app/fstl\""
+        "   style=\"color: #93a1a1;\">https://github.com/fstl-app/fstl</a></p>"
+        "<p>© 2014-2022 Matthew Keeter<br>"
         "<a href=\"mailto:matt.j.keeter@gmail.com\""
         "   style=\"color: #93a1a1;\">matt.j.keeter@gmail.com</a></p>");
 }
@@ -155,14 +215,6 @@ void Window::on_empty_mesh()
                           "This file is syntactically correct<br>but contains no triangles.");
 }
 
-void Window::on_confusing_stl()
-{
-    QMessageBox::warning(this, "Warning",
-                         "<b>Warning:</b><br>"
-                         "This <code>.stl</code> file begins with <code>solid </code>but appears to be a binary file.<br>"
-                         "<code>fstl</code> loaded it, but other programs may be confused by this file.");
-}
-
 void Window::on_missing_file()
 {
     QMessageBox::critical(this, "Error",
@@ -206,24 +258,45 @@ void Window::on_projection(QAction* proj)
 {
     if (proj == perspective_action)
     {
-        canvas->view_perspective();
+        canvas->view_perspective(Canvas::P_PERSPECTIVE, true);
+        QSettings().setValue(PROJECTION_KEY, "perspective");
     }
     else
     {
-        canvas->view_orthographic();
+        canvas->view_perspective(Canvas::P_ORTHOGRAPHIC, true);
+        QSettings().setValue(PROJECTION_KEY, "orthographic");
     }
 }
 
-void Window::on_drawMode(QAction* mode)
+void Window::on_drawMode(QAction* act)
 {
-    if (mode == shaded_action)
+    DrawMode mode;
+    if (act == shaded_action)
     {
-        canvas->draw_shaded();
+        mode = shaded;
+    }
+    else if (act == wireframe_action)
+    {
+        mode = wireframe;
     }
     else
     {
-        canvas->draw_wireframe();
+        mode = surfaceangle;
     }
+    canvas->set_drawMode(mode);
+    QSettings().setValue(DRAW_MODE_KEY, mode);
+}
+
+void Window::on_drawAxes(bool d)
+{
+    canvas->draw_axes(d);
+    QSettings().setValue(DRAW_AXES_KEY, d);
+}
+
+void Window::on_invertZoom(bool d)
+{
+    canvas->invert_zoom(d);
+    QSettings().setValue(INVERT_ZOOM_KEY, d);
 }
 
 void Window::on_watched_change(const QString& filename)
@@ -240,6 +313,7 @@ void Window::on_autoreload_triggered(bool b)
     {
         on_reload();
     }
+    QSettings().setValue(AUTORELOAD_KEY, b);
 }
 
 void Window::on_clear_recent()
@@ -293,6 +367,11 @@ void Window::on_save_screenshot()
     }
 }
 
+void Window::on_hide_menuBar()
+{
+    menuBar()->setVisible(!hide_menuBar_action->isChecked());
+}
+
 void Window::rebuild_recent_files()
 {
     QSettings settings;
@@ -347,8 +426,6 @@ bool Window::load_stl(const QString& filename, bool is_reload)
               this, &Window::on_bad_stl);
     connect(loader, &Loader::error_empty_mesh,
               this, &Window::on_empty_mesh);
-    connect(loader, &Loader::warning_confusing_stl,
-              this, &Window::on_confusing_stl);
     connect(loader, &Loader::error_missing_file,
               this, &Window::on_missing_file);
 
@@ -367,7 +444,6 @@ bool Window::load_stl(const QString& filename, bool is_reload)
                   this, &Window::set_watched);
         connect(loader, &Loader::loaded_file,
                   this, &Window::on_loaded);
-        autoreload_action->setEnabled(true);
         reload_action->setEnabled(true);
     }
 
@@ -390,6 +466,18 @@ void Window::dropEvent(QDropEvent *event)
     load_stl(event->mimeData()->urls().front().toLocalFile());
 }
 
+void Window::resizeEvent(QResizeEvent *event)
+{
+    QSettings().setValue(WINDOW_GEOM_KEY, saveGeometry());
+    QWidget::resizeEvent(event);
+}
+
+void Window::moveEvent(QMoveEvent *event)
+{
+    QSettings().setValue(WINDOW_GEOM_KEY, saveGeometry());
+    QWidget::moveEvent(event);
+}
+
 void Window::sorted_insert(QStringList& list, const QCollator& collator, const QString& value)
 {
     int start = 0;
@@ -441,7 +529,7 @@ void Window::build_folder_file_list()
 QPair<QString, QString> Window::get_file_neighbors()
 {
     if (current_file.isEmpty()) {
-        return QPair<QString, QString>(QString::null, QString::null);
+        return QPair<QString, QString>(QString(), QString());
     }
 
     build_folder_file_list();
@@ -451,8 +539,8 @@ QPair<QString, QString> Window::get_file_neighbors()
     QString current_dir = fileInfo.absoluteDir().absolutePath();
     QString current_name = fileInfo.fileName();
 
-    QString prev = QString::null;
-    QString next = QString::null;
+    QString prev = QString();
+    QString next = QString();
 
     QListIterator<QString> fileIterator(lookup_folder_files);
     while (fileIterator.hasNext()) {
@@ -514,6 +602,11 @@ void Window::keyPressEvent(QKeyEvent* event)
         load_next();
         return;
     }
+    else if (event->key() == Qt::Key_Escape)
+    {
+        hide_menuBar_action->setChecked(false);
+        return;
+    }
 
     QMainWindow::keyPressEvent(event);
 }
index 8619b4d96d323a1deaeda786636279c602b04884..81cd29f5f6a42b15f91539bc3048f448c0d49c38 100644 (file)
@@ -20,6 +20,8 @@ public:
 protected:
     void dragEnterEvent(QDragEnterEvent* event) override;
     void dropEvent(QDropEvent* event) override;
+    void resizeEvent(QResizeEvent *event) override;
+    void moveEvent(QMoveEvent *event) override;
     void keyPressEvent(QKeyEvent* event) override;
 
 public slots:
@@ -28,7 +30,6 @@ public slots:
     void on_bad_stl();
     void on_empty_mesh();
     void on_missing_file();
-    void on_confusing_stl();
 
     void enable_open();
     void disable_open();
@@ -38,6 +39,8 @@ public slots:
 private slots:
     void on_projection(QAction* proj);
     void on_drawMode(QAction* mode);
+    void on_drawAxes(bool d);
+    void on_invertZoom(bool d);
     void on_watched_change(const QString& filename);
     void on_reload();
     void on_autoreload_triggered(bool r);
@@ -45,9 +48,11 @@ private slots:
     void on_load_recent(QAction* a);
     void on_loaded(const QString& filename);
     void on_save_screenshot();
-       
+    void on_hide_menuBar();
+
 private:
     void rebuild_recent_files();
+    void load_persist_settings();
     void sorted_insert(QStringList& list, const QCollator& collator, const QString& value);
     void build_folder_file_list();
     QPair<QString, QString> get_file_neighbors();
@@ -56,18 +61,29 @@ private:
     QAction* const about_action;
     QAction* const quit_action;
     QAction* const perspective_action;
-    QAction* const orthogonal_action;
+    QAction* const orthographic_action;
     QAction* const shaded_action;
     QAction* const wireframe_action;
+    QAction* const surfaceangle_action;
+    QAction* const axes_action;
+    QAction* const invert_zoom_action;
     QAction* const reload_action;
     QAction* const autoreload_action;
     QAction* const save_screenshot_action;
+    QAction* const hide_menuBar_action;
 
     QMenu* const recent_files;
     QActionGroup* const recent_files_group;
     QAction* const recent_files_clear_action;
     const static int MAX_RECENT_FILES=8;
     const static QString RECENT_FILE_KEY;
+    const static QString INVERT_ZOOM_KEY;
+    const static QString AUTORELOAD_KEY;
+    const static QString DRAW_AXES_KEY;
+    const static QString PROJECTION_KEY;
+    const static QString DRAW_MODE_KEY;
+    const static QString WINDOW_GEOM_KEY;
+
     QString current_file;
     QString lookup_folder;
     QStringList lookup_folder_files;