From 51e25316e8dbb9383f8d296a649abdaf4bc249ce Mon Sep 17 00:00:00 2001 From: Matt Keeter Date: Sat, 22 Mar 2014 12:54:06 -0400 Subject: [PATCH 01/16] Adding hash-based mesh loading (better big-O) --- src/loader.cpp | 8 ++++++++ src/mesh.cpp | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/mesh.h | 1 + 3 files changed, 62 insertions(+) diff --git a/src/loader.cpp b/src/loader.cpp index 4131d02..f0f4268 100644 --- a/src/loader.cpp +++ b/src/loader.cpp @@ -8,6 +8,14 @@ Loader::Loader(QObject* parent, const QString& filename) void Loader::run() { + QTime timer; + timer.start(); emit got_mesh(Mesh::load_stl(filename)); + qDebug() << "Sorted:" << timer.elapsed(); + + timer.start(); + emit got_mesh(Mesh::load_stl_hash(filename)); + qDebug() << "Hash:" << timer.elapsed(); + emit loaded_file(filename); } diff --git a/src/mesh.cpp b/src/mesh.cpp index 36ad10e..6520741 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -56,6 +56,59 @@ typedef std::pair Vec3i; //////////////////////////////////////////////////////////////////////////////// +Mesh* Mesh::load_stl_hash(const QString& filename) +{ + QFile file(filename); + file.open(QIODevice::ReadOnly); + + QDataStream data(&file); + data.setByteOrder(QDataStream::LittleEndian); + data.setFloatingPointPrecision(QDataStream::SinglePrecision); + + // Skip .stl file header + data.skipRawData(80); + + // Load the triangle count from the .stl file + uint32_t tri_count; + data >> tri_count; + + // This vector will store triangles as sets of 3 indices + std::vector indices(tri_count * 3); + + std::vector verts; + verts.reserve(tri_count * 9); + + QHash map; + map.reserve(tri_count * 3); + + float xyz[3]; + QByteArray v(sizeof(xyz), 0); + for (unsigned i=0; i < tri_count; ++i) + { + // Skip face's normal vector + data.skipRawData(3*sizeof(float)); + + for (int j=0; j < 3; ++j) + { + data >> xyz[0] >> xyz[1] >> xyz[2]; + memcpy(v.data(), xyz, sizeof(xyz)); + if (!map.contains(v)) + { + map[v] = verts.size() / 3; + verts.push_back(xyz[0]); + verts.push_back(xyz[1]); + verts.push_back(xyz[2]); + } + indices[i*3 + j] = map[v]; + } + + // Skip face attribute + data.skipRawData(sizeof(uint16_t)); + } + + return new Mesh(verts, indices); +} + Mesh* Mesh::load_stl(const QString& filename) { QFile file(filename); diff --git a/src/mesh.h b/src/mesh.h index f83a477..80f2a1e 100644 --- a/src/mesh.h +++ b/src/mesh.h @@ -11,6 +11,7 @@ class Mesh public: Mesh(std::vector vertices, std::vector indices); static Mesh* load_stl(const QString& filename); + static Mesh* load_stl_hash(const QString& filename); float min(size_t start) const; float max(size_t start) const; From 44f171a823fb8afbc9699c58ef5297ebb1dd9430 Mon Sep 17 00:00:00 2001 From: Matt Keeter Date: Sat, 22 Mar 2014 12:56:37 -0400 Subject: [PATCH 02/16] readRawData is much faster than skipRawData --- src/mesh.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/mesh.cpp b/src/mesh.cpp index 6520741..be36d72 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -86,7 +86,7 @@ Mesh* Mesh::load_stl_hash(const QString& filename) for (unsigned i=0; i < tri_count; ++i) { // Skip face's normal vector - data.skipRawData(3*sizeof(float)); + data.readRawData(reinterpret_cast(xyz), 3*sizeof(float)); for (int j=0; j < 3; ++j) { @@ -103,7 +103,7 @@ Mesh* Mesh::load_stl_hash(const QString& filename) } // Skip face attribute - data.skipRawData(sizeof(uint16_t)); + data.readRawData(reinterpret_cast(xyz), sizeof(uint16_t)); } return new Mesh(verts, indices); @@ -127,12 +127,12 @@ Mesh* Mesh::load_stl(const QString& filename) // Extract vertices into an array of xyz, unsigned pairs QVector verts(tri_count*3); - + float xyz[3]; // Store vertices in the array, processing one triangle at a time. for (auto v=verts.begin(); v != verts.end(); v += 3) { // Skip face's normal vector - data.skipRawData(3*sizeof(float)); + data.readRawData(reinterpret_cast(xyz), 3*sizeof(float)); // Load vertex data from .stl file into vertices data >> v[0].first.x >> v[0].first.y >> v[0].first.z; @@ -140,7 +140,7 @@ Mesh* Mesh::load_stl(const QString& filename) data >> v[2].first.x >> v[2].first.y >> v[2].first.z; // Skip face attribute - data.skipRawData(sizeof(uint16_t)); + data.readRawData(reinterpret_cast(xyz), sizeof(uint16_t)); } // Save indicies as the second element in the array From b6368a26636027836b82f3950dc02de49260decc Mon Sep 17 00:00:00 2001 From: Matt Keeter Date: Sat, 22 Mar 2014 13:03:54 -0400 Subject: [PATCH 03/16] Hash is slower; back to sorted list --- src/loader.cpp | 6 +---- src/mesh.cpp | 64 ++++++-------------------------------------------- src/mesh.h | 1 - 3 files changed, 8 insertions(+), 63 deletions(-) diff --git a/src/loader.cpp b/src/loader.cpp index f0f4268..107d90f 100644 --- a/src/loader.cpp +++ b/src/loader.cpp @@ -11,11 +11,7 @@ void Loader::run() QTime timer; timer.start(); emit got_mesh(Mesh::load_stl(filename)); - qDebug() << "Sorted:" << timer.elapsed(); - - timer.start(); - emit got_mesh(Mesh::load_stl_hash(filename)); - qDebug() << "Hash:" << timer.elapsed(); + qDebug() << "Time taken:" << timer.elapsed(); emit loaded_file(filename); } diff --git a/src/mesh.cpp b/src/mesh.cpp index be36d72..3e092d6 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -56,59 +56,6 @@ typedef std::pair Vec3i; //////////////////////////////////////////////////////////////////////////////// -Mesh* Mesh::load_stl_hash(const QString& filename) -{ - QFile file(filename); - file.open(QIODevice::ReadOnly); - - QDataStream data(&file); - data.setByteOrder(QDataStream::LittleEndian); - data.setFloatingPointPrecision(QDataStream::SinglePrecision); - - // Skip .stl file header - data.skipRawData(80); - - // Load the triangle count from the .stl file - uint32_t tri_count; - data >> tri_count; - - // This vector will store triangles as sets of 3 indices - std::vector indices(tri_count * 3); - - std::vector verts; - verts.reserve(tri_count * 9); - - QHash map; - map.reserve(tri_count * 3); - - float xyz[3]; - QByteArray v(sizeof(xyz), 0); - for (unsigned i=0; i < tri_count; ++i) - { - // Skip face's normal vector - data.readRawData(reinterpret_cast(xyz), 3*sizeof(float)); - - for (int j=0; j < 3; ++j) - { - data >> xyz[0] >> xyz[1] >> xyz[2]; - memcpy(v.data(), xyz, sizeof(xyz)); - if (!map.contains(v)) - { - map[v] = verts.size() / 3; - verts.push_back(xyz[0]); - verts.push_back(xyz[1]); - verts.push_back(xyz[2]); - } - indices[i*3 + j] = map[v]; - } - - // Skip face attribute - data.readRawData(reinterpret_cast(xyz), sizeof(uint16_t)); - } - - return new Mesh(verts, indices); -} - Mesh* Mesh::load_stl(const QString& filename) { QFile file(filename); @@ -127,12 +74,15 @@ Mesh* Mesh::load_stl(const QString& filename) // Extract vertices into an array of xyz, unsigned pairs QVector verts(tri_count*3); - float xyz[3]; + + // Dummy array, because readRawData is faster than skipRawData + char buffer[sizeof(float)*3]; + // Store vertices in the array, processing one triangle at a time. for (auto v=verts.begin(); v != verts.end(); v += 3) { // Skip face's normal vector - data.readRawData(reinterpret_cast(xyz), 3*sizeof(float)); + data.readRawData(buffer, 3*sizeof(float)); // Load vertex data from .stl file into vertices data >> v[0].first.x >> v[0].first.y >> v[0].first.z; @@ -140,7 +90,7 @@ Mesh* Mesh::load_stl(const QString& filename) data >> v[2].first.x >> v[2].first.y >> v[2].first.z; // Skip face attribute - data.readRawData(reinterpret_cast(xyz), sizeof(uint16_t)); + data.readRawData(buffer, sizeof(uint16_t)); } // Save indicies as the second element in the array @@ -171,7 +121,7 @@ Mesh* Mesh::load_stl(const QString& filename) } verts.resize(vertex_count); - std::vector flat_verts; + std::vector flat_verts; flat_verts.reserve(vertex_count*3); for (auto v : verts) { diff --git a/src/mesh.h b/src/mesh.h index 80f2a1e..f83a477 100644 --- a/src/mesh.h +++ b/src/mesh.h @@ -11,7 +11,6 @@ class Mesh public: Mesh(std::vector vertices, std::vector indices); static Mesh* load_stl(const QString& filename); - static Mesh* load_stl_hash(const QString& filename); float min(size_t start) const; float max(size_t start) const; From e36ff6ae9c3002ff915fb0dfcb8b3462430cc67b Mon Sep 17 00:00:00 2001 From: Matt Keeter Date: Mon, 24 Mar 2014 19:11:17 -0400 Subject: [PATCH 04/16] Adding zoom and pan --- src/canvas.cpp | 40 ++++++++++++++++++++++++++++++++++------ src/canvas.h | 2 ++ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/canvas.cpp b/src/canvas.cpp index 03f9376..1539469 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -10,7 +10,7 @@ Canvas::Canvas(const QGLFormat& format, QWidget *parent) : QGLWidget(format, parent), mesh(NULL), - scale(1), tilt(90), yaw(0), status(" ") + scale(1), zoom(1), tilt(90), yaw(0), status(" ") { // Nothing to do here } @@ -122,12 +122,14 @@ QMatrix4x4 Canvas::view_matrix() const { m.scale(-1, width() / float(height()), 0.5); } + m.scale(zoom, zoom, 1); return m; } void Canvas::mousePressEvent(QMouseEvent* event) { - if (event->button() == Qt::LeftButton) + if (event->button() == Qt::LeftButton || + event->button() == Qt::RightButton) { mouse_pos = event->pos(); setCursor(Qt::ClosedHandCursor); @@ -136,7 +138,8 @@ void Canvas::mousePressEvent(QMouseEvent* event) void Canvas::mouseReleaseEvent(QMouseEvent* event) { - if (event->button() == Qt::LeftButton) + if (event->button() == Qt::LeftButton || + event->button() == Qt::RightButton) { unsetCursor(); } @@ -144,13 +147,38 @@ void Canvas::mouseReleaseEvent(QMouseEvent* event) void Canvas::mouseMoveEvent(QMouseEvent* event) { + auto p = event->pos(); + auto d = p - mouse_pos; + if (event->buttons() & Qt::LeftButton) { - auto p = event->pos(); - auto d = p - mouse_pos; yaw = fmod(yaw - d.x(), 360); tilt = fmax(0, fmin(180, tilt - d.y())); - mouse_pos = p; update(); } + else if (event->buttons() & Qt::RightButton) + { + qDebug() << d; + center = transform_matrix().inverted() * + view_matrix().inverted() * + QVector3D(-d.x() / (0.5*width()), + d.y() / (0.5*height()), 0); + update(); + } + mouse_pos = p; +} + +void Canvas::wheelEvent(QWheelEvent *event) +{ + if (event->delta() < 0) + { + for (int i=0; i > event->delta(); --i) + zoom *= 1.001; + } + else if (event->delta() > 0) + { + for (int i=0; i < event->delta(); ++i) + zoom /= 1.001; + } + update(); } diff --git a/src/canvas.h b/src/canvas.h index 64a8dd5..33313cf 100644 --- a/src/canvas.h +++ b/src/canvas.h @@ -32,6 +32,7 @@ protected: void mousePressEvent(QMouseEvent* event); void mouseReleaseEvent(QMouseEvent* event); void mouseMoveEvent(QMouseEvent* event); + void wheelEvent(QWheelEvent* event); private: @@ -48,6 +49,7 @@ private: QVector3D center; float scale; + float zoom; float tilt; float yaw; From 9283aa4752f20951dbb82975d209ee147eea0ec1 Mon Sep 17 00:00:00 2001 From: Matt Keeter Date: Mon, 24 Mar 2014 19:14:17 -0400 Subject: [PATCH 05/16] Compensate for z-flattening when zooming --- gl/mesh.frag | 6 ++++++ src/canvas.cpp | 3 +++ 2 files changed, 9 insertions(+) diff --git a/gl/mesh.frag b/gl/mesh.frag index 5949025..d7a54d5 100644 --- a/gl/mesh.frag +++ b/gl/mesh.frag @@ -1,12 +1,18 @@ #version 120 +uniform float zoom; + varying vec3 ec_pos; void main() { vec3 base3 = vec3(0.99, 0.96, 0.89); vec3 base2 = vec3(0.92, 0.91, 0.83); vec3 base00 = vec3(0.40, 0.48, 0.51); + vec3 ec_normal = normalize(cross(dFdx(ec_pos), dFdy(ec_pos))); + ec_normal.z *= zoom; + ec_normal = normalize(ec_normal); + float a = dot(ec_normal, vec3(0.0, 0.0, 1.0)); float b = dot(ec_normal, vec3(-0.57, -0.57, 0.57)); diff --git a/src/canvas.cpp b/src/canvas.cpp index 1539469..1a1e060 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -89,6 +89,9 @@ void Canvas::draw_mesh() mesh_shader.uniformLocation("view_matrix"), 1, GL_FALSE, view_matrix().data()); + // Compensate for z-flattening when zooming + glUniform1f(mesh_shader.uniformLocation("zoom"), 1/zoom); + // Find and enable the attribute location for vertex position const GLuint vp = mesh_shader.attributeLocation("vertex_position"); glEnableVertexAttribArray(vp); From 54206d3f9caa44f91c51547cd1edbfc4091cc908 Mon Sep 17 00:00:00 2001 From: Matt Keeter Date: Mon, 24 Mar 2014 19:32:12 -0400 Subject: [PATCH 06/16] Zoom about mouse cursor --- src/canvas.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/canvas.cpp b/src/canvas.cpp index 1a1e060..15624da 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -161,7 +161,6 @@ void Canvas::mouseMoveEvent(QMouseEvent* event) } else if (event->buttons() & Qt::RightButton) { - qDebug() << d; center = transform_matrix().inverted() * view_matrix().inverted() * QVector3D(-d.x() / (0.5*width()), @@ -173,6 +172,14 @@ void Canvas::mouseMoveEvent(QMouseEvent* event) void Canvas::wheelEvent(QWheelEvent *event) { + // Find GL position before the zoom operation + // (to zoom about mouse cursor) + auto p = event->pos(); + 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) { for (int i=0; i > event->delta(); --i) @@ -183,5 +190,10 @@ void Canvas::wheelEvent(QWheelEvent *event) for (int i=0; i < event->delta(); ++i) zoom /= 1.001; } + + // Then find the cursor's GL position post-zoom and adjust center. + QVector3D b = transform_matrix().inverted() * + view_matrix().inverted() * v; + center += b - a; update(); } From 25a7138328a2b54bc1476621bbc97ef37a65d858 Mon Sep 17 00:00:00 2001 From: Matt Keeter Date: Mon, 24 Mar 2014 19:49:11 -0400 Subject: [PATCH 07/16] Reset camera parameters after loading model --- src/canvas.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/canvas.cpp b/src/canvas.cpp index 15624da..dd645a5 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -30,6 +30,12 @@ void Canvas::load_mesh(Mesh* m) pow(m->xmax() - m->xmin(), 2) + pow(m->ymax() - m->ymin(), 2) + pow(m->zmax() - m->zmin(), 2)); + + // Reset other camera parameters + zoom = 1; + yaw = 0; + tilt = 90; + update(); delete m; From 236a2033208b7c207e54be6199dde48434982abb Mon Sep 17 00:00:00 2001 From: Matt Keeter Date: Mon, 24 Mar 2014 21:00:50 -0400 Subject: [PATCH 08/16] Add similar script for windows --- exe/package.sh | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 exe/package.sh diff --git a/exe/package.sh b/exe/package.sh new file mode 100644 index 0000000..5faecf1 --- /dev/null +++ b/exe/package.sh @@ -0,0 +1,3 @@ +cd .. +cp build/release/fstl.exe . +/c/Program\ Files/7-Zip/7z.exe a fstl.zip fstl.exe README.md From 428aff52a5780af24436a896fec1b6fcd65eea5e Mon Sep 17 00:00:00 2001 From: Matt Keeter Date: Mon, 24 Mar 2014 20:14:13 -0400 Subject: [PATCH 09/16] Raise an error message box on ascii stl --- src/loader.cpp | 14 ++++++++++---- src/loader.h | 1 + src/window.cpp | 10 ++++++++++ src/window.h | 1 + 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/loader.cpp b/src/loader.cpp index 107d90f..d7ce46a 100644 --- a/src/loader.cpp +++ b/src/loader.cpp @@ -8,10 +8,16 @@ Loader::Loader(QObject* parent, const QString& filename) void Loader::run() { - QTime timer; - timer.start(); - emit got_mesh(Mesh::load_stl(filename)); - qDebug() << "Time taken:" << timer.elapsed(); + { // Verify that this isn't an ascii stl file + QFile file(filename); + file.open(QIODevice::ReadOnly); + if (file.read(5) == "solid") + { + emit error_ascii_stl(); + return; + } + } + emit got_mesh(Mesh::load_stl(filename)); emit loaded_file(filename); } diff --git a/src/loader.h b/src/loader.h index 350985c..85e0c53 100644 --- a/src/loader.h +++ b/src/loader.h @@ -15,6 +15,7 @@ public: signals: void loaded_file(QString filename); void got_mesh(Mesh* m); + void error_ascii_stl(); private: const QString filename; diff --git a/src/window.cpp b/src/window.cpp index 15ffb03..5f8ac9e 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -69,6 +69,14 @@ void Window::on_about() " style=\"color: #93a1a1;\">matt.j.keeter@gmail.com

"); } +void Window::on_ascii_stl() +{ + QMessageBox::critical(this, "Error", + "Error:
" + "Cannot open ASCII .stl file
" + "Please convert to binary .stl and retry"); +} + void Window::enable_open() { open_action->setEnabled(true); @@ -91,6 +99,8 @@ bool Window::load_stl(const QString& filename) connect(loader, &Loader::got_mesh, canvas, &Canvas::load_mesh); + connect(loader, &Loader::error_ascii_stl, + this, &Window::on_ascii_stl); connect(loader, &Loader::finished, loader, &Loader::deleteLater); diff --git a/src/window.h b/src/window.h index 22c35cb..8484822 100644 --- a/src/window.h +++ b/src/window.h @@ -15,6 +15,7 @@ public: public slots: void on_open(); void on_about(); + void on_ascii_stl(); void enable_open(); void disable_open(); From 70f0297d209e04f3de354a3684759eecc6077719 Mon Sep 17 00:00:00 2001 From: Matt Keeter Date: Mon, 24 Mar 2014 20:50:06 -0400 Subject: [PATCH 10/16] Small bash script to run macdeployqt then clean up unused frameworks --- app/package.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100755 app/package.sh diff --git a/app/package.sh b/app/package.sh new file mode 100755 index 0000000..d012bfb --- /dev/null +++ b/app/package.sh @@ -0,0 +1,10 @@ +#!/bin/sh +cd ../build +macdeployqt fstl.app +cd fstl.app/Contents/PlugIns +rm -rf accessible audio imageformats mediaservice playlistformats position printsupport qml1tooling sensorgestures sensors +cd ../Frameworks +rm -rf QtDeclarative.framework QtMultimedia.framework QtMultimediaWidgets.framework QtNetwork.framework QtPositioning.framework QtQml.framework QtQuick.framework QtScript.framework QtSensors.framework QtSql.framework QtXmlPatterns.framework +cd ../Resources +rm empty.lproj + From 030f0c5161fc2da378ff5af8aa20610555f90188 Mon Sep 17 00:00:00 2001 From: Matt Keeter Date: Mon, 24 Mar 2014 20:53:12 -0400 Subject: [PATCH 11/16] and compress the app and the README into a zip file --- app/package.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/package.sh b/app/package.sh index d012bfb..da97313 100755 --- a/app/package.sh +++ b/app/package.sh @@ -7,4 +7,8 @@ cd ../Frameworks rm -rf QtDeclarative.framework QtMultimedia.framework QtMultimediaWidgets.framework QtNetwork.framework QtPositioning.framework QtQml.framework QtQuick.framework QtScript.framework QtSensors.framework QtSql.framework QtXmlPatterns.framework cd ../Resources rm empty.lproj +cd ../../.. +cp -r fstl.app .. +cd .. +zip -r fstl.zip fstl.app README.md From de5c4b440a941e20420262a0b152f435e4cc5bb8 Mon Sep 17 00:00:00 2001 From: Matt Keeter Date: Mon, 24 Mar 2014 21:11:25 -0400 Subject: [PATCH 12/16] Add mac/win suffixes to zip files --- app/package.sh | 2 +- exe/package.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/package.sh b/app/package.sh index da97313..d51c470 100755 --- a/app/package.sh +++ b/app/package.sh @@ -10,5 +10,5 @@ rm empty.lproj cd ../../.. cp -r fstl.app .. cd .. -zip -r fstl.zip fstl.app README.md +zip -r fstl_mac.zip fstl.app README.md diff --git a/exe/package.sh b/exe/package.sh index 5faecf1..ef81b62 100644 --- a/exe/package.sh +++ b/exe/package.sh @@ -1,3 +1,3 @@ cd .. cp build/release/fstl.exe . -/c/Program\ Files/7-Zip/7z.exe a fstl.zip fstl.exe README.md +/c/Program\ Files/7-Zip/7z.exe a fstl_win.zip fstl.exe README.md From 9cc3bd82e832f5d49911048d6dc053e177922abf Mon Sep 17 00:00:00 2001 From: Matt Keeter Date: Tue, 25 Mar 2014 18:53:07 -0400 Subject: [PATCH 13/16] Added check for stl corruption --- src/loader.cpp | 14 ++++++++++++++ src/loader.h | 1 + src/window.cpp | 10 ++++++++++ src/window.h | 1 + 4 files changed, 26 insertions(+) diff --git a/src/loader.cpp b/src/loader.cpp index d7ce46a..355f40c 100644 --- a/src/loader.cpp +++ b/src/loader.cpp @@ -4,6 +4,7 @@ Loader::Loader(QObject* parent, const QString& filename) : QThread(parent), filename(filename) { + // Nothing to do here } void Loader::run() @@ -16,6 +17,19 @@ void Loader::run() emit error_ascii_stl(); return; } + + // Skip the rest of the buffer + file.read(75); + + // Assume we're on a little-endian system for simplicity + uint32_t tri_count; + file.read(reinterpret_cast(&tri_count), sizeof(tri_count)); + + if (file.size() != 84 + tri_count*50) + { + emit error_bad_stl(); + return; + } } emit got_mesh(Mesh::load_stl(filename)); diff --git a/src/loader.h b/src/loader.h index 85e0c53..1de1157 100644 --- a/src/loader.h +++ b/src/loader.h @@ -16,6 +16,7 @@ signals: void loaded_file(QString filename); void got_mesh(Mesh* m); void error_ascii_stl(); + void error_bad_stl(); private: const QString filename; diff --git a/src/window.cpp b/src/window.cpp index 5f8ac9e..2d809bf 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -77,6 +77,14 @@ void Window::on_ascii_stl() "Please convert to binary .stl and retry"); } +void Window::on_bad_stl() +{ + QMessageBox::critical(this, "Error", + "Error:
" + "This .stl file is invalid or corrupted.
" + "Please export it from the original source, verify, and retry."); +} + void Window::enable_open() { open_action->setEnabled(true); @@ -101,6 +109,8 @@ bool Window::load_stl(const QString& filename) canvas, &Canvas::load_mesh); connect(loader, &Loader::error_ascii_stl, this, &Window::on_ascii_stl); + connect(loader, &Loader::error_bad_stl, + this, &Window::on_bad_stl); connect(loader, &Loader::finished, loader, &Loader::deleteLater); diff --git a/src/window.h b/src/window.h index 8484822..3ac5e95 100644 --- a/src/window.h +++ b/src/window.h @@ -16,6 +16,7 @@ public slots: void on_open(); void on_about(); void on_ascii_stl(); + void on_bad_stl(); void enable_open(); void disable_open(); From 61c930444d093b2d1bb43b72a2f34b4a7d748eaf Mon Sep 17 00:00:00 2001 From: Matt Keeter Date: Tue, 25 Mar 2014 20:01:46 -0400 Subject: [PATCH 14/16] Move load_stl from Mesh to Loader --- src/loader.cpp | 134 +++++++++++++++++++++++++++++++++++++++++-------- src/loader.h | 4 ++ src/mesh.cpp | 99 ------------------------------------ src/mesh.h | 1 - 4 files changed, 117 insertions(+), 121 deletions(-) diff --git a/src/loader.cpp b/src/loader.cpp index 355f40c..0d6b165 100644 --- a/src/loader.cpp +++ b/src/loader.cpp @@ -1,5 +1,4 @@ #include "loader.h" -#include "mesh.h" Loader::Loader(QObject* parent, const QString& filename) : QThread(parent), filename(filename) @@ -9,29 +8,122 @@ Loader::Loader(QObject* parent, const QString& filename) void Loader::run() { - { // Verify that this isn't an ascii stl file - QFile file(filename); - file.open(QIODevice::ReadOnly); - if (file.read(5) == "solid") - { - emit error_ascii_stl(); - return; - } + Mesh* mesh = load_stl(); + if (mesh) + { + emit got_mesh(mesh); + emit loaded_file(filename); + } +} - // Skip the rest of the buffer - file.read(75); - // Assume we're on a little-endian system for simplicity - uint32_t tri_count; - file.read(reinterpret_cast(&tri_count), sizeof(tri_count)); +//////////////////////////////////////////////////////////////////////////////// - if (file.size() != 84 + tri_count*50) - { - emit error_bad_stl(); - return; - } +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; + +//////////////////////////////////////////////////////////////////////////////// + +Mesh* Loader::load_stl() +{ + QFile file(filename); + file.open(QIODevice::ReadOnly); + if (file.read(5) == "solid") + { + emit error_ascii_stl(); + return NULL; + } + // Skip the rest of the header material + file.read(75); + + QDataStream data(&file); + data.setByteOrder(QDataStream::LittleEndian); + data.setFloatingPointPrecision(QDataStream::SinglePrecision); + + // Load the triangle count from the .stl file + uint32_t tri_count; + data >> tri_count; + + // Verify that the file is the right size + if (file.size() != 84 + tri_count*50) + { + emit error_bad_stl(); + return NULL; } - emit got_mesh(Mesh::load_stl(filename)); - emit loaded_file(filename); + // Extract vertices into an array of xyz, unsigned pairs + QVector verts(tri_count*3); + + // Dummy array, because readRawData is faster than skipRawData + char buffer[sizeof(float)*3]; + + // Store vertices in the array, processing one triangle at a time. + for (auto v=verts.begin(); v != verts.end(); v += 3) + { + // Skip face's normal vector + data.readRawData(buffer, 3*sizeof(float)); + + // Load vertex data from .stl file into vertices + data >> v[0].first.x >> v[0].first.y >> v[0].first.z; + data >> v[1].first.x >> v[1].first.y >> v[1].first.z; + data >> v[2].first.x >> v[2].first.y >> v[2].first.z; + + // Skip face attribute + data.readRawData(buffer, sizeof(uint16_t)); + } + + // 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; + } + + // Sort the set of vertices (to deduplicate) + std::sort(verts.begin(), verts.end()); + + // This vector will store triangles as sets of 3 indices + std::vector indices(tri_count*3); + + // Go through the sorted vertex list, deduplicating and creating + // an indexed geometry representation for the triangles. + // Unique vertices are moved so that they occupy the first vertex_count + // positions in the verts array. + size_t vertex_count = 0; + for (auto v : verts) + { + if (!vertex_count || v.first != verts[vertex_count-1].first) + { + verts[vertex_count++] = v; + } + indices[v.second] = vertex_count - 1; + } + verts.resize(vertex_count); + + std::vector flat_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); + } + + return new Mesh(flat_verts, indices); } + diff --git a/src/loader.h b/src/loader.h index 1de1157..fb0c8d8 100644 --- a/src/loader.h +++ b/src/loader.h @@ -12,9 +12,13 @@ public: explicit Loader(QObject* parent, const QString& filename); void run(); +protected: + Mesh* load_stl(); + signals: void loaded_file(QString filename); void got_mesh(Mesh* m); + void error_ascii_stl(); void error_bad_stl(); diff --git a/src/mesh.cpp b/src/mesh.cpp index 3e092d6..cfb4ca4 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include "mesh.h" @@ -34,101 +33,3 @@ float Mesh::max(size_t start) const } return v; } -//////////////////////////////////////////////////////////////////////////////// - -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; - -//////////////////////////////////////////////////////////////////////////////// - -Mesh* Mesh::load_stl(const QString& filename) -{ - QFile file(filename); - file.open(QIODevice::ReadOnly); - - QDataStream data(&file); - data.setByteOrder(QDataStream::LittleEndian); - data.setFloatingPointPrecision(QDataStream::SinglePrecision); - - // Skip .stl file header - data.skipRawData(80); - - // Load the triangle count from the .stl file - uint32_t tri_count; - data >> tri_count; - - // Extract vertices into an array of xyz, unsigned pairs - QVector verts(tri_count*3); - - // Dummy array, because readRawData is faster than skipRawData - char buffer[sizeof(float)*3]; - - // Store vertices in the array, processing one triangle at a time. - for (auto v=verts.begin(); v != verts.end(); v += 3) - { - // Skip face's normal vector - data.readRawData(buffer, 3*sizeof(float)); - - // Load vertex data from .stl file into vertices - data >> v[0].first.x >> v[0].first.y >> v[0].first.z; - data >> v[1].first.x >> v[1].first.y >> v[1].first.z; - data >> v[2].first.x >> v[2].first.y >> v[2].first.z; - - // Skip face attribute - data.readRawData(buffer, sizeof(uint16_t)); - } - - // 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; - } - - // Sort the set of vertices (to deduplicate) - std::sort(verts.begin(), verts.end()); - - // This vector will store triangles as sets of 3 indices - std::vector indices(tri_count*3); - - // Go through the sorted vertex list, deduplicating and creating - // an indexed geometry representation for the triangles. - // Unique vertices are moved so that they occupy the first vertex_count - // positions in the verts array. - size_t vertex_count = 0; - for (auto v : verts) - { - if (!vertex_count || v.first != verts[vertex_count-1].first) - { - verts[vertex_count++] = v; - } - indices[v.second] = vertex_count - 1; - } - verts.resize(vertex_count); - - std::vector flat_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); - } - - return new Mesh(flat_verts, indices); -} diff --git a/src/mesh.h b/src/mesh.h index f83a477..e8a02f0 100644 --- a/src/mesh.h +++ b/src/mesh.h @@ -10,7 +10,6 @@ class Mesh { public: Mesh(std::vector vertices, std::vector indices); - static Mesh* load_stl(const QString& filename); float min(size_t start) const; float max(size_t start) const; From d9301bbba097438546344405bd8899b0311cdc85 Mon Sep 17 00:00:00 2001 From: Matt Keeter Date: Wed, 26 Mar 2014 21:40:59 -0400 Subject: [PATCH 15/16] Fix double-click to open .stl on windows --- src/app.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/app.cpp b/src/app.cpp index afa4ea7..b6eb3af 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -8,7 +8,10 @@ App::App(int argc, char *argv[]) : QApplication(argc, argv), window(new Window()) { window->show(); - window->load_stl(":gl/sphere.stl"); + if (argc > 1) + window->load_stl(argv[1]); + else + window->load_stl(":gl/sphere.stl"); } bool App::event(QEvent* e) From 5a38a2db976cf17640808317694c2aaa7091fe35 Mon Sep 17 00:00:00 2001 From: Matt Keeter Date: Wed, 26 Mar 2014 21:51:58 -0400 Subject: [PATCH 16/16] Allow dropping of files onto window --- src/window.cpp | 16 ++++++++++++++++ src/window.h | 4 ++++ 2 files changed, 20 insertions(+) diff --git a/src/window.cpp b/src/window.cpp index 2d809bf..3002ecc 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -14,6 +14,7 @@ Window::Window(QWidget *parent) : { setWindowTitle("fstl"); + setAcceptDrops(true); QFile styleFile(":/qt/style.qss"); styleFile.open( QFile::ReadOnly ); @@ -128,3 +129,18 @@ bool Window::load_stl(const QString& filename) loader->start(); return true; } + +void Window::dragEnterEvent(QDragEnterEvent *event) +{ + if (event->mimeData()->hasUrls()) + { + auto urls = event->mimeData()->urls(); + if (urls.size() == 1 && urls.front().path().endsWith(".stl")) + event->acceptProposedAction(); + } +} + +void Window::dropEvent(QDropEvent *event) +{ + load_stl(event->mimeData()->urls().front().toLocalFile()); +} diff --git a/src/window.h b/src/window.h index 3ac5e95..cb92ebb 100644 --- a/src/window.h +++ b/src/window.h @@ -12,6 +12,10 @@ public: explicit Window(QWidget* parent=0); bool load_stl(const QString& filename); +protected: + void dragEnterEvent(QDragEnterEvent* event); + void dropEvent(QDropEvent* event); + public slots: void on_open(); void on_about();