From: Jakob Haufe Date: Wed, 29 Jun 2022 14:02:12 +0000 (+0200) Subject: New upstream version 0.10.0 X-Git-Url: https://git.sur5r.net/?p=fstl;a=commitdiff_plain;h=refs%2Fheads%2Fupstream New upstream version 0.10.0 --- diff --git a/CMakeLists.txt b/CMakeLists.txt index 66cc168..0628afa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 - $ - $ - $ - $ - $ - $ - 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 $ $ - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ - ) - 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 + $ + $ + $ + $ + $ + $ + 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 $ $ + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ + ) + 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) diff --git a/README.md b/README.md index a8a3d18..c58eeae 100644 --- 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 ``` diff --git a/app/package.sh b/app/package.sh index bdb4999..6cf065b 100755 --- a/app/package.sh +++ b/app/package.sh @@ -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 index 0000000..1c02e1b --- /dev/null +++ b/gl/colored_lines.frag @@ -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 index 0000000..2100403 --- /dev/null +++ b/gl/colored_lines.vert @@ -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; +} diff --git a/gl/gl.qrc b/gl/gl.qrc index 776226c..6e8baa9 100644 --- a/gl/gl.qrc +++ b/gl/gl.qrc @@ -3,8 +3,11 @@ mesh.frag mesh.vert mesh_wireframe.frag + mesh_surfaceangle.frag quad.frag quad.vert + colored_lines.frag + colored_lines.vert sphere.stl diff --git a/gl/mesh_surfaceangle.frag b/gl/mesh_surfaceangle.frag new file mode 100644 index 0000000..3ee94dd --- /dev/null +++ b/gl/mesh_surfaceangle.frag @@ -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 index 5d8613d..0000000 --- a/qt/fstl.pro +++ /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 -} diff --git a/src/app.cpp b/src/app.cpp index 982d0d0..a76a7da 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -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 index 0000000..831f592 --- /dev/null +++ b/src/axis.cpp @@ -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 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 index 0000000..7d0a991 --- /dev/null +++ b/src/axis.h @@ -0,0 +1,22 @@ +#ifndef AXIS_H +#define AXIS_H + +#include +#include +#include + +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 diff --git a/src/canvas.cpp b/src/canvas.cpp index dd188ff..4da41a7 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -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. diff --git a/src/canvas.h b/src/canvas.h index 7fa80c8..0fdff64 100644 --- a/src/canvas.h +++ b/src/canvas.h @@ -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 diff --git a/src/loader.cpp b/src/loader.cpp index b5a2f04..38bd2dd 100644 --- a/src/loader.cpp +++ b/src/loader.cpp @@ -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 verts(tri_count*3); // Dummy array, because readRawData is faster than skipRawData - std::unique_ptr buffer(new uint8_t[tri_count * 50]); + std::unique_ptr 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(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); } diff --git a/src/loader.h b/src/loader.h index 006f1d1..6f5942f 100644 --- a/src/loader.h +++ b/src/loader.h @@ -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 diff --git a/src/main.cpp b/src/main.cpp index 4b76222..4ec8722 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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(); } diff --git a/src/mesh.cpp b/src/mesh.cpp index b971029..2c44a2c 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -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; diff --git a/src/mesh.h b/src/mesh.h index c4e888b..9e2ab0c 100644 --- a/src/mesh.h +++ b/src/mesh.h @@ -21,6 +21,7 @@ public: float ymax() const { return max(1); } float zmax() const { return max(2); } + int triCount() const; bool empty() const; private: diff --git a/src/window.cpp b/src/window.cpp index 09d74ed..e9f3f94 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -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, "", - "

fstl

" + "

fstl
" FSTL_VERSION "

" "

A fast viewer for .stl files.
" - "https://github.com/mkeeter/fstl

" - "

© 2014-2017 Matthew Keeter
" + "https://github.com/fstl-app/fstl

" + "

© 2014-2022 Matthew Keeter
" "matt.j.keeter@gmail.com

"); } @@ -155,14 +215,6 @@ void Window::on_empty_mesh() "This file is syntactically correct
but contains no triangles."); } -void Window::on_confusing_stl() -{ - QMessageBox::warning(this, "Warning", - "Warning:
" - "This .stl file begins with solid but appears to be a binary file.
" - "fstl 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 Window::get_file_neighbors() { if (current_file.isEmpty()) { - return QPair(QString::null, QString::null); + return QPair(QString(), QString()); } build_folder_file_list(); @@ -451,8 +539,8 @@ QPair 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 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); } diff --git a/src/window.h b/src/window.h index 8619b4d..81cd29f 100644 --- a/src/window.h +++ b/src/window.h @@ -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 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;