From: Jakob Haufe Date: Sun, 13 Mar 2016 22:07:22 +0000 (+0100) Subject: Imported Upstream version 0.9.2 X-Git-Tag: upstream/0.9.2^0 X-Git-Url: https://git.sur5r.net/?p=fstl;a=commitdiff_plain;h=967d178c4343c689c728571bb10d5c94ab8d9d13 Imported Upstream version 0.9.2 --- 967d178c4343c689c728571bb10d5c94ab8d9d13 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..deaad71 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build/* +*.pro.user +*.qmake.stash diff --git a/README.md b/README.md new file mode 100644 index 0000000..a9a845f --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +`fstl` is a viewer for [.stl files](http://en.wikipedia.org/wiki/STL_\(file_format\)). + +Here's a screenshot: +![Eiffel tower](http://mattkeeter.com/projects/fstl/eiffel.png) +(credit to [Pranav Panchal](https://grabcad.com/pranav.panchal)) + +It is designed to quickly load and render very high-polygon models; +showing 2 million triangles at 60+ FPS on a mid-range laptop. + +For more details, see the [project page](http://mattkeeter.com/projects/fstl). + +Issues and pull requests are welcome; +the project is under 1K lines of code and should be fairly approachable. + +-------------------------------------------------------------------------------- + +License +------- +(c) 2014 Matt Keeter + +This work may be reproduced, modified, distributed, performed, and displayed for any purpose. Copyright is retained and must be preserved. The work is provided as is; no warranty is provided, and users accept all liability. + +-------------------------------------------------------------------------------- +*p.s.* In my day job, I'm an engineer at [Formlabs](http://formlabs.com). +If you think `fstl` is cool, you should check out our +[jobs page](http://formlabs.com/jobs) -- we're hiring software developers +and engineers of all stripes to work on interesting, hard problems in +consumer-level 3D printing. + diff --git a/app/Info.plist b/app/Info.plist new file mode 100644 index 0000000..ff3047b --- /dev/null +++ b/app/Info.plist @@ -0,0 +1,31 @@ + + + + + NSPrincipalClass + NSApplication + CFBundleIconFile + fstl.icns + CFBundlePackageType + APPL + CFBundleGetInfoString + A minimal fast STL viewer + CFBundleExecutable + fstl + CFBundleIdentifier + com.impraxical.fstl + CFBundleDocumentTypes + + + CFBundleTypeExtensions + + stl + + CFBundleTypeName + Stereolithography file + CFBundleTypeRole + Viewer + + + + diff --git a/app/fstl.icns b/app/fstl.icns new file mode 100644 index 0000000..69c011c Binary files /dev/null and b/app/fstl.icns differ diff --git a/app/package.sh b/app/package.sh new file mode 100755 index 0000000..d51c470 --- /dev/null +++ b/app/package.sh @@ -0,0 +1,14 @@ +#!/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 +cd ../../.. +cp -r fstl.app .. +cd .. +zip -r fstl_mac.zip fstl.app README.md + diff --git a/exe/fstl.ico b/exe/fstl.ico new file mode 100644 index 0000000..2e2d891 Binary files /dev/null and b/exe/fstl.ico differ diff --git a/exe/fstl.rc b/exe/fstl.rc new file mode 100644 index 0000000..3620f29 --- /dev/null +++ b/exe/fstl.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON DISCARDABLE "fstl.ico" diff --git a/exe/package.sh b/exe/package.sh new file mode 100644 index 0000000..ef81b62 --- /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_win.zip fstl.exe README.md diff --git a/gl/gl.qrc b/gl/gl.qrc new file mode 100644 index 0000000..ef93a0e --- /dev/null +++ b/gl/gl.qrc @@ -0,0 +1,9 @@ + + + mesh.frag + mesh.vert + quad.frag + quad.vert + sphere.stl + + diff --git a/gl/mesh.frag b/gl/mesh.frag new file mode 100644 index 0000000..d7a54d5 --- /dev/null +++ b/gl/mesh.frag @@ -0,0 +1,21 @@ +#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)); + + gl_FragColor = vec4((a*base2 + (1-a)*base00)*0.5 + + (b*base3 + (1-b)*base00)*0.5, 1.0); +} diff --git a/gl/mesh.vert b/gl/mesh.vert new file mode 100644 index 0000000..e60e76b --- /dev/null +++ b/gl/mesh.vert @@ -0,0 +1,13 @@ +#version 120 +attribute vec3 vertex_position; + +uniform mat4 transform_matrix; +uniform mat4 view_matrix; + +varying vec3 ec_pos; + +void main() { + gl_Position = view_matrix*transform_matrix* + vec4(vertex_position, 1.0); + ec_pos = gl_Position.xyz; +} diff --git a/gl/quad.frag b/gl/quad.frag new file mode 100644 index 0000000..1c02e1b --- /dev/null +++ b/gl/quad.frag @@ -0,0 +1,7 @@ +#version 120 + +varying vec3 frag_color; + +void main() { + gl_FragColor = vec4(frag_color, 1.0); +} diff --git a/gl/quad.vert b/gl/quad.vert new file mode 100644 index 0000000..6698297 --- /dev/null +++ b/gl/quad.vert @@ -0,0 +1,10 @@ +#version 120 +attribute vec2 vertex_position; +attribute vec3 vertex_color; + +varying vec3 frag_color; + +void main() { + gl_Position = vec4(vertex_position, 0.9, 1.0); + frag_color = vertex_color; +} diff --git a/gl/sphere.stl b/gl/sphere.stl new file mode 100644 index 0000000..b78c7b4 Binary files /dev/null and b/gl/sphere.stl differ diff --git a/qt/fstl.pro b/qt/fstl.pro new file mode 100644 index 0000000..7a0f449 --- /dev/null +++ b/qt/fstl.pro @@ -0,0 +1,46 @@ +QT += core gui opengl widgets + +TARGET = fstl +TEMPLATE = app + +# Bump optimization up to -O3 in release builds +QMAKE_CXXFLAGS_RELEASE -= -O2 +QMAKE_CXXFLAGS_RELEASE += -O3 + +SOURCES += \ + ../src/app.cpp\ + ../src/main.cpp\ + ../src/canvas.cpp \ + ../src/mesh.cpp \ + ../src/glmesh.cpp \ + ../src/loader.cpp \ + ../src/window.cpp \ + ../src/backdrop.cpp + +HEADERS += \ + ../src/app.h\ + ../src/canvas.h \ + ../src/mesh.h \ + ../src/glmesh.h \ + ../src/loader.h \ + ../src/window.h \ + ../src/backdrop.h + +CONFIG += c++11 + +RESOURCES += \ + qt.qrc \ + ../gl/gl.qrc + +macx { + QMAKE_INFO_PLIST = ../app/Info.plist + ICON = ../app/fstl.icns +} + +win32 { + RC_FILE = ../exe/fstl.rc +} + +static { + CONFIG += static +} diff --git a/qt/qt.qrc b/qt/qt.qrc new file mode 100644 index 0000000..883ac14 --- /dev/null +++ b/qt/qt.qrc @@ -0,0 +1,5 @@ + + + style.qss + + diff --git a/qt/style.qss b/qt/style.qss new file mode 100644 index 0000000..0297148 --- /dev/null +++ b/qt/style.qss @@ -0,0 +1,19 @@ +QWidget { + background-color: #fdf6e3; + color: #839496; +} + +QPushButton { + background-color: #eee8d5; + border-top-width: 5px; + border-bottom-width: 5px; + border-left-width: 30px; + border-right-width: 30px; + border-style: flat; + margin: 0px; + color: #839496; +} + +QPushButton:pressed { + background-color: #ddd7c4; +} diff --git a/src/app.cpp b/src/app.cpp new file mode 100644 index 0000000..b6eb3af --- /dev/null +++ b/src/app.cpp @@ -0,0 +1,28 @@ +#include +#include + +#include "app.h" +#include "window.h" + +App::App(int argc, char *argv[]) : + QApplication(argc, argv), window(new Window()) +{ + window->show(); + if (argc > 1) + window->load_stl(argv[1]); + else + window->load_stl(":gl/sphere.stl"); +} + +bool App::event(QEvent* e) +{ + if (e->type() == QEvent::FileOpen) + { + window->load_stl(static_cast(e)->file()); + return true; + } + else + { + return QApplication::event(e); + } +} diff --git a/src/app.h b/src/app.h new file mode 100644 index 0000000..7a7672e --- /dev/null +++ b/src/app.h @@ -0,0 +1,20 @@ +#ifndef APP_H +#define APP_H + +#include + +class Window; + +class App : public QApplication +{ + Q_OBJECT +public: + explicit App(int argc, char *argv[]); +protected: + bool event(QEvent* e); +private: + Window* const window; + +}; + +#endif // APP_H diff --git a/src/backdrop.cpp b/src/backdrop.cpp new file mode 100644 index 0000000..13d0aa3 --- /dev/null +++ b/src/backdrop.cpp @@ -0,0 +1,44 @@ +#include "backdrop.h" + +Backdrop::Backdrop() +{ + initializeGLFunctions(); + + shader.addShaderFromSourceFile(QGLShader::Vertex, ":/gl/quad.vert"); + shader.addShaderFromSourceFile(QGLShader::Fragment, ":/gl/quad.frag"); + shader.link(); + + float vbuf[] = { + -1, -1, 0.00, 0.10, 0.15, + -1, 1, 0.03, 0.21, 0.26, + 1, -1, 0.00, 0.12, 0.18, + 1, 1, 0.06, 0.26, 0.30}; + + vertices.create(); + vertices.bind(); + vertices.allocate(vbuf, sizeof(vbuf)); + vertices.release(); +} + +void Backdrop::draw() +{ + shader.bind(); + vertices.bind(); + + const GLuint vp = shader.attributeLocation("vertex_position"); + const GLuint vc = shader.attributeLocation("vertex_color"); + + glEnableVertexAttribArray(vp); + glEnableVertexAttribArray(vc); + + glVertexAttribPointer(vp, 2, GL_FLOAT, false, + 5 * sizeof(GLfloat), 0); + glVertexAttribPointer(vc, 3, GL_FLOAT, false, + 5 * sizeof(GLfloat), + (GLvoid*)(2 * sizeof(GLfloat))); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 8); + + vertices.release(); + shader.release(); +} diff --git a/src/backdrop.h b/src/backdrop.h new file mode 100644 index 0000000..a2398c8 --- /dev/null +++ b/src/backdrop.h @@ -0,0 +1,18 @@ +#ifndef BACKDROP_H +#define BACKDROP_H + +#include +#include +#include + +class Backdrop : protected QGLFunctions +{ +public: + Backdrop(); + void draw(); +private: + QGLShaderProgram shader; + QGLBuffer vertices; +}; + +#endif // BACKDROP_H diff --git a/src/canvas.cpp b/src/canvas.cpp new file mode 100644 index 0000000..dd645a5 --- /dev/null +++ b/src/canvas.cpp @@ -0,0 +1,205 @@ +#include +#include + +#include + +#include "canvas.h" +#include "backdrop.h" +#include "glmesh.h" +#include "mesh.h" + +Canvas::Canvas(const QGLFormat& format, QWidget *parent) + : QGLWidget(format, parent), mesh(NULL), + scale(1), zoom(1), tilt(90), yaw(0), status(" ") +{ + // Nothing to do here +} + +Canvas::~Canvas() +{ + delete mesh; +} + +void Canvas::load_mesh(Mesh* m) +{ + mesh = new GLMesh(m); + center = QVector3D(m->xmin() + m->xmax(), + m->ymin() + m->ymax(), + m->zmin() + m->zmax()) / 2; + scale = 2 / sqrt( + 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; +} + +void Canvas::set_status(const QString &s) +{ + status = s; + update(); +} + +void Canvas::clear_status() +{ + status = ""; + update(); +} + +void Canvas::initializeGL() +{ + initializeGLFunctions(); + + mesh_shader.addShaderFromSourceFile(QGLShader::Vertex, ":/gl/mesh.vert"); + mesh_shader.addShaderFromSourceFile(QGLShader::Fragment, ":/gl/mesh.frag"); + mesh_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); + + backdrop->draw(); + if (mesh) draw_mesh(); + + if (status.isNull()) return; + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + painter.drawText(10, height() - 10, status); +} + + +void Canvas::draw_mesh() +{ + mesh_shader.bind(); + + // Load the transform and view matrices into the shader + glUniformMatrix4fv( + mesh_shader.uniformLocation("transform_matrix"), + 1, GL_FALSE, transform_matrix().data()); + glUniformMatrix4fv( + 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); + + // Then draw the mesh with that vertex position + mesh->draw(vp); + + // Clean up state machine + glDisableVertexAttribArray(vp); + mesh_shader.release(); +} + +QMatrix4x4 Canvas::transform_matrix() const +{ + QMatrix4x4 m; + m.rotate(tilt, QVector3D(1, 0, 0)); + m.rotate(yaw, QVector3D(0, 0, 1)); + m.scale(scale); + m.translate(-center); + return m; +} + +QMatrix4x4 Canvas::view_matrix() const +{ + QMatrix4x4 m; + if (width() > height()) + { + m.scale(-height() / float(width()), 1, 0.5); + } + else + { + 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 || + event->button() == Qt::RightButton) + { + mouse_pos = event->pos(); + setCursor(Qt::ClosedHandCursor); + } +} + +void Canvas::mouseReleaseEvent(QMouseEvent* event) +{ + if (event->button() == Qt::LeftButton || + event->button() == Qt::RightButton) + { + unsetCursor(); + } +} + +void Canvas::mouseMoveEvent(QMouseEvent* event) +{ + auto p = event->pos(); + auto d = p - mouse_pos; + + if (event->buttons() & Qt::LeftButton) + { + yaw = fmod(yaw - d.x(), 360); + tilt = fmax(0, fmin(180, tilt - d.y())); + update(); + } + else if (event->buttons() & Qt::RightButton) + { + 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) +{ + // 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) + zoom *= 1.001; + } + else if (event->delta() > 0) + { + 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(); +} diff --git a/src/canvas.h b/src/canvas.h new file mode 100644 index 0000000..33313cf --- /dev/null +++ b/src/canvas.h @@ -0,0 +1,60 @@ +#ifndef CANVAS_H +#define CANVAS_H + +#include +#include +#include +#include +#include + +class GLMesh; +class Mesh; +class Backdrop; + +class Canvas : public QGLWidget, protected QGLFunctions +{ + Q_OBJECT + +public: + Canvas(const QGLFormat& format, QWidget* parent=0); + + void initializeGL(); + void paintEvent(QPaintEvent* event); + ~Canvas(); + +public slots: + void set_status(const QString& s); + void clear_status(); + void load_mesh(Mesh* m); + + +protected: + void mousePressEvent(QMouseEvent* event); + void mouseReleaseEvent(QMouseEvent* event); + void mouseMoveEvent(QMouseEvent* event); + void wheelEvent(QWheelEvent* event); + + +private: + void draw_mesh(); + + QMatrix4x4 transform_matrix() const; + QMatrix4x4 view_matrix() const; + + QGLShaderProgram mesh_shader; + QGLShaderProgram quad_shader; + + GLMesh* mesh; + Backdrop* backdrop; + + QVector3D center; + float scale; + float zoom; + float tilt; + float yaw; + + QPoint mouse_pos; + QString status; +}; + +#endif // CANVAS_H diff --git a/src/glmesh.cpp b/src/glmesh.cpp new file mode 100644 index 0000000..7053809 --- /dev/null +++ b/src/glmesh.cpp @@ -0,0 +1,37 @@ +#include "glmesh.h" +#include "mesh.h" + +GLMesh::GLMesh(const Mesh* const mesh) + : vertices(QGLBuffer::VertexBuffer), indices(QGLBuffer::IndexBuffer) +{ + initializeGLFunctions(); + + vertices.create(); + indices.create(); + + vertices.setUsagePattern(QGLBuffer::StaticDraw); + indices.setUsagePattern(QGLBuffer::StaticDraw); + + vertices.bind(); + vertices.allocate(mesh->vertices.data(), + mesh->vertices.size() * sizeof(float)); + vertices.release(); + + indices.bind(); + indices.allocate(mesh->indices.data(), + mesh->indices.size() * sizeof(uint32_t)); + indices.release(); +} + +void GLMesh::draw(GLuint vp) +{ + vertices.bind(); + indices.bind(); + + glVertexAttribPointer(vp, 3, GL_FLOAT, false, 3*sizeof(float), NULL); + glDrawElements(GL_TRIANGLES, indices.size() / sizeof(uint32_t), + GL_UNSIGNED_INT, NULL); + + vertices.release(); + indices.release(); +} diff --git a/src/glmesh.h b/src/glmesh.h new file mode 100644 index 0000000..74a193f --- /dev/null +++ b/src/glmesh.h @@ -0,0 +1,19 @@ +#ifndef GLMESH_H +#define GLMESH_H + +#include +#include + +class Mesh; + +class GLMesh : protected QGLFunctions +{ +public: + GLMesh(const Mesh* const mesh); + void draw(GLuint vp); +private: + QGLBuffer vertices; + QGLBuffer indices; +}; + +#endif // GLMESH_H diff --git a/src/loader.cpp b/src/loader.cpp new file mode 100644 index 0000000..0d6b165 --- /dev/null +++ b/src/loader.cpp @@ -0,0 +1,129 @@ +#include "loader.h" + +Loader::Loader(QObject* parent, const QString& filename) + : QThread(parent), filename(filename) +{ + // Nothing to do here +} + +void Loader::run() +{ + Mesh* mesh = load_stl(); + if (mesh) + { + emit got_mesh(mesh); + emit loaded_file(filename); + } +} + + +//////////////////////////////////////////////////////////////////////////////// + +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; + } + + // 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 new file mode 100644 index 0000000..fb0c8d8 --- /dev/null +++ b/src/loader.h @@ -0,0 +1,30 @@ +#ifndef LOADER_H +#define LOADER_H + +#include + +#include "mesh.h" + +class Loader : public QThread +{ + Q_OBJECT +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(); + +private: + const QString filename; + +}; + +#endif // LOADER_H diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..4b76222 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,9 @@ +#include + +#include "app.h" + +int main(int argc, char *argv[]) +{ + App a(argc, argv); + return a.exec(); +} diff --git a/src/mesh.cpp b/src/mesh.cpp new file mode 100644 index 0000000..cfb4ca4 --- /dev/null +++ b/src/mesh.cpp @@ -0,0 +1,35 @@ +#include +#include +#include + +#include + +#include "mesh.h" + +//////////////////////////////////////////////////////////////////////////////// + +Mesh::Mesh(std::vector v, std::vector i) + : vertices(v), indices(i) +{ + // Nothing to do here +} + +float Mesh::min(size_t start) const +{ + float v = vertices[start]; + for (size_t i=start; i < vertices.size(); i += 3) + { + v = fmin(v, vertices[i]); + } + return v; +} + +float Mesh::max(size_t start) const +{ + float v = vertices[start]; + for (size_t i=start; i < vertices.size(); i += 3) + { + v = fmax(v, vertices[i]); + } + return v; +} diff --git a/src/mesh.h b/src/mesh.h new file mode 100644 index 0000000..e8a02f0 --- /dev/null +++ b/src/mesh.h @@ -0,0 +1,31 @@ +#ifndef MESH_H +#define MESH_H + +#include +#include + +#include + +class Mesh +{ +public: + Mesh(std::vector vertices, std::vector indices); + + float min(size_t start) const; + float max(size_t start) const; + + float xmin() const { return min(0); } + float ymin() const { return min(1); } + float zmin() const { return min(2); } + float xmax() const { return max(0); } + float ymax() const { return max(1); } + float zmax() const { return max(2); } + +private: + std::vector vertices; + std::vector indices; + + friend class GLMesh; +}; + +#endif // MESH_H diff --git a/src/window.cpp b/src/window.cpp new file mode 100644 index 0000000..3002ecc --- /dev/null +++ b/src/window.cpp @@ -0,0 +1,146 @@ +#include +#include +#include + +#include "window.h" +#include "canvas.h" +#include "loader.h" + +Window::Window(QWidget *parent) : + QMainWindow(parent), + open_action(new QAction("Open", this)), + about_action(new QAction("About", this)), + quit_action(new QAction("Quit", this)) + +{ + setWindowTitle("fstl"); + setAcceptDrops(true); + + QFile styleFile(":/qt/style.qss"); + styleFile.open( QFile::ReadOnly ); + setStyleSheet(styleFile.readAll()); + + QGLFormat format; + format.setVersion(2, 1); + format.setSampleBuffers(true); + + canvas = new Canvas(format, this); + setCentralWidget(canvas); + + open_action->setShortcut(QKeySequence::Open); + QObject::connect(open_action, &QAction::triggered, + this, &Window::on_open); + + quit_action->setShortcut(QKeySequence::Quit); + QObject::connect(quit_action, &QAction::triggered, + this, &Window::close); + + QObject::connect(about_action, &QAction::triggered, + this, &Window::on_about); + + auto file_menu = menuBar()->addMenu("File"); + file_menu->addAction(open_action); + file_menu->addAction(quit_action); + + auto help_menu = menuBar()->addMenu("Help"); + help_menu->addAction(about_action); + + resize(600, 400); +} + +void Window::on_open() +{ + QString filename = QFileDialog::getOpenFileName( + this, "Load .stl file", QString(), "*.stl"); + if (not filename.isNull()) + { + load_stl(filename); + } +} + +void Window::on_about() +{ + QMessageBox::about(this, "", + "

fstl

" + "

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

" + "

© 2014 Matthew Keeter
" + "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::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); +} + +void Window::disable_open() +{ + open_action->setEnabled(false); +} + +bool Window::load_stl(const QString& filename) +{ + if (!open_action->isEnabled()) return false; + + canvas->set_status("Loading " + filename); + + Loader* loader = new Loader(this, filename); + connect(loader, &Loader::started, + this, &Window::disable_open); + + connect(loader, &Loader::got_mesh, + 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); + connect(loader, &Loader::finished, + this, &Window::enable_open); + connect(loader, &Loader::finished, + canvas, &Canvas::clear_status); + + if (filename[0] != ':') + { + connect(loader, &Loader::loaded_file, + this, &Window::setWindowTitle); + } + + 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 new file mode 100644 index 0000000..cb92ebb --- /dev/null +++ b/src/window.h @@ -0,0 +1,36 @@ +#ifndef WINDOW_H +#define WINDOW_H + +#include + +class Canvas; + +class Window : public QMainWindow +{ + Q_OBJECT +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(); + void on_ascii_stl(); + void on_bad_stl(); + + void enable_open(); + void disable_open(); + +private: + QAction* const open_action; + QAction* const about_action; + QAction* const quit_action; + + Canvas* canvas; +}; + +#endif // WINDOW_H