New upstream version 0.9.4 upstream
authorJakob Haufe <sur5r@debian.org>
Mon, 4 Jan 2021 21:19:16 +0000 (21:19 +0000)
committerJakob Haufe <sur5r@debian.org>
Mon, 4 Jan 2021 21:19:16 +0000 (21:19 +0000)
18 files changed:
CMakeLists.txt [new file with mode: 0644]
README.md
gl/gl.qrc
gl/mesh_wireframe.frag [new file with mode: 0644]
src/app.cpp
src/app.h
src/backdrop.cpp
src/backdrop.h
src/canvas.cpp
src/canvas.h
src/glmesh.cpp
src/glmesh.h
src/loader.cpp
src/mesh.cpp
src/mesh.h
src/vertex.h [new file with mode: 0644]
src/window.cpp
src/window.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644 (file)
index 0000000..66cc168
--- /dev/null
@@ -0,0 +1,147 @@
+#### Fast .stl viewer #####
+# Original Project Author: Matt Keeter Copyright 2014 -2017
+# Author: Paul Tsouchlos Copyright 2017
+
+cmake_minimum_required(VERSION 3.3)
+
+# Setting -std=c++11
+set(CMAKE_CXX_STANDARD 11)
+# Setting standard to required, as requisted by DeveloperPaul123 on github
+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 (PROJECT_VERSION "${FSTL_VERSION_MAJOR}.${FSTL_VERSION_MINOR}.${FSTL_VERSION_PATCH}")
+
+project(fstl)
+
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+set(CMAKE_AUTOUIC ON)
+
+#set project sources
+set(Project_Sources src/app.cpp
+src/backdrop.cpp
+src/canvas.cpp
+src/glmesh.cpp
+src/loader.cpp
+src/main.cpp
+src/mesh.cpp
+src/window.cpp)
+
+#set project headers. 
+set(Project_Headers src/app.h
+src/backdrop.h
+src/canvas.h
+src/glmesh.h
+src/loader.h
+src/mesh.h
+src/window.h)
+
+#set project resources and icon resource
+set(Project_Resources qt/qt.qrc gl/gl.qrc)
+set(Icon_Resource exe/fstl.rc)
+
+#set Policy CMP0072 FindOpenGL behavior
+set(OpenGL_GL_PREFERENCE GLVND)
+
+#find required packages. 
+find_package(Qt5 REQUIRED COMPONENTS Core Gui Widgets OpenGL)
+find_package(OpenGL REQUIRED)
+find_package(Threads REQUIRED)
+
+#add resources to RCC
+qt5_add_resources(Project_Resources_RCC ${Project_Resources})
+
+#tell CMake AUTOGEN to skip autogen on the generated qrc files
+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)
+  set(Fstl_LINK_FLAGS ${CMAKE_CURRENT_SOURCE_DIR}/${Icon_Resource})
+  set_target_properties(fstl PROPERTIES LINK_FLAGS ${Fstl_LINK_FLAGS})
+endif(WIN32)
+
+# Add version definitions to use within the code. 
+target_compile_definitions(fstl PRIVATE -DFSTL_VERSION="${PROJECT_VERSION}")
+
+#installer information that is platform independent
+set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Fast .stl file viewer.")
+set(CPACK_PACKAGE_VERSION_MAJOR ${FSTL_VERSION_MAJOR})
+set(CPACK_PACKAGE_VERSION_MINOR ${FSTL_VERSION_MINOR})
+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)
+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_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/app/fstl.icns")
+else()
+       install(TARGETS fstl RUNTIME DESTINATION bin)
+
+       set(CPACK_GENERATOR "DEB;RPM")
+       set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}")
+endif(WIN32)
+
+include(CPack)
index b4ab04b55bf6ee1f58861c01d59e3b3d5b1b786e..a8a3d18422b7cb11b477bc29b4b6c197fecaee0a 100644 (file)
--- a/README.md
+++ b/README.md
@@ -11,12 +11,12 @@ 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)  
+![Eiffel tower](http://mattkeeter.com/projects/fstl/eiffel.png)
 (credit to [Pranav Panchal](https://grabcad.com/pranav.panchal))
 
 ## Building
 
-The only dependency for `fstl` is [Qt](https://www.qt.io).
+The only dependency for `fstl` is [Qt 5](https://www.qt.io).
 
 ### macOS
 
@@ -45,6 +45,25 @@ This should produce two new files in the root directory:
 - `fstl.app` is a standalone application that can be copied to `/Applications`
 - `fstl.dmg` is a disk image that can be given to a friend
 
+### Linux
+
+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:
+```
+git clone https://github.com/mkeeter/fstl
+cd fstl
+mkdir build
+cd build
+
+qmake ../qt/fstl.pro  # For qmake build
+cmake ..              # For CMake build
+
+make -j8
+./fstl
+```
+
 --------------------------------------------------------------------------------
 
 # License
index ef93a0e4895e6d87a73ed11ef709646cbbb65d74..776226c0d2b069bc4d4d380d496627ba5376e82d 100644 (file)
--- a/gl/gl.qrc
+++ b/gl/gl.qrc
@@ -2,6 +2,7 @@
     <qresource prefix="gl/">
         <file>mesh.frag</file>
         <file>mesh.vert</file>
+        <file>mesh_wireframe.frag</file>
         <file>quad.frag</file>
         <file>quad.vert</file>
         <file>sphere.stl</file>
diff --git a/gl/mesh_wireframe.frag b/gl/mesh_wireframe.frag
new file mode 100644 (file)
index 0000000..13f001c
--- /dev/null
@@ -0,0 +1,9 @@
+#version 120
+
+uniform float zoom;
+
+varying vec3 ec_pos;
+
+void main() {
+    gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
+}
index d18204898d5dd5d5eb006d8d880a9b67a1bd2f76..982d0d00185a8bcc0510303c35858a2e7344219d 100644 (file)
@@ -7,11 +7,20 @@
 App::App(int& argc, char *argv[]) :
     QApplication(argc, argv), window(new Window())
 {
-    window->show();
+    QCoreApplication::setOrganizationName("mkeeter");
+    QCoreApplication::setOrganizationDomain("https://github.com/mkeeter/fstl");
+    QCoreApplication::setApplicationName("fstl");
+
     if (argc > 1)
         window->load_stl(argv[1]);
     else
         window->load_stl(":gl/sphere.stl");
+    window->show();
+}
+
+App::~App()
+{
+       delete window;
 }
 
 bool App::event(QEvent* e)
index 2e06c249b26d2b20f6a59843f37bfe54332f4f0e..6afb4d702e1b3d327e7e2b4df509bbb0adb01c50 100644 (file)
--- a/src/app.h
+++ b/src/app.h
@@ -10,8 +10,9 @@ class App : public QApplication
     Q_OBJECT
 public:
     explicit App(int& argc, char *argv[]);
+       ~App();
 protected:
-    bool event(QEvent* e);
+    bool event(QEvent* e) override;
 private:
     Window* const window;
 
index 13d0aa39ae7e22b54978a79eaf4d661830d06ce1..4ef1bba7c8d455208297a8afb2641be954ad32fd 100644 (file)
@@ -2,10 +2,10 @@
 
 Backdrop::Backdrop()
 {
-    initializeGLFunctions();
+    initializeOpenGLFunctions();
 
-    shader.addShaderFromSourceFile(QGLShader::Vertex, ":/gl/quad.vert");
-    shader.addShaderFromSourceFile(QGLShader::Fragment, ":/gl/quad.frag");
+    shader.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/gl/quad.vert");
+    shader.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/gl/quad.frag");
     shader.link();
 
     float vbuf[] = {
index a2398c82ac546096997edd113a86b0601623c780..95ae48bd59d7e5b4294419812f30e7dcec08ba63 100644 (file)
@@ -1,18 +1,18 @@
 #ifndef BACKDROP_H
 #define BACKDROP_H
 
-#include <QtOpenGL/QGLFunctions>
-#include <QtOpenGL/QGLShaderProgram>
-#include <QtOpenGL/QGLBuffer>
+#include <QOpenGLBuffer>
+#include <QOpenGLShaderProgram>
+#include <QOpenGLFunctions>
 
-class Backdrop : protected QGLFunctions
+class Backdrop : protected QOpenGLFunctions
 {
 public:
     Backdrop();
     void draw();
 private:
-    QGLShaderProgram shader;
-    QGLBuffer vertices;
+    QOpenGLShaderProgram shader;
+    QOpenGLBuffer vertices;
 };
 
 #endif // BACKDROP_H
index b42a2be85957b2679696a0029c4371019c0a9f22..dd188fffb7e26a82a9841d4437926fe0c134ff23 100644 (file)
@@ -1,5 +1,4 @@
 #include <QMouseEvent>
-#include <QDebug>
 
 #include <cmath>
 
@@ -8,11 +7,12 @@
 #include "glmesh.h"
 #include "mesh.h"
 
-Canvas::Canvas(const QGLFormat& format, QWidget *parent)
-    : QGLWidget(format, parent), mesh(NULL),
+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(" ")
 {
+       setFormat(format);
     QFile styleFile(":/qt/style.qss");
     styleFile.open( QFile::ReadOnly );
     setStyleSheet(styleFile.readAll());
@@ -22,7 +22,9 @@ Canvas::Canvas(const QGLFormat& format, QWidget *parent)
 
 Canvas::~Canvas()
 {
-    delete mesh;
+       makeCurrent();
+       delete mesh;
+       doneCurrent();
 }
 
 void Canvas::view_anim(float v)
@@ -42,6 +44,16 @@ void Canvas::view_perspective()
     view_anim(0.25);
 }
 
+void Canvas::draw_shaded()
+{
+    set_drawMode(0);
+}
+
+void Canvas::draw_wireframe()
+{
+    set_drawMode(1);
+}
+
 void Canvas::load_mesh(Mesh* m, bool is_reload)
 {
     mesh = new GLMesh(m);
@@ -76,6 +88,12 @@ void Canvas::set_perspective(float p)
     update();
 }
 
+void Canvas::set_drawMode(int mode)
+{
+    drawMode = mode;
+    update();
+}
+
 void Canvas::clear_status()
 {
     status = "";
@@ -84,59 +102,76 @@ void Canvas::clear_status()
 
 void Canvas::initializeGL()
 {
-    initializeGLFunctions();
+    initializeOpenGLFunctions();
 
-    mesh_shader.addShaderFromSourceFile(QGLShader::Vertex, ":/gl/mesh.vert");
-    mesh_shader.addShaderFromSourceFile(QGLShader::Fragment, ":/gl/mesh.frag");
+    mesh_shader.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/gl/mesh.vert");
+    mesh_shader.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/gl/mesh.frag");
     mesh_shader.link();
+    mesh_wireframe_shader.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/gl/mesh.vert");
+    mesh_wireframe_shader.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/gl/mesh_wireframe.frag");
+    mesh_wireframe_shader.link();
 
     backdrop = new Backdrop();
 }
 
-void Canvas::paintEvent(QPaintEvent *event)
-{
-    Q_UNUSED(event);
 
-    glClearColor(0.0, 0.0, 0.0, 0.0);
-    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-    glEnable(GL_DEPTH_TEST);
+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();
+       backdrop->draw();
+       if (mesh)  draw_mesh();
 
-    if (status.isNull())    return;
+       if (status.isNull())  return;
 
-    QPainter painter(this);
-    painter.setRenderHint(QPainter::Antialiasing);
-    painter.drawText(10, height() - 10, status);
+       QPainter painter(this);
+       painter.setRenderHint(QPainter::Antialiasing);
+       painter.drawText(10, height() - 10, status);
 }
 
-
 void Canvas::draw_mesh()
 {
-    mesh_shader.bind();
+    QOpenGLShaderProgram* selected_mesh_shader = NULL;
+    // Set gl draw mode
+    if(drawMode == 1)
+    {
+        selected_mesh_shader = &mesh_wireframe_shader;
+        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+    }
+    else
+    {
+        selected_mesh_shader = &mesh_shader;
+        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+    }
+
+    selected_mesh_shader->bind();
 
     // Load the transform and view matrices into the shader
     glUniformMatrix4fv(
-                mesh_shader.uniformLocation("transform_matrix"),
+                selected_mesh_shader->uniformLocation("transform_matrix"),
                 1, GL_FALSE, transform_matrix().data());
     glUniformMatrix4fv(
-                mesh_shader.uniformLocation("view_matrix"),
+                selected_mesh_shader->uniformLocation("view_matrix"),
                 1, GL_FALSE, view_matrix().data());
 
     // Compensate for z-flattening when zooming
-    glUniform1f(mesh_shader.uniformLocation("zoom"), 1/zoom);
+    glUniform1f(selected_mesh_shader->uniformLocation("zoom"), 1/zoom);
 
     // Find and enable the attribute location for vertex position
-    const GLuint vp = mesh_shader.attributeLocation("vertex_position");
+    const GLuint vp = selected_mesh_shader->attributeLocation("vertex_position");
     glEnableVertexAttribArray(vp);
 
     // Then draw the mesh with that vertex position
     mesh->draw(vp);
 
+    // Reset draw mode for the background and anything else that needs to be drawn
+    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+
     // Clean up state machine
     glDisableVertexAttribArray(vp);
-    mesh_shader.release();
+    selected_mesh_shader->release();
 }
 
 QMatrix4x4 Canvas::transform_matrix() const
index 4dae29891c0c3e60d14b9223b6668141dc9a65db..7fa80c8f163bca4f09673f5373b1e81259102553 100644 (file)
@@ -1,55 +1,55 @@
 #ifndef CANVAS_H
 #define CANVAS_H
 
-#include <QWidget>
-#include <QPropertyAnimation>
-#include <QtOpenGL/QGLWidget>
-#include <QtOpenGL/QGLFunctions>
-#include <QtOpenGL/QGLShaderProgram>
-#include <QMatrix4x4>
+#include <QtOpenGL>
+#include <QSurfaceFormat>
+#include <QOpenGLShaderProgram>
 
 class GLMesh;
 class Mesh;
 class Backdrop;
 
-class Canvas : public QGLWidget, protected QGLFunctions
+class Canvas : public QOpenGLWidget, protected QOpenGLFunctions
 {
     Q_OBJECT
 
 public:
-    Canvas(const QGLFormat& format, QWidget* parent=0);
-
-    void initializeGL();
-    void paintEvent(QPaintEvent* event);
+       explicit Canvas(const QSurfaceFormat& format, QWidget* parent=0);
     ~Canvas();
 
     void view_orthographic();
     void view_perspective();
+    void draw_shaded();
+    void draw_wireframe();
 
 public slots:
     void set_status(const QString& s);
     void clear_status();
     void load_mesh(Mesh* m, bool is_reload);
 
-
 protected:
-    void mousePressEvent(QMouseEvent* event);
-    void mouseReleaseEvent(QMouseEvent* event);
-    void mouseMoveEvent(QMouseEvent* event);
-    void wheelEvent(QWheelEvent* event);
-    void resizeGL(int width, int height);
-    void set_perspective(float p);
+       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 view_anim(float v);
 
-
 private:
     void draw_mesh();
 
     QMatrix4x4 transform_matrix() const;
     QMatrix4x4 view_matrix() const;
 
-    QGLShaderProgram mesh_shader;
-    QGLShaderProgram quad_shader;
+    QOpenGLShaderProgram mesh_shader;
+    QOpenGLShaderProgram mesh_wireframe_shader;
+       QOpenGLShaderProgram quad_shader;
 
     GLMesh* mesh;
     Backdrop* backdrop;
@@ -61,7 +61,8 @@ private:
     float yaw;
 
     float perspective;
-    Q_PROPERTY(float perspective WRITE set_perspective);
+    int drawMode;
+    Q_PROPERTY(float perspective MEMBER perspective WRITE set_perspective);
     QPropertyAnimation anim;
 
     QPoint mouse_pos;
index 7053809e178f652c6500bb665e3b747853da96bb..863f558079ca3829f29014708eb0799af4cd448b 100644 (file)
@@ -2,15 +2,15 @@
 #include "mesh.h"
 
 GLMesh::GLMesh(const Mesh* const mesh)
-    : vertices(QGLBuffer::VertexBuffer), indices(QGLBuffer::IndexBuffer)
+    : vertices(QOpenGLBuffer::VertexBuffer), indices(QOpenGLBuffer::IndexBuffer)
 {
-    initializeGLFunctions();
+    initializeOpenGLFunctions();
 
     vertices.create();
     indices.create();
 
-    vertices.setUsagePattern(QGLBuffer::StaticDraw);
-    indices.setUsagePattern(QGLBuffer::StaticDraw);
+    vertices.setUsagePattern(QOpenGLBuffer::StaticDraw);
+    indices.setUsagePattern(QOpenGLBuffer::StaticDraw);
 
     vertices.bind();
     vertices.allocate(mesh->vertices.data(),
index 74a193f93dce7f5fede53c3dd91ef0885661b6a6..5c47c2ddd921aed3eb05b98e8f22863a702f6a93 100644 (file)
@@ -1,19 +1,20 @@
 #ifndef GLMESH_H
 #define GLMESH_H
 
-#include <QtOpenGL/QGLBuffer>
-#include <QtOpenGL/QGLFunctions>
+#include <QOpenGLBuffer>
+#include <QOpenGLFunctions>
 
+// forward declaration
 class Mesh;
 
-class GLMesh : protected QGLFunctions
+class GLMesh : protected QOpenGLFunctions
 {
 public:
     GLMesh(const Mesh* const mesh);
     void draw(GLuint vp);
 private:
-    QGLBuffer vertices;
-    QGLBuffer indices;
+       QOpenGLBuffer vertices;
+       QOpenGLBuffer indices;
 };
 
 #endif // GLMESH_H
index 93f5525320ce1f7ddc29dd9493b0d3b8a35852ca..b5a2f04b9a43580cfbfda4ede4bd08c40549df22 100644 (file)
@@ -1,6 +1,7 @@
 #include <future>
 
 #include "loader.h"
+#include "vertex.h"
 
 Loader::Loader(QObject* parent, const QString& filename, bool is_reload)
     : QThread(parent), filename(filename), is_reload(is_reload)
@@ -28,25 +29,7 @@ void Loader::run()
 
 ////////////////////////////////////////////////////////////////////////////////
 
-struct Vec3
-{
-    GLfloat x, y, z;
-    bool operator!=(const Vec3& rhs) const
-    {
-        return x != rhs.x || y != rhs.y || z != rhs.z;
-    }
-    bool operator<(const Vec3& rhs) const
-    {
-        if      (x != rhs.x)    return x < rhs.x;
-        else if (y != rhs.y)    return y < rhs.y;
-        else if (z != rhs.z)    return z < rhs.z;
-        else                    return false;
-    }
-};
-
-typedef std::pair<Vec3, GLuint> Vec3i;
-
-void parallel_sort(Vec3i* begin, Vec3i* end, int threads)
+void parallel_sort(Vertex* begin, Vertex* end, int threads)
 {
     if (threads < 2 || end - begin < 2)
     {
@@ -72,17 +55,25 @@ void parallel_sort(Vec3i* begin, Vec3i* end, int threads)
     }
 }
 
-Mesh* mesh_from_verts(uint32_t tri_count, QVector<Vec3i>& verts)
+Mesh* mesh_from_verts(uint32_t tri_count, QVector<Vertex>& verts)
 {
     // Save indicies as the second element in the array
     // (so that we can reconstruct triangle order after sorting)
     for (size_t i=0; i < tri_count*3; ++i)
     {
-        verts[i].second = i;
+        verts[i].i = i;
+    }
+
+    // Check how many threads the hardware can safely support. This may return
+    // 0 if the property can't be read so we shoud check for that too.
+    auto threads = std::thread::hardware_concurrency();
+    if (threads == 0)
+    {
+        threads = 8;
     }
 
     // Sort the set of vertices (to deduplicate)
-    parallel_sort(verts.begin(), verts.end(), 8);
+    parallel_sort(verts.begin(), verts.end(), threads);
 
     // This vector will store triangles as sets of 3 indices
     std::vector<GLuint> indices(tri_count*3);
@@ -94,11 +85,11 @@ Mesh* mesh_from_verts(uint32_t tri_count, QVector<Vec3i>& verts)
     size_t vertex_count = 0;
     for (auto v : verts)
     {
-        if (!vertex_count || v.first != verts[vertex_count-1].first)
+        if (!vertex_count || v != verts[vertex_count-1])
         {
             verts[vertex_count++] = v;
         }
-        indices[v.second] = vertex_count - 1;
+        indices[v.i] = vertex_count - 1;
     }
     verts.resize(vertex_count);
 
@@ -106,12 +97,12 @@ Mesh* mesh_from_verts(uint32_t tri_count, QVector<Vec3i>& verts)
     flat_verts.reserve(vertex_count*3);
     for (auto v : verts)
     {
-        flat_verts.push_back(v.first.x);
-        flat_verts.push_back(v.first.y);
-        flat_verts.push_back(v.first.z);
+        flat_verts.push_back(v.x);
+        flat_verts.push_back(v.y);
+        flat_verts.push_back(v.z);
     }
 
-    return new Mesh(flat_verts, indices);
+    return new Mesh(std::move(flat_verts), std::move(indices));
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -126,7 +117,7 @@ Mesh* Loader::load_stl()
     }
 
     // First, try to read the stl as an ASCII file
-    if (file.read(6) == "solid ")
+    if (file.read(5) == "solid")
     {
         file.readLine(); // skip solid name
         const auto line = file.readLine().trimmed();
@@ -167,35 +158,31 @@ Mesh* Loader::read_stl_binary(QFile& file)
     }
 
     // Extract vertices into an array of xyz, unsigned pairs
-    QVector<Vec3i> verts(tri_count*3);
+    QVector<Vertex> verts(tri_count*3);
 
     // Dummy array, because readRawData is faster than skipRawData
-    uint8_t* buffer = (uint8_t*)malloc(tri_count * 50);
-    data.readRawData((char*)buffer, 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.
-    auto b = buffer;
+    auto b = buffer.get() + 3 * sizeof(float);
     for (auto v=verts.begin(); v != verts.end(); v += 3)
     {
-        // Skip face's normal vector
-        b += 3 * sizeof(float);
-
         // Load vertex data from .stl file into vertices
         for (unsigned i=0; i < 3; ++i)
         {
-            memcpy(&v[i].first, b, 3*sizeof(float));
+            memcpy(&v[i], b, 3*sizeof(float));
             b += 3 * sizeof(float);
         }
 
-        // Skip face attribute
-        b += sizeof(uint16_t);
+        // Skip face attribute and next face's normal vector
+        b += 3 * sizeof(float) + sizeof(uint16_t);
     }
 
     if (confusing_stl)
     {
         emit warning_confusing_stl();
     }
-    free(buffer);
 
     return mesh_from_verts(tri_count, verts);
 }
@@ -204,7 +191,7 @@ Mesh* Loader::read_stl_ascii(QFile& file)
 {
     file.readLine();
     uint32_t tri_count = 0;
-    QVector<Vec3i> verts(tri_count*3);
+    QVector<Vertex> verts(tri_count*3);
 
     bool okay = true;
     while (!file.atEnd() && okay)
@@ -232,7 +219,7 @@ Mesh* Loader::read_stl_ascii(QFile& file)
             const float x = line[1].toFloat(&okay);
             const float y = line[2].toFloat(&okay);
             const float z = line[3].toFloat(&okay);
-            verts.push_back({{x, y, z}, 0});
+            verts.push_back(Vertex(x, y, z));
         }
         if (!file.readLine().trimmed().startsWith("endloop") ||
             !file.readLine().trimmed().startsWith("endfacet"))
index 140a769b5eab739ff35454a098a45e27cac4039d..b971029bdbb4aea31b4e023bfbd6161e07897642 100644 (file)
@@ -8,8 +8,8 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 
-Mesh::Mesh(std::vector<GLfloat> v, std::vector<GLuint> i)
-    : vertices(v), indices(i)
+Mesh::Mesh(std::vector<GLfloat>&& v, std::vector<GLuint>&& i)
+    : vertices(std::move(v)), indices(std::move(i))
 {
     // Nothing to do here
 }
index 141278139b64d3086234e08c66632233a6bca395..c4e888bd8a405e91fdc07e3edbc3359aaf36a58f 100644 (file)
@@ -9,7 +9,7 @@
 class Mesh
 {
 public:
-    Mesh(std::vector<GLfloat> vertices, std::vector<GLuint> indices);
+    Mesh(std::vector<GLfloat>&& vertices, std::vector<GLuint>&& indices);
 
     float min(size_t start) const;
     float max(size_t start) const;
diff --git a/src/vertex.h b/src/vertex.h
new file mode 100644 (file)
index 0000000..9738a75
--- /dev/null
@@ -0,0 +1,30 @@
+#ifndef VEC3_H
+#define VEC3_H
+
+#include <QtOpenGL/QtOpenGL>
+
+/*
+ *  Represents an optionally-indexed vertex in space
+ */
+struct Vertex
+{
+    Vertex() {}
+    Vertex(float x, float y, float z) : x(x), y(y), z(z) {}
+
+    GLfloat x, y, z;
+    GLuint i=0;
+
+    bool operator!=(const Vertex& rhs) const
+    {
+        return x != rhs.x || y != rhs.y || z != rhs.z;
+    }
+    bool operator<(const Vertex& rhs) const
+    {
+        if      (x != rhs.x)    return x < rhs.x;
+        else if (y != rhs.y)    return y < rhs.y;
+        else if (z != rhs.z)    return z < rhs.z;
+        else                    return false;
+    }
+};
+
+#endif
index 0954b33eb92f46b2951f7330c50221dcfc8917fe..09d74eddad444f5fb811d500a570d901b85fb8c4 100644 (file)
@@ -1,6 +1,4 @@
 #include <QMenuBar>
-#include <QMessageBox>
-#include <QFileDialog>
 
 #include "window.h"
 #include "canvas.h"
@@ -15,8 +13,11 @@ Window::Window(QWidget *parent) :
     quit_action(new QAction("Quit", this)),
     perspective_action(new QAction("Perspective", this)),
     orthogonal_action(new QAction("Orthographic", this)),
+    shaded_action(new QAction("Shaded", this)),
+    wireframe_action(new QAction("Wireframe", this)),
     reload_action(new QAction("Reload", this)),
     autoreload_action(new QAction("Autoreload", this)),
+    save_screenshot_action(new QAction("Save Screenshot", this)),
     recent_files(new QMenu("Open recent", this)),
     recent_files_group(new QActionGroup(this)),
     recent_files_clear_action(new QAction("Clear recent files", this)),
@@ -26,10 +27,14 @@ Window::Window(QWidget *parent) :
     setWindowTitle("fstl");
     setAcceptDrops(true);
 
-    QGLFormat format;
+    QSurfaceFormat format;
+    format.setDepthBufferSize(24);
+    format.setStencilBufferSize(8);
     format.setVersion(2, 1);
-    format.setSampleBuffers(true);
+    format.setProfile(QSurfaceFormat::CoreProfile);
 
+    QSurfaceFormat::setDefaultFormat(format);
+    
     canvas = new Canvas(format, this);
     setCentralWidget(canvas);
 
@@ -63,6 +68,10 @@ Window::Window(QWidget *parent) :
     QObject::connect(recent_files_group, &QActionGroup::triggered,
                      this, &Window::on_load_recent);
 
+    save_screenshot_action->setCheckable(false);
+    QObject::connect(save_screenshot_action, &QAction::triggered, 
+        this, &Window::on_save_screenshot);
+    
     rebuild_recent_files();
 
     auto file_menu = menuBar()->addMenu("File");
@@ -71,6 +80,7 @@ Window::Window(QWidget *parent) :
     file_menu->addSeparator();
     file_menu->addAction(reload_action);
     file_menu->addAction(autoreload_action);
+    file_menu->addAction(save_screenshot_action);
     file_menu->addAction(quit_action);
 
     auto view_menu = menuBar()->addMenu("View");
@@ -88,6 +98,20 @@ Window::Window(QWidget *parent) :
     QObject::connect(projections, &QActionGroup::triggered,
                      this, &Window::on_projection);
 
+    auto draw_menu = view_menu->addMenu("Draw Mode");
+    draw_menu->addAction(shaded_action);
+    draw_menu->addAction(wireframe_action);
+    auto drawModes = new QActionGroup(draw_menu);
+    for (auto p : {shaded_action, wireframe_action})
+    {
+        drawModes->addAction(p);
+        p->setCheckable(true);
+    }
+    shaded_action->setChecked(true);
+    drawModes->setExclusive(true);
+    QObject::connect(drawModes, &QActionGroup::triggered,
+                     this, &Window::on_drawMode);
+
     auto help_menu = menuBar()->addMenu("Help");
     help_menu->addAction(about_action);
 
@@ -98,7 +122,7 @@ void Window::on_open()
 {
     QString filename = QFileDialog::getOpenFileName(
                 this, "Load .stl file", QString(), "*.stl");
-    if (not filename.isNull())
+    if (!filename.isNull())
     {
         load_stl(filename);
     }
@@ -190,6 +214,18 @@ void Window::on_projection(QAction* proj)
     }
 }
 
+void Window::on_drawMode(QAction* mode)
+{
+    if (mode == shaded_action)
+    {
+        canvas->draw_shaded();
+    }
+    else
+    {
+        canvas->draw_wireframe();
+    }
+}
+
 void Window::on_watched_change(const QString& filename)
 {
     if (autoreload_action->isChecked())
@@ -218,6 +254,45 @@ void Window::on_load_recent(QAction* a)
     load_stl(a->data().toString());
 }
 
+void Window::on_loaded(const QString& filename)
+{
+    current_file = filename;
+}
+
+void Window::on_save_screenshot()
+{
+    const auto image = canvas->grabFramebuffer();
+    auto file_name = QFileDialog::getSaveFileName(
+        this, 
+        tr("Save Screenshot Image"),
+        QStandardPaths::standardLocations(QStandardPaths::StandardLocation::PicturesLocation).first(),
+        "Images (*.png *.jpg)");
+
+    auto get_file_extension = [](const std::string& file_name) -> std::string
+    {
+        const auto location = std::find(file_name.rbegin(), file_name.rend(), '.');
+        if (location == file_name.rend())
+        {
+            return "";
+        }
+
+        const auto index = std::distance(file_name.rbegin(), location);
+        return file_name.substr(file_name.size() - index);
+    };
+
+    const auto extension = get_file_extension(file_name.toStdString());
+    if(extension.empty() || (extension != "png" && extension != "jpg"))
+    {
+        file_name.append(".png");
+    }
+    
+    const auto save_ok = image.save(file_name);
+    if(!save_ok)
+    {
+        QMessageBox::warning(this, tr("Error Saving Image"), tr("Unable to save screen shot image."));
+    }
+}
+
 void Window::rebuild_recent_files()
 {
     QSettings settings;
@@ -290,6 +365,8 @@ bool Window::load_stl(const QString& filename, bool is_reload)
                   this, &Window::setWindowTitle);
         connect(loader, &Loader::loaded_file,
                   this, &Window::set_watched);
+        connect(loader, &Loader::loaded_file,
+                  this, &Window::on_loaded);
         autoreload_action->setEnabled(true);
         reload_action->setEnabled(true);
     }
@@ -312,3 +389,131 @@ void Window::dropEvent(QDropEvent *event)
 {
     load_stl(event->mimeData()->urls().front().toLocalFile());
 }
+
+void Window::sorted_insert(QStringList& list, const QCollator& collator, const QString& value)
+{
+    int start = 0;
+    int end = list.size() - 1;
+    int index = 0;
+    while (start <= end){
+        int mid = (start+end)/2;
+        if (list[mid] == value) {
+            return;
+        }
+        int compare = collator.compare(value, list[mid]);
+        if (compare < 0) {
+            end = mid-1;
+            index = mid;
+        } else {
+            start = mid+1;
+            index = start;
+        }
+    }
+
+    list.insert(index, value);
+}
+
+void Window::build_folder_file_list()
+{
+    QString current_folder_path = QFileInfo(current_file).absoluteDir().absolutePath();
+    if (!lookup_folder_files.isEmpty())
+    {
+        if (current_folder_path == lookup_folder) {
+            return;
+        }
+
+        lookup_folder_files.clear();
+    }
+    lookup_folder = current_folder_path;
+
+    QCollator collator;
+    collator.setNumericMode(true);
+
+    QDirIterator dirIterator(lookup_folder, QStringList() << "*.stl", QDir::Files | QDir::Readable | QDir::Hidden);
+    while (dirIterator.hasNext()) {
+        dirIterator.next();
+
+        QString name = dirIterator.fileName();
+        sorted_insert(lookup_folder_files, collator, name);
+    }
+}
+
+QPair<QString, QString> Window::get_file_neighbors()
+{
+    if (current_file.isEmpty()) {
+        return QPair<QString, QString>(QString::null, QString::null);
+    }
+
+    build_folder_file_list();
+
+    QFileInfo fileInfo(current_file);
+
+    QString current_dir = fileInfo.absoluteDir().absolutePath();
+    QString current_name = fileInfo.fileName();
+
+    QString prev = QString::null;
+    QString next = QString::null;
+
+    QListIterator<QString> fileIterator(lookup_folder_files);
+    while (fileIterator.hasNext()) {
+        QString name = fileIterator.next();
+
+        if (name == current_name) {
+            if (fileIterator.hasNext()) {
+                next = current_dir + QDir::separator() + fileIterator.next();
+            }
+            break;
+        }
+
+        prev = name;
+    }
+
+    if (!prev.isEmpty()) {
+        prev.prepend(QDir::separator());
+        prev.prepend(current_dir);
+    }
+
+    return QPair<QString, QString>(prev, next);
+}
+
+bool Window::load_prev(void)
+{
+    QPair<QString, QString> neighbors = get_file_neighbors();
+    if (neighbors.first.isEmpty()) {
+        return false;
+    }
+
+    return load_stl(neighbors.first);
+}
+
+bool Window::load_next(void)
+{
+    QPair<QString, QString> neighbors = get_file_neighbors();
+    if (neighbors.second.isEmpty()) {
+        return false;
+    }
+
+    return load_stl(neighbors.second);
+}
+
+void Window::keyPressEvent(QKeyEvent* event)
+{
+    if (!open_action->isEnabled())
+    {
+        QMainWindow::keyPressEvent(event);
+        return;
+    }
+
+    if (event->key() == Qt::Key_Left)
+    {
+        load_prev();
+        return;
+    }
+    else if (event->key() == Qt::Key_Right)
+    {
+        load_next();
+        return;
+    }
+
+    QMainWindow::keyPressEvent(event);
+}
index fd0a77001e4eac827951c8fa9344c6c268d2a10e..8619b4d96d323a1deaeda786636279c602b04884 100644 (file)
@@ -4,6 +4,7 @@
 #include <QMainWindow>
 #include <QActionGroup>
 #include <QFileSystemWatcher>
+#include <QCollator>
 
 class Canvas;
 
@@ -13,10 +14,13 @@ class Window : public QMainWindow
 public:
     explicit Window(QWidget* parent=0);
     bool load_stl(const QString& filename, bool is_reload=false);
+    bool load_prev(void);
+    bool load_next(void);
 
 protected:
-    void dragEnterEvent(QDragEnterEvent* event);
-    void dropEvent(QDropEvent* event);
+    void dragEnterEvent(QDragEnterEvent* event) override;
+    void dropEvent(QDropEvent* event) override;
+    void keyPressEvent(QKeyEvent* event) override;
 
 public slots:
     void on_open();
@@ -33,28 +37,40 @@ public slots:
 
 private slots:
     void on_projection(QAction* proj);
+    void on_drawMode(QAction* mode);
     void on_watched_change(const QString& filename);
     void on_reload();
     void on_autoreload_triggered(bool r);
     void on_clear_recent();
     void on_load_recent(QAction* a);
-
+    void on_loaded(const QString& filename);
+    void on_save_screenshot();
+       
 private:
     void rebuild_recent_files();
+    void sorted_insert(QStringList& list, const QCollator& collator, const QString& value);
+    void build_folder_file_list();
+    QPair<QString, QString> get_file_neighbors();
 
     QAction* const open_action;
     QAction* const about_action;
     QAction* const quit_action;
     QAction* const perspective_action;
     QAction* const orthogonal_action;
+    QAction* const shaded_action;
+    QAction* const wireframe_action;
     QAction* const reload_action;
     QAction* const autoreload_action;
+    QAction* const save_screenshot_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;
+    QString current_file;
+    QString lookup_folder;
+    QStringList lookup_folder_files;
 
     QFileSystemWatcher* watcher;