From 0da9fdf2b9623665a991990e57485a007645eba6 Mon Sep 17 00:00:00 2001 From: Jakob Haufe Date: Mon, 4 Jan 2021 21:19:16 +0000 Subject: [PATCH] New upstream version 0.9.4 --- CMakeLists.txt | 147 ++++++++++++++++++++++++++++ README.md | 23 ++++- gl/gl.qrc | 1 + gl/mesh_wireframe.frag | 9 ++ src/app.cpp | 11 ++- src/app.h | 3 +- src/backdrop.cpp | 6 +- src/backdrop.h | 12 +-- src/canvas.cpp | 87 ++++++++++++----- src/canvas.h | 45 ++++----- src/glmesh.cpp | 8 +- src/glmesh.h | 11 ++- src/loader.cpp | 71 ++++++-------- src/mesh.cpp | 4 +- src/mesh.h | 2 +- src/vertex.h | 30 ++++++ src/window.cpp | 215 ++++++++++++++++++++++++++++++++++++++++- src/window.h | 22 ++++- 18 files changed, 584 insertions(+), 123 deletions(-) create mode 100644 CMakeLists.txt create mode 100644 gl/mesh_wireframe.frag create mode 100644 src/vertex.h diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..66cc168 --- /dev/null +++ b/CMakeLists.txt @@ -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 + $ + $ + $ + $ + $ + $ + 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) +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) diff --git a/README.md b/README.md index b4ab04b..a8a3d18 100644 --- 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 diff --git a/gl/gl.qrc b/gl/gl.qrc index ef93a0e..776226c 100644 --- a/gl/gl.qrc +++ b/gl/gl.qrc @@ -2,6 +2,7 @@ mesh.frag mesh.vert + mesh_wireframe.frag quad.frag quad.vert sphere.stl diff --git a/gl/mesh_wireframe.frag b/gl/mesh_wireframe.frag new file mode 100644 index 0000000..13f001c --- /dev/null +++ b/gl/mesh_wireframe.frag @@ -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); +} diff --git a/src/app.cpp b/src/app.cpp index d182048..982d0d0 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -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) diff --git a/src/app.h b/src/app.h index 2e06c24..6afb4d7 100644 --- 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; diff --git a/src/backdrop.cpp b/src/backdrop.cpp index 13d0aa3..4ef1bba 100644 --- a/src/backdrop.cpp +++ b/src/backdrop.cpp @@ -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[] = { diff --git a/src/backdrop.h b/src/backdrop.h index a2398c8..95ae48b 100644 --- a/src/backdrop.h +++ b/src/backdrop.h @@ -1,18 +1,18 @@ #ifndef BACKDROP_H #define BACKDROP_H -#include -#include -#include +#include +#include +#include -class Backdrop : protected QGLFunctions +class Backdrop : protected QOpenGLFunctions { public: Backdrop(); void draw(); private: - QGLShaderProgram shader; - QGLBuffer vertices; + QOpenGLShaderProgram shader; + QOpenGLBuffer vertices; }; #endif // BACKDROP_H diff --git a/src/canvas.cpp b/src/canvas.cpp index b42a2be..dd188ff 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -1,5 +1,4 @@ #include -#include #include @@ -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 diff --git a/src/canvas.h b/src/canvas.h index 4dae298..7fa80c8 100644 --- a/src/canvas.h +++ b/src/canvas.h @@ -1,55 +1,55 @@ #ifndef CANVAS_H #define CANVAS_H -#include -#include -#include -#include -#include -#include +#include +#include +#include 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; diff --git a/src/glmesh.cpp b/src/glmesh.cpp index 7053809..863f558 100644 --- a/src/glmesh.cpp +++ b/src/glmesh.cpp @@ -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(), diff --git a/src/glmesh.h b/src/glmesh.h index 74a193f..5c47c2d 100644 --- a/src/glmesh.h +++ b/src/glmesh.h @@ -1,19 +1,20 @@ #ifndef GLMESH_H #define GLMESH_H -#include -#include +#include +#include +// 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 diff --git a/src/loader.cpp b/src/loader.cpp index 93f5525..b5a2f04 100644 --- a/src/loader.cpp +++ b/src/loader.cpp @@ -1,6 +1,7 @@ #include #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 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& verts) +Mesh* mesh_from_verts(uint32_t tri_count, QVector& 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 indices(tri_count*3); @@ -94,11 +85,11 @@ Mesh* mesh_from_verts(uint32_t tri_count, QVector& 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& 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 verts(tri_count*3); + QVector 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 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 verts(tri_count*3); + QVector 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")) diff --git a/src/mesh.cpp b/src/mesh.cpp index 140a769..b971029 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -8,8 +8,8 @@ //////////////////////////////////////////////////////////////////////////////// -Mesh::Mesh(std::vector v, std::vector i) - : vertices(v), indices(i) +Mesh::Mesh(std::vector&& v, std::vector&& i) + : vertices(std::move(v)), indices(std::move(i)) { // Nothing to do here } diff --git a/src/mesh.h b/src/mesh.h index 1412781..c4e888b 100644 --- a/src/mesh.h +++ b/src/mesh.h @@ -9,7 +9,7 @@ class Mesh { public: - Mesh(std::vector vertices, std::vector indices); + Mesh(std::vector&& vertices, std::vector&& 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 index 0000000..9738a75 --- /dev/null +++ b/src/vertex.h @@ -0,0 +1,30 @@ +#ifndef VEC3_H +#define VEC3_H + +#include + +/* + * 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 diff --git a/src/window.cpp b/src/window.cpp index 0954b33..09d74ed 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -1,6 +1,4 @@ #include -#include -#include #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 Window::get_file_neighbors() +{ + if (current_file.isEmpty()) { + return QPair(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 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(prev, next); +} + +bool Window::load_prev(void) +{ + QPair neighbors = get_file_neighbors(); + if (neighbors.first.isEmpty()) { + return false; + } + + return load_stl(neighbors.first); +} + +bool Window::load_next(void) +{ + QPair 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); +} diff --git a/src/window.h b/src/window.h index fd0a770..8619b4d 100644 --- a/src/window.h +++ b/src/window.h @@ -4,6 +4,7 @@ #include #include #include +#include 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 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; -- 2.39.2