From 25b935eb199abe52da5c47fbfe64ba838df83d0d Mon Sep 17 00:00:00 2001 From: Jakob Haufe Date: Mon, 31 Mar 2025 12:42:16 +0200 Subject: [PATCH] New upstream version 0.11.0 --- CMakeLists.txt | 12 +- gl/gl.qrc | 1 + gl/mesh_light.frag | 26 +++ qt/icons/fstl_64x64.png | Bin 0 -> 4007 bytes qt/qt.qrc | 3 +- src/app.cpp | 12 +- src/canvas.cpp | 295 +++++++++++++++++++++++++++-- src/canvas.h | 54 +++++- src/loader.cpp | 9 + src/main.cpp | 4 + src/shaderlightprefs.cpp | 180 ++++++++++++++++++ src/shaderlightprefs.h | 46 +++++ src/window.cpp | 185 ++++++++++++++++-- src/window.h | 23 +++ xdg/README.txt | 23 +++ xdg/fstlapp-fstl.desktop | 9 + xdg/icons/fstlapp-fstl_128x128.png | Bin 0 -> 8324 bytes xdg/icons/fstlapp-fstl_16x16.png | Bin 0 -> 1122 bytes xdg/icons/fstlapp-fstl_22x22.png | Bin 0 -> 1461 bytes xdg/icons/fstlapp-fstl_256x256.png | Bin 0 -> 18244 bytes xdg/icons/fstlapp-fstl_32x32.png | Bin 0 -> 1991 bytes xdg/icons/fstlapp-fstl_48x48.png | Bin 0 -> 3374 bytes xdg/icons/fstlapp-fstl_64x64.png | Bin 0 -> 4006 bytes xdg/xdg_install.sh | 52 +++++ xdg/xdg_package_install.sh | 53 ++++++ xdg/xdg_uninstall.sh | 50 +++++ 26 files changed, 996 insertions(+), 41 deletions(-) create mode 100644 gl/mesh_light.frag create mode 100644 qt/icons/fstl_64x64.png create mode 100644 src/shaderlightprefs.cpp create mode 100644 src/shaderlightprefs.h create mode 100644 xdg/README.txt create mode 100644 xdg/fstlapp-fstl.desktop create mode 100644 xdg/icons/fstlapp-fstl_128x128.png create mode 100644 xdg/icons/fstlapp-fstl_16x16.png create mode 100644 xdg/icons/fstlapp-fstl_22x22.png create mode 100644 xdg/icons/fstlapp-fstl_256x256.png create mode 100644 xdg/icons/fstlapp-fstl_32x32.png create mode 100644 xdg/icons/fstlapp-fstl_48x48.png create mode 100644 xdg/icons/fstlapp-fstl_64x64.png create mode 100755 xdg/xdg_install.sh create mode 100755 xdg/xdg_package_install.sh create mode 100755 xdg/xdg_uninstall.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 0628afa..fe586c7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ #### Fast .stl viewer ##### -# Original Project Author: Matt Keeter Copyright 2014 -2017 -# Author: Paul Tsouchlos Copyright 2017 +# Original Project Author: Matt Keeter Copyright 2014-2024 +# Author: Paul Tsouchlos Copyright 2017-2024 cmake_minimum_required(VERSION 3.3) @@ -13,7 +13,7 @@ set(CXX_STANDARD_REQUIRED ON) # Set the version number set (FSTL_VERSION_MAJOR "0") -set (FSTL_VERSION_MINOR "10") +set (FSTL_VERSION_MINOR "11") set (FSTL_VERSION_PATCH "0") set (PROJECT_VERSION "${FSTL_VERSION_MAJOR}.${FSTL_VERSION_MINOR}.${FSTL_VERSION_PATCH}") @@ -32,7 +32,8 @@ src/glmesh.cpp src/loader.cpp src/main.cpp src/mesh.cpp -src/window.cpp) +src/window.cpp +src/shaderlightprefs.cpp) #set project headers. set(Project_Headers src/app.h @@ -42,7 +43,8 @@ src/canvas.h src/glmesh.h src/loader.h src/mesh.h -src/window.h) +src/window.h +src/shaderlightprefs.h) #set project resources and icon resource set(Project_Resources qt/qt.qrc gl/gl.qrc) diff --git a/gl/gl.qrc b/gl/gl.qrc index 6e8baa9..a806530 100644 --- a/gl/gl.qrc +++ b/gl/gl.qrc @@ -4,6 +4,7 @@ mesh.vert mesh_wireframe.frag mesh_surfaceangle.frag + mesh_light.frag quad.frag quad.vert colored_lines.frag diff --git a/gl/mesh_light.frag b/gl/mesh_light.frag new file mode 100644 index 0000000..31d43bb --- /dev/null +++ b/gl/mesh_light.frag @@ -0,0 +1,26 @@ +#version 120 + +uniform float zoom; +uniform vec4 ambient_light_color; +uniform vec4 directive_light_color; +uniform vec3 directive_light_direction; + +varying vec3 ec_pos; + +void main() { + // Normalize light direction + vec3 dir = normalize(directive_light_direction); + + // vec3 a = vec3(0.0, 1.0, 1.0); + // normal vector + vec3 ec_normal = normalize(cross(dFdx(ec_pos), dFdy(ec_pos))); + ec_normal.z *= zoom; + ec_normal = normalize(ec_normal); + + + vec3 color = ambient_light_color.w * ambient_light_color.xyz + directive_light_color.w * dot(ec_normal,dir) * directive_light_color.xyz; + + // float coef = dot(ec_normal,dir); + // vec3 color = coef * lightcolor + (1.0 - coef) * objectcolor; + gl_FragColor = vec4(color, 1.0); +} diff --git a/qt/icons/fstl_64x64.png b/qt/icons/fstl_64x64.png new file mode 100644 index 0000000000000000000000000000000000000000..2a63e418068a895b90e92f59dfc0763d4e39e9fa GIT binary patch literal 4007 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 N002ovPDHLkV1nP&U_}4` literal 0 HcmV?d00001 diff --git a/qt/qt.qrc b/qt/qt.qrc index 883ac14..f2b1266 100644 --- a/qt/qt.qrc +++ b/qt/qt.qrc @@ -1,5 +1,6 @@ - + style.qss + icons/fstl_64x64.png diff --git a/src/app.cpp b/src/app.cpp index a76a7da..07de99e 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -1,5 +1,6 @@ #include #include +#include #include "app.h" #include "window.h" @@ -7,10 +8,17 @@ App::App(int& argc, char *argv[]) : QApplication(argc, argv), window(new Window()) { - if (argc > 1) - window->load_stl(argv[1]); + if (argc > 1) { + QString filename = argv[1]; + if (filename.startsWith("~")) { + filename.replace(0, 1, QDir::homePath()); + } + window->load_stl(filename); + } else + { window->load_stl(":gl/sphere.stl"); + } window->show(); } diff --git a/src/canvas.cpp b/src/canvas.cpp index 4da41a7..ad4ec37 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -11,9 +11,21 @@ const float Canvas::P_PERSPECTIVE = 0.25f; const float Canvas::P_ORTHOGRAPHIC = 0.0f; +const QString Canvas::AMBIENT_COLOR = "ambientColor"; +const QString Canvas::AMBIENT_FACTOR = "ambientFactor"; +const QString Canvas::DIRECTIVE_COLOR = "directiveColor"; +const QString Canvas::DIRECTIVE_FACTOR = "directiveFactor"; +const QString Canvas::CURRENT_LIGHT_DIRECTION = "currentLightDirection"; + +const QColor Canvas::defaultAmbientColor = QColor::fromRgbF(0.22,0.8,1.0); +const QColor Canvas::defaultDirectiveColor = QColor(255,255,255); +const float Canvas::defaultAmbientFactor = 0.67; +const float Canvas::defaultDirectiveFactor = 0.5; +const int Canvas::defaultCurrentLightDirection = 1; + Canvas::Canvas(const QSurfaceFormat& format, QWidget *parent) : QOpenGLWidget(parent), mesh(nullptr), - scale(1), zoom(1), tilt(90), yaw(0), + scale(1), zoom(1), anim(this, "perspective"), status(" "), meshInfo("") { @@ -21,6 +33,38 @@ Canvas::Canvas(const QSurfaceFormat& format, QWidget *parent) QFile styleFile(":/qt/style.qss"); styleFile.open( QFile::ReadOnly ); setStyleSheet(styleFile.readAll()); + currentTransform = QMatrix4x4(); + resetTransform(); + + QSettings settings; + ambientColor = settings.value(AMBIENT_COLOR,defaultAmbientColor).value(); + directiveColor = settings.value(DIRECTIVE_COLOR,defaultDirectiveColor).value(); + ambientFactor = settings.value(AMBIENT_FACTOR,defaultAmbientFactor).value(); + directiveFactor = settings.value(DIRECTIVE_FACTOR,defaultDirectiveFactor).value(); + + // Fill direction list + // Fill in directions + nameDir.clear(); + listDir.clear(); + QList xname, yname, zname; + xname << "right " << " " << "left "; + yname << "top " << " " << "bottom "; + zname << "rear " << " " << "front "; + for (int i=-1; i<2 ; i++) { + for (int j=-1; j<2; j++) { + for (int k=-1; k<2; k++) { + QString current = xname.at(i+1) + yname.at(j+1) + zname.at(k+1); + if (!(i==0 && j==0 && k==0)) { + nameDir << current.simplified(); + listDir << QVector3D((double)i,(double)j,(double)k); + } + } + } + } + currentLightDirection = settings.value(CURRENT_LIGHT_DIRECTION,defaultCurrentLightDirection).value(); + if (currentLightDirection < 0 || currentLightDirection >= nameDir.length()) { + currentLightDirection = defaultCurrentLightDirection; + } anim.setDuration(100); } @@ -42,6 +86,65 @@ void Canvas::view_anim(float v) anim.start(); } +void Canvas::common_view_change(enum ViewPoint c) +{ + if (c == centerview) { + scale = default_scale; + center = default_center; + zoom = 1; + update(); + return; + } + + currentTransform.setToIdentity(); + currentTransform.rotate(180.0, QVector3D(0, 0, 1)); + + switch (c) + { + case isoview: + { + currentTransform.rotate(90, QVector3D(1, 0, 0)); + currentTransform.rotate(-45, QVector3D(0, 0, 1)); + currentTransform.rotate(35.264, QVector3D(1, 1, 0)); + } + break; + case topview: + { + currentTransform.rotate(180, QVector3D(1, 0, 0)); + } + break; + case leftview: + { + currentTransform.rotate(180, QVector3D(1, 0, 0)); + currentTransform.rotate(90, QVector3D(0, 0, 1)); + currentTransform.rotate(90, QVector3D(0, 1, 0)); + } + break; + case rightview: + { + currentTransform.rotate(180, QVector3D(1, 0, 0)); + currentTransform.rotate(-90.0, QVector3D(0, 1, 0)); + currentTransform.rotate(-90, QVector3D(1, 0, 0)); + } + break; + case frontview: + { + currentTransform.rotate(90, QVector3D(1, 0, 0)); + } + break; + case backview: + { + currentTransform.rotate(90, QVector3D(1, 0, 0)); + currentTransform.rotate(180, QVector3D(0, 0, 1)); + } + case bottomview: + [[fallthrough]]; + default: + break; + } + update(); +} + void Canvas::view_perspective(float p, bool animate){ if(animate) { @@ -65,6 +168,20 @@ void Canvas::invert_zoom(bool d) update(); } +void Canvas::setResetTransformOnLoad(bool d) { + resetTransformOnLoad = d; +} + +void Canvas::resetTransform() { + currentTransform.setToIdentity(); + // apply some rotations to define initial orientation + currentTransform.rotate(-90.0, QVector3D(1, 0, 0)); + currentTransform.rotate(180.0 + 15.0, QVector3D(0, 0, 1)); + currentTransform.rotate(15.0, QVector3D(1, -sin(M_PI/12), 0)); + + zoom = 1; +} + void Canvas::load_mesh(Mesh* m, bool is_reload) { delete mesh; @@ -73,13 +190,14 @@ void Canvas::load_mesh(Mesh* m, bool is_reload) QVector3D upper(m->xmax(), m->ymax(), m->zmax()); if (!is_reload) { - center = (lower + upper) / 2; - scale = 2 / (upper - lower).length(); + default_center = center = (lower + upper) / 2; + default_scale = scale = 2 / (upper - lower).length(); // Reset other camera parameters zoom = 1; - yaw = 0; - tilt = 90; + if (resetTransformOnLoad) { + resetTransform(); + } } meshInfo = QStringLiteral("Triangles: %1\nX: [%2, %3]\nY: [%4, %5]\nZ: [%6, %7]").arg(m->triCount()); for(int dIdx = 0; dIdx < 3; dIdx++) meshInfo = meshInfo.arg(lower[dIdx]).arg(upper[dIdx]); @@ -128,6 +246,9 @@ void Canvas::initializeGL() mesh_surfaceangle_shader.addShader(mesh_vertshader); mesh_surfaceangle_shader.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/gl/mesh_surfaceangle.frag"); mesh_surfaceangle_shader.link(); + mesh_meshlight_shader.addShader(mesh_vertshader); + mesh_meshlight_shader.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/gl/mesh_light.frag"); + mesh_meshlight_shader.link(); backdrop = new Backdrop(); axis = new Axis(); @@ -165,10 +286,14 @@ void Canvas::draw_mesh() { selected_mesh_shader = &mesh_shader; } - else + else if (drawMode == surfaceangle) { selected_mesh_shader = &mesh_surfaceangle_shader; } + else if (drawMode == meshlight) + { + selected_mesh_shader = &mesh_meshlight_shader; + } glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } @@ -185,6 +310,28 @@ void Canvas::draw_mesh() // Compensate for z-flattening when zooming glUniform1f(selected_mesh_shader->uniformLocation("zoom"), 1/zoom); + // specific meshlight arguments + if (drawMode == meshlight) { + // Ambient Light Color, followed by the ambient light coefficient to use + //glUniform4f(selected_mesh_shader->uniformLocation("ambient_light_color"),0.22f, 0.8f, 1.0f, 0.67f); + glUniform4f(selected_mesh_shader->uniformLocation("ambient_light_color"),ambientColor.redF(), ambientColor.greenF(), ambientColor.blueF(), ambientFactor); + // Directive Light Color, followed by the directive light coefficient to use + //glUniform4f(selected_mesh_shader->uniformLocation("directive_light_color"),1.0f,1.0f,1.0f,0.5f); + glUniform4f(selected_mesh_shader->uniformLocation("directive_light_color"),directiveColor.redF(),directiveColor.greenF(),directiveColor.blueF(),directiveFactor); + + // Directive Light Direction + // dir 1,0,0 Light from the left + // dir -1,0,0 Light from the right + // dir 0,1,0 Light from bottom + // dir 0,-1,0 Light from top + // dir 0,0,1 Light from viewer (front) + // dir 0,0,-1 Light from behind + // + // -1,-1,0 Light from top right + //glUniform3f(selected_mesh_shader->uniformLocation("directive_light_direction"),-1.0f,-1.0f,0.0f); + glUniform3f(selected_mesh_shader->uniformLocation("directive_light_direction"),listDir.at(currentLightDirection).x(), listDir.at(currentLightDirection).y(), listDir.at(currentLightDirection).z()); + } + // Find and enable the attribute location for vertex position const GLuint vp = selected_mesh_shader->attributeLocation("vertex_position"); glEnableVertexAttribArray(vp); @@ -201,11 +348,7 @@ void Canvas::draw_mesh() } QMatrix4x4 Canvas::orient_matrix() const { - QMatrix4x4 m; - m.rotate(tilt, QVector3D(1, 0, 0)); - m.rotate(yaw, QVector3D(0, 0, 1)); - //We want the x axis to the right, and the z axis up - m.scale(-1, 1, -1); + QMatrix4x4 m = currentTransform; return m; } QMatrix4x4 Canvas::transform_matrix() const @@ -255,16 +398,72 @@ void Canvas::mouseReleaseEvent(QMouseEvent* event) } } + +// This method change the referential of the mouse point coordinates +// into a referential x=[-1.0,1.0], y=[-1.0,1.0], with 0,0 being the +// center of the widget. +QPointF Canvas::changeMouseCoordinates(QPoint p) { + QPointF pr; + // Change coordinates + double ws2 = this->width() / 2.0; + double hs2 = this->height() / 2.0; + pr.setX(p.x() / ws2 - 1.0); + pr.setY(p.y() / hs2 - 1.0); + return pr; +} + +void Canvas::calcArcballTransform(QPointF p1, QPointF p2) { + // Calc z1 & z2 + double x1 = p1.x(); + double x2 = p2.x(); + double y1 = p1.y(); + double y2 = p2.y(); + double p1sq = x1 * x1 + y1 * y1; + double z1; + if (p1sq <= 1) { + z1 = sqrt(1.0 - p1sq); + } else { + x1 = x1 / sqrt(p1sq); + y1 = y1 / sqrt(p1sq); + z1 = 0.0; + } + double p2sq = x2 * x2 + y2 * y2; + double z2; + if (p2sq <= 1) { + z2 = sqrt(1.0 - p2sq); + } else { + x2 = x2 / sqrt(p2sq); + y2 = y2 / sqrt(p2sq); + z2 = 0.0; + } + + // set v1 and v2 + QVector3D v1(x1, y1, z1); + QVector3D v2(x2, y2, z2); + + // calc v1 cross v2 + QVector3D v1xv2 = QVector3D::crossProduct(v1, v2); + QVector3D v1xv2Obj = currentTransform.inverted().mapVector(v1xv2); + + // calc angle + double angle = acos(std::min(1.0f,QVector3D::dotProduct(v1, v2))) * 180.0 / M_PI; + + // apply transform + currentTransform.rotate(angle,v1xv2Obj); +} + 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 = fmod(tilt - d.y(), 360); + QPointF p1r = changeMouseCoordinates(mouse_pos); + QPointF p2r = changeMouseCoordinates(p); + calcArcballTransform(p1r,p2r); + update(); } else if (event->buttons() & Qt::RightButton) @@ -316,3 +515,71 @@ void Canvas::resizeGL(int width, int height) { glViewport(0, 0, width, height); } + +QColor Canvas::getAmbientColor() { + return ambientColor; +} + +void Canvas::setAmbientColor(QColor c) { + ambientColor = c; + QSettings settings; + settings.setValue(AMBIENT_COLOR,c); +} + +double Canvas::getAmbientFactor() { + return (float) ambientFactor; +} + +void Canvas::setAmbientFactor(double f) { + ambientFactor = (float) f; + QSettings settings; + settings.setValue(AMBIENT_FACTOR,f); +} + +void Canvas::resetAmbientColor() { + setAmbientColor(defaultAmbientColor); + setAmbientFactor(defaultAmbientFactor); +} + +QColor Canvas::getDirectiveColor() { + return directiveColor; +} + +void Canvas::setDirectiveColor(QColor c) { + directiveColor = c; + QSettings settings; + settings.setValue(DIRECTIVE_COLOR,c); +} + +double Canvas::getDirectiveFactor() { + return (float) directiveFactor; +} + +void Canvas::setDirectiveFactor(double f) { + directiveFactor = (float) f; + QSettings settings; + settings.setValue(DIRECTIVE_FACTOR,f); +} + +void Canvas::resetDirectiveColor() { + setDirectiveColor(defaultDirectiveColor); + setDirectiveFactor(defaultDirectiveFactor); +} + +QList Canvas::getNameDir() { + return nameDir; +} + +int Canvas::getCurrentLightDirection() { + return currentLightDirection; +} + +void Canvas::setCurrentLightDirection(int ind) { + currentLightDirection = ind; + QSettings settings; + settings.setValue(CURRENT_LIGHT_DIRECTION,currentLightDirection); +} + +void Canvas::resetCurrentLightDirection() { + setCurrentLightDirection(defaultCurrentLightDirection); +} diff --git a/src/canvas.h b/src/canvas.h index 0fdff64..8dab30c 100644 --- a/src/canvas.h +++ b/src/canvas.h @@ -10,7 +10,8 @@ class Mesh; class Backdrop; class Axis; -enum DrawMode {shaded, wireframe, surfaceangle, DRAWMODECOUNT}; +enum ViewPoint {centerview, isoview, topview, bottomview, leftview, rightview, frontview, backview}; +enum DrawMode {shaded, wireframe, surfaceangle, meshlight, DRAWMODECOUNT}; class Canvas : public QOpenGLWidget, protected QOpenGLFunctions { @@ -27,6 +28,25 @@ public: void draw_axes(bool d); void invert_zoom(bool d); void set_drawMode(enum DrawMode mode); + void common_view_change(enum ViewPoint c); + void setResetTransformOnLoad(bool d); + + QColor getAmbientColor(); + void setAmbientColor(QColor c); + double getAmbientFactor(); + void setAmbientFactor(double f); + void resetAmbientColor(); + + QColor getDirectiveColor(); + void setDirectiveColor(QColor c); + double getDirectiveFactor(); + void setDirectiveFactor(double f); + void resetDirectiveColor(); + + QList getNameDir(); + int getCurrentLightDirection(); + void setCurrentLightDirection(int ind); + void resetCurrentLightDirection(); public slots: void set_status(const QString& s); @@ -53,26 +73,50 @@ private: QMatrix4x4 transform_matrix() const; QMatrix4x4 aspect_matrix() const; QMatrix4x4 view_matrix() const; + void resetTransform(); + QPointF changeMouseCoordinates(QPoint p); + void calcArcballTransform(QPointF p1, QPointF p2); QOpenGLShader* mesh_vertshader; QOpenGLShaderProgram mesh_shader; QOpenGLShaderProgram mesh_wireframe_shader; QOpenGLShaderProgram mesh_surfaceangle_shader; + QOpenGLShaderProgram mesh_meshlight_shader; + + QColor ambientColor; + QColor directiveColor; + float ambientFactor; + float directiveFactor; + QList nameDir; + QList listDir; + int currentLightDirection; + + const static QColor defaultAmbientColor; + const static QColor defaultDirectiveColor; + const static float defaultAmbientFactor; + const static float defaultDirectiveFactor; + const static int defaultCurrentLightDirection; + const static QString AMBIENT_COLOR; + const static QString AMBIENT_FACTOR; + const static QString DIRECTIVE_COLOR; + const static QString DIRECTIVE_FACTOR; + const static QString CURRENT_LIGHT_DIRECTION; + GLMesh* mesh; Backdrop* backdrop; Axis* axis; - QVector3D center; - float scale; + QVector3D center, default_center; + float scale, default_scale; float zoom; - float tilt; - float yaw; + QMatrix4x4 currentTransform; float perspective; enum DrawMode drawMode; bool drawAxes; bool invertZoom; + bool resetTransformOnLoad; Q_PROPERTY(float perspective MEMBER perspective WRITE set_perspective); QPropertyAnimation anim; diff --git a/src/loader.cpp b/src/loader.cpp index 38bd2dd..58b81a1 100644 --- a/src/loader.cpp +++ b/src/loader.cpp @@ -116,6 +116,15 @@ Mesh* Loader::load_stl() return NULL; } + qint64 file_size, file_size_old; + file_size = file.size(); + do { + file_size_old = file_size; + QThread::usleep(100000); + file_size = file.size(); + } + while(file_size != file_size_old); + // First, try to read the stl as an ASCII file if (file.read(5) == "solid") { diff --git a/src/main.cpp b/src/main.cpp index 4ec8722..14561eb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,10 +4,14 @@ int main(int argc, char *argv[]) { + // Force C locale to force decimal point + QLocale::setDefault(QLocale::c()); + QCoreApplication::setOrganizationName("fstl-app"); QCoreApplication::setOrganizationDomain("https://github.com/fstl-app/fstl"); QCoreApplication::setApplicationName("fstl"); QCoreApplication::setApplicationVersion(FSTL_VERSION); App a(argc, argv); + return a.exec(); } diff --git a/src/shaderlightprefs.cpp b/src/shaderlightprefs.cpp new file mode 100644 index 0000000..d00c707 --- /dev/null +++ b/src/shaderlightprefs.cpp @@ -0,0 +1,180 @@ +#include "shaderlightprefs.h" +#include "canvas.h" +#include + +const QString ShaderLightPrefs::PREFS_GEOM = "shaderPrefsGeometry"; + +ShaderLightPrefs::ShaderLightPrefs(QWidget *parent, Canvas *_canvas) : QDialog(parent) +{ + canvas = _canvas; + + QVBoxLayout* prefsLayout = new QVBoxLayout; + this->setLayout(prefsLayout); + + QLabel* title = new QLabel("Shader preferences"); + QFont boldFont = QApplication::font(); + boldFont.setWeight(QFont::Bold); + title->setFont(boldFont); + title->setAlignment(Qt::AlignCenter); + prefsLayout->addWidget(title); + + QWidget* middleWidget = new QWidget; + QGridLayout* middleLayout = new QGridLayout; + middleWidget->setLayout(middleLayout); + this->layout()->addWidget(middleWidget); + + // labels + middleLayout->addWidget(new QLabel("Ambient Color"),0,0); + middleLayout->addWidget(new QLabel("Directive Color"),1,0); + middleLayout->addWidget(new QLabel("Direction"),2,0); + + QPixmap dummy(20, 20); + + dummy.fill(canvas->getAmbientColor()); + buttonAmbientColor = new QPushButton; + buttonAmbientColor->setIcon(QIcon(dummy)); + middleLayout->addWidget(buttonAmbientColor,0,1); + buttonAmbientColor->setFocusPolicy(Qt::NoFocus); + connect(buttonAmbientColor,SIGNAL(clicked(bool)),this,SLOT(buttonAmbientColorClicked())); + + editAmbientFactor = new QLineEdit; + editAmbientFactor->setValidator(new QDoubleValidator); + editAmbientFactor->setText(QString("%1").arg(canvas->getAmbientFactor())); + middleLayout->addWidget(editAmbientFactor,0,2); + connect(editAmbientFactor,SIGNAL(editingFinished()),this,SLOT(editAmbientFactorFinished())); + + QPushButton* buttonResetAmbientColor = new QPushButton("Reset"); + middleLayout->addWidget(buttonResetAmbientColor,0,3); + buttonResetAmbientColor->setFocusPolicy(Qt::NoFocus); + connect(buttonResetAmbientColor,SIGNAL(clicked(bool)),this,SLOT(resetAmbientColorClicked())); + + + dummy.fill(canvas->getDirectiveColor()); + buttonDirectiveColor = new QPushButton; + buttonDirectiveColor->setIcon(QIcon(dummy)); + middleLayout->addWidget(buttonDirectiveColor,1,1); + buttonDirectiveColor->setFocusPolicy(Qt::NoFocus); + connect(buttonDirectiveColor,SIGNAL(clicked(bool)),this,SLOT(buttonDirectiveColorClicked())); + + editDirectiveFactor = new QLineEdit; + editDirectiveFactor->setValidator(new QDoubleValidator); + editDirectiveFactor->setText(QString("%1").arg(canvas->getDirectiveFactor())); + middleLayout->addWidget(editDirectiveFactor,1,2); + connect(editDirectiveFactor,SIGNAL(editingFinished()),this,SLOT(editDirectiveFactorFinished())); + + QPushButton* buttonResetDirectiveColor = new QPushButton("Reset"); + middleLayout->addWidget(buttonResetDirectiveColor,1,3); + buttonResetDirectiveColor->setFocusPolicy(Qt::NoFocus); + connect(buttonResetDirectiveColor,SIGNAL(clicked(bool)),this,SLOT(resetDirectiveColorClicked())); + + // Fill in directions + + comboDirections = new QComboBox; + middleLayout->addWidget(comboDirections,2,1,1,2); + comboDirections->addItems(canvas->getNameDir()); + comboDirections->setCurrentIndex(canvas->getCurrentLightDirection()); + connect(comboDirections,SIGNAL(currentIndexChanged(int)),this,SLOT(comboDirectionsChanged(int))); + + QPushButton* buttonResetDirection = new QPushButton("Reset"); + middleLayout->addWidget(buttonResetDirection,2,3); + buttonResetDirection->setFocusPolicy(Qt::NoFocus); + connect(buttonResetDirection,SIGNAL(clicked(bool)),this,SLOT(resetDirection())); + + + // Ok button + QWidget* boxButton = new QWidget; + QHBoxLayout* boxButtonLayout = new QHBoxLayout; + boxButton->setLayout(boxButtonLayout); + QFrame *spacerL = new QFrame; + spacerL->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding)); + QPushButton* okButton = new QPushButton("Ok"); + boxButtonLayout->addWidget(spacerL); + boxButtonLayout->addWidget(okButton); + this->layout()->addWidget(boxButton); + okButton->setFocusPolicy(Qt::NoFocus); + connect(okButton,SIGNAL(clicked(bool)),this,SLOT(okButtonClicked())); + + QSettings settings; + if (!settings.value(PREFS_GEOM).isNull()) { + restoreGeometry(settings.value(PREFS_GEOM).toByteArray()); + } +} + +void ShaderLightPrefs::buttonAmbientColorClicked() { + QColor newColor = QColorDialog::getColor(canvas->getAmbientColor(), this, QString("Choose color"),QColorDialog::DontUseNativeDialog); + if (newColor.isValid() == true) + { + canvas->setAmbientColor(newColor); + QPixmap dummy(20, 20); + dummy.fill(canvas->getAmbientColor()); + buttonAmbientColor->setIcon(QIcon(dummy)); + canvas->update(); + } +} + +void ShaderLightPrefs::editAmbientFactorFinished() { + canvas->setAmbientFactor(editAmbientFactor->text().toDouble()); + canvas->update(); +} + +void ShaderLightPrefs::resetAmbientColorClicked() { + canvas->resetAmbientColor(); + QPixmap dummy(20, 20); + dummy.fill(canvas->getAmbientColor()); + buttonAmbientColor->setIcon(QIcon(dummy)); + editAmbientFactor->setText(QString("%1").arg(canvas->getAmbientFactor())); + canvas->update(); +} + +void ShaderLightPrefs::buttonDirectiveColorClicked() { + QColor newColor = QColorDialog::getColor(canvas->getDirectiveColor(), this, QString("Choose color"),QColorDialog::DontUseNativeDialog); + if (newColor.isValid() == true) + { + canvas->setDirectiveColor(newColor); + QPixmap dummy(20, 20); + dummy.fill(canvas->getDirectiveColor()); + buttonDirectiveColor->setIcon(QIcon(dummy)); + canvas->update(); + } +} + +void ShaderLightPrefs::editDirectiveFactorFinished() { + canvas->setDirectiveFactor(editDirectiveFactor->text().toDouble()); + canvas->update(); +} + +void ShaderLightPrefs::resetDirectiveColorClicked() { + canvas->resetDirectiveColor(); + QPixmap dummy(20, 20); + dummy.fill(canvas->getDirectiveColor()); + buttonDirectiveColor->setIcon(QIcon(dummy)); + editDirectiveFactor->setText(QString("%1").arg(canvas->getDirectiveFactor())); + canvas->update(); +} + +void ShaderLightPrefs::okButtonClicked() { + this->close(); +} + +void ShaderLightPrefs::comboDirectionsChanged(int ind) { + canvas->setCurrentLightDirection(ind); + canvas->update(); +} + +void ShaderLightPrefs::resetDirection() { + canvas->resetCurrentLightDirection(); + comboDirections->setCurrentIndex(canvas->getCurrentLightDirection()); + canvas->update(); +} + +void ShaderLightPrefs::resizeEvent(QResizeEvent *event) +{ + QSettings().setValue(PREFS_GEOM, saveGeometry()); +} + +void ShaderLightPrefs::moveEvent(QMoveEvent *event) +{ + QSettings().setValue(PREFS_GEOM, saveGeometry()); +} + + diff --git a/src/shaderlightprefs.h b/src/shaderlightprefs.h new file mode 100644 index 0000000..9f57339 --- /dev/null +++ b/src/shaderlightprefs.h @@ -0,0 +1,46 @@ +#ifndef SHADERLIGHTPREFS_H +#define SHADERLIGHTPREFS_H + +#include + +class Canvas; +class QLabel; +class QLineEdit; +class QComboBox; + +class ShaderLightPrefs : public QDialog +{ + Q_OBJECT +public: + ShaderLightPrefs(QWidget* parent, Canvas* _canvas); + +protected: + void resizeEvent(QResizeEvent *event) override; + void moveEvent(QMoveEvent *event) override; + +private slots: + void buttonAmbientColorClicked(); + void editAmbientFactorFinished(); + void resetAmbientColorClicked(); + + void buttonDirectiveColorClicked(); + void editDirectiveFactorFinished(); + void resetDirectiveColorClicked(); + + void comboDirectionsChanged(int ind); + void resetDirection(); + + void okButtonClicked(); + +private: + Canvas* canvas; + QPushButton* buttonAmbientColor; + QLineEdit* editAmbientFactor; + QPushButton* buttonDirectiveColor; + QLineEdit* editDirectiveFactor; + QComboBox* comboDirections; + + const static QString PREFS_GEOM; +}; + +#endif // SHADERLIGHTPREFS_H diff --git a/src/window.cpp b/src/window.cpp index e9f3f94..c8aaba7 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -3,7 +3,9 @@ #include "window.h" #include "canvas.h" #include "loader.h" +#include "shaderlightprefs.h" +const QString Window::OPEN_EXTERNAL_KEY = "externalCmd"; const QString Window::RECENT_FILE_KEY = "recentFiles"; const QString Window::INVERT_ZOOM_KEY = "invertZoom"; const QString Window::AUTORELOAD_KEY = "autoreload"; @@ -11,23 +13,37 @@ const QString Window::DRAW_AXES_KEY = "drawAxes"; const QString Window::PROJECTION_KEY = "projection"; const QString Window::DRAW_MODE_KEY = "drawMode"; const QString Window::WINDOW_GEOM_KEY = "windowGeometry"; +const QString Window::RESET_TRANSFORM_ON_LOAD_KEY = "resetTransformOnLoad"; Window::Window(QWidget *parent) : QMainWindow(parent), open_action(new QAction("Open", this)), + open_external_action(new QAction("Open with", this)), about_action(new QAction("About", this)), quit_action(new QAction("Quit", this)), perspective_action(new QAction("Perspective", this)), + common_view_center_action(new QAction("Center the model", this)), + common_view_iso_action(new QAction("Isometric", this)), + common_view_top_action(new QAction("Top", this)), + common_view_bottom_action(new QAction("Bottom", this)), + common_view_left_action(new QAction("Left", this)), + common_view_right_action(new QAction("Right", this)), + common_view_front_action(new QAction("Front", this)), + common_view_back_action(new QAction("Back", this)), orthographic_action(new QAction("Orthographic", this)), shaded_action(new QAction("Shaded", this)), wireframe_action(new QAction("Wireframe", this)), surfaceangle_action(new QAction("Surface Angle", this)), + meshlight_action(new QAction("Shaded ambient and directive light source", this)), + drawModePrefs_action(new QAction("Draw Mode Settings")), axes_action(new QAction("Draw Axes", this)), invert_zoom_action(new QAction("Invert Zoom", this)), reload_action(new QAction("Reload", this)), autoreload_action(new QAction("Autoreload", this)), save_screenshot_action(new QAction("Save Screenshot", this)), hide_menuBar_action(new QAction("Hide Menu Bar", this)), + fullscreen_action(new QAction("Toggle Fullscreen",this)), + resetTransformOnLoadAction(new QAction("Reset rotation on load",this)), recent_files(new QMenu("Open recent", this)), recent_files_group(new QActionGroup(this)), recent_files_clear_action(new QAction("Clear recent files", this)), @@ -35,6 +51,7 @@ Window::Window(QWidget *parent) : { setWindowTitle("fstl"); + setWindowIcon(QIcon(":/qt/icons/fstl_64x64.png")); setAcceptDrops(true); QSurfaceFormat format; @@ -48,6 +65,10 @@ Window::Window(QWidget *parent) : canvas = new Canvas(format, this); setCentralWidget(canvas); + meshlightprefs = new ShaderLightPrefs(this, canvas); + + QObject::connect(drawModePrefs_action, &QAction::triggered,this,&Window::on_drawModePrefs); + QObject::connect(watcher, &QFileSystemWatcher::fileChanged, this, &Window::on_watched_change); @@ -56,7 +77,13 @@ Window::Window(QWidget *parent) : this, &Window::on_open); this->addAction(open_action); - quit_action->setShortcut(QKeySequence::Quit); + open_external_action->setShortcut(QKeySequence::Open); + QObject::connect(open_external_action, &QAction::triggered, this, &Window::on_open_external); + this->addAction(open_external_action); + open_external_action->setShortcut(QKeySequence(Qt::ALT + Qt::Key_S)); + + QList quitShortcuts = { QKeySequence::Quit, QKeySequence::Close }; + quit_action->setShortcuts(quitShortcuts); QObject::connect(quit_action, &QAction::triggered, this, &Window::close); this->addAction(quit_action); @@ -84,8 +111,9 @@ Window::Window(QWidget *parent) : rebuild_recent_files(); - auto file_menu = menuBar()->addMenu("File"); + const auto file_menu = menuBar()->addMenu("File"); file_menu->addAction(open_action); + file_menu->addAction(open_external_action); file_menu->addMenu(recent_files); file_menu->addSeparator(); file_menu->addAction(reload_action); @@ -93,11 +121,11 @@ Window::Window(QWidget *parent) : file_menu->addAction(save_screenshot_action); file_menu->addAction(quit_action); - auto view_menu = menuBar()->addMenu("View"); - auto projection_menu = view_menu->addMenu("Projection"); + const auto view_menu = menuBar()->addMenu("View"); + const auto projection_menu = view_menu->addMenu("Projection"); projection_menu->addAction(perspective_action); projection_menu->addAction(orthographic_action); - auto projections = new QActionGroup(projection_menu); + const auto projections = new QActionGroup(projection_menu); for (auto p : {perspective_action, orthographic_action}) { projections->addAction(p); @@ -107,12 +135,13 @@ Window::Window(QWidget *parent) : QObject::connect(projections, &QActionGroup::triggered, this, &Window::on_projection); - auto draw_menu = view_menu->addMenu("Draw Mode"); + const auto draw_menu = view_menu->addMenu("Draw Mode"); draw_menu->addAction(shaded_action); draw_menu->addAction(wireframe_action); draw_menu->addAction(surfaceangle_action); - auto drawModes = new QActionGroup(draw_menu); - for (auto p : {shaded_action, wireframe_action, surfaceangle_action}) + draw_menu->addAction(meshlight_action); + const auto drawModes = new QActionGroup(draw_menu); + for (auto p : {shaded_action, wireframe_action, surfaceangle_action, meshlight_action}) { drawModes->addAction(p); p->setCheckable(true); @@ -120,6 +149,38 @@ Window::Window(QWidget *parent) : drawModes->setExclusive(true); QObject::connect(drawModes, &QActionGroup::triggered, this, &Window::on_drawMode); + view_menu->addAction(drawModePrefs_action); + drawModePrefs_action->setDisabled(true); + + const auto common_menu = view_menu->addMenu("Viewpoint"); + common_menu->addAction(common_view_iso_action); + common_menu->addAction(common_view_top_action); + common_menu->addAction(common_view_bottom_action); + common_menu->addAction(common_view_front_action); + common_menu->addAction(common_view_back_action); + common_menu->addAction(common_view_left_action); + common_menu->addAction(common_view_right_action); + common_menu->addAction(common_view_center_action); + const auto common_views = new QActionGroup(common_menu); + common_views->addAction(common_view_iso_action); + common_views->addAction(common_view_top_action); + common_views->addAction(common_view_bottom_action); + common_views->addAction(common_view_front_action); + common_views->addAction(common_view_back_action); + common_views->addAction(common_view_left_action); + common_views->addAction(common_view_right_action); + common_views->addAction(common_view_center_action); + common_view_iso_action->setShortcut(Qt::Key_0); + common_view_top_action->setShortcut(Qt::Key_1); + common_view_bottom_action->setShortcut(Qt::Key_2); + common_view_front_action->setShortcut(Qt::Key_3); + common_view_back_action->setShortcut(Qt::Key_4); + common_view_left_action->setShortcut(Qt::Key_5); + common_view_right_action->setShortcut(Qt::Key_6); + common_view_center_action->setShortcut(Qt::Key_9); + QObject::connect(common_views, &QActionGroup::triggered, + this, &Window::on_common_view_change); + view_menu->addAction(axes_action); axes_action->setCheckable(true); QObject::connect(axes_action, &QAction::triggered, @@ -130,6 +191,11 @@ Window::Window(QWidget *parent) : QObject::connect(invert_zoom_action, &QAction::triggered, this, &Window::on_invertZoom); + view_menu->addAction(resetTransformOnLoadAction); + resetTransformOnLoadAction->setCheckable(true); + QObject::connect(resetTransformOnLoadAction, &QAction::triggered, + this, &Window::on_resetTransformOnLoad); + view_menu->addAction(hide_menuBar_action); hide_menuBar_action->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_C); hide_menuBar_action->setCheckable(true); @@ -137,6 +203,14 @@ Window::Window(QWidget *parent) : this, &Window::on_hide_menuBar); this->addAction(hide_menuBar_action); + view_menu->addAction(fullscreen_action); + fullscreen_action->setShortcut(Qt::Key_F11); + fullscreen_action->setCheckable(true); + QObject::connect(fullscreen_action, &QAction::toggled, + this, &Window::on_fullscreen); + this->addAction(fullscreen_action); + + auto help_menu = menuBar()->addMenu("Help"); help_menu->addAction(about_action); @@ -149,6 +223,10 @@ void Window::load_persist_settings(){ canvas->invert_zoom(invert_zoom); invert_zoom_action->setChecked(invert_zoom); + bool resetTransformOnLoad = settings.value(RESET_TRANSFORM_ON_LOAD_KEY, true).toBool(); + canvas->setResetTransformOnLoad(resetTransformOnLoad); + resetTransformOnLoadAction->setChecked(resetTransformOnLoad); + autoreload_action->setChecked(settings.value(AUTORELOAD_KEY, true).toBool()); bool draw_axes = settings.value(DRAW_AXES_KEY, false).toBool(); @@ -164,30 +242,73 @@ void Window::load_persist_settings(){ orthographic_action->setChecked(true); } + QString path = settings.value(OPEN_EXTERNAL_KEY, "").toString(); + if (!QDir::isAbsolutePath(path) && !path.isEmpty()) + { + path = QStandardPaths::findExecutable(path); + } + QString displayName = path.mid(path.lastIndexOf(QDir::separator()) + 1); + open_external_action->setText("Open with " + displayName); + open_external_action->setData(path); + DrawMode draw_mode = (DrawMode)settings.value(DRAW_MODE_KEY, DRAWMODECOUNT).toInt(); if(draw_mode >= DRAWMODECOUNT) { draw_mode = shaded; } - canvas->set_drawMode(draw_mode); - QAction* (dm_acts[]) = {shaded_action, wireframe_action, surfaceangle_action}; + QAction* (dm_acts[]) = {shaded_action, wireframe_action, surfaceangle_action, meshlight_action}; dm_acts[draw_mode]->setChecked(true); + on_drawMode(dm_acts[draw_mode]); resize(600, 400); restoreGeometry(settings.value(WINDOW_GEOM_KEY).toByteArray()); } +void Window::on_drawModePrefs() { + // For now only one draw mode has settings + // when settings for other draw mode will be available + // we will need to check the current mode + if (meshlightprefs->isVisible()) { + meshlightprefs->hide(); + } else { + meshlightprefs->show(); + } +} + void Window::on_open() { - QString filename = QFileDialog::getOpenFileName( - this, "Load .stl file", QString(), "STL files (*.stl, *.STL)"); + const QString filename = QFileDialog::getOpenFileName( + this, "Load .stl file", QString(), "STL files (*.stl *.STL)"); if (!filename.isNull()) { load_stl(filename); } } +void Window::on_open_external() const +{ + if (current_file.isEmpty()) + { + return; + } + + + QString program = open_external_action->data().toString(); + if (program.isEmpty()) { + program = QFileDialog::getOpenFileName((QWidget*) this, "Select program to open with", QDir::rootPath()); + if (!program.isEmpty()) { + QSettings settings; + settings.setValue(OPEN_EXTERNAL_KEY, program); + QString displayName = program.mid(program.lastIndexOf(QDir::separator()) + 1); + open_external_action->setText("Open with " + displayName); + open_external_action->setData(program); + } + } + + QProcess::startDetached(program, QStringList(current_file)); +} + void Window::on_about() { QMessageBox::about(this, "", @@ -195,7 +316,7 @@ void Window::on_about() "

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

" - "

© 2014-2022 Matthew Keeter
" + "

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

"); } @@ -270,19 +391,30 @@ void Window::on_projection(QAction* proj) void Window::on_drawMode(QAction* act) { + // On mode change hide prefs first + meshlightprefs->hide(); + DrawMode mode; if (act == shaded_action) { + drawModePrefs_action->setEnabled(false); mode = shaded; } else if (act == wireframe_action) { + drawModePrefs_action->setEnabled(false); mode = wireframe; } - else + else if (act == surfaceangle_action) { + drawModePrefs_action->setEnabled(false); mode = surfaceangle; } + else if (act == meshlight_action) + { + drawModePrefs_action->setEnabled(true); + mode = meshlight; + } canvas->set_drawMode(mode); QSettings().setValue(DRAW_MODE_KEY, mode); } @@ -299,6 +431,11 @@ void Window::on_invertZoom(bool d) QSettings().setValue(INVERT_ZOOM_KEY, d); } +void Window::on_resetTransformOnLoad(bool d) { + canvas->setResetTransformOnLoad(d); + QSettings().setValue(RESET_TRANSFORM_ON_LOAD_KEY, d); +} + void Window::on_watched_change(const QString& filename) { if (autoreload_action->isChecked()) @@ -410,6 +547,18 @@ void Window::on_reload() } } +void Window::on_common_view_change(QAction* common) +{ + if (common == common_view_center_action) canvas->common_view_change(centerview); + if (common == common_view_iso_action) canvas->common_view_change(isoview); + if (common == common_view_top_action) canvas->common_view_change(topview); + if (common == common_view_bottom_action) canvas->common_view_change(bottomview); + if (common == common_view_left_action) canvas->common_view_change(leftview); + if (common == common_view_right_action) canvas->common_view_change(rightview); + if (common == common_view_front_action) canvas->common_view_change(frontview); + if (common == common_view_back_action) canvas->common_view_change(backview); +} + bool Window::load_stl(const QString& filename, bool is_reload) { if (!open_action->isEnabled()) return false; @@ -610,3 +759,11 @@ void Window::keyPressEvent(QKeyEvent* event) QMainWindow::keyPressEvent(event); } + +void Window::on_fullscreen() { + if (!this->isFullScreen()) { + this->showFullScreen(); + } else { + this->showNormal(); + } +} diff --git a/src/window.h b/src/window.h index 81cd29f..f13137b 100644 --- a/src/window.h +++ b/src/window.h @@ -7,6 +7,7 @@ #include class Canvas; +class ShaderLightPrefs; class Window : public QMainWindow { @@ -26,6 +27,7 @@ protected: public slots: void on_open(); + void on_open_external() const; void on_about(); void on_bad_stl(); void on_empty_mesh(); @@ -41,14 +43,18 @@ private slots: void on_drawMode(QAction* mode); void on_drawAxes(bool d); void on_invertZoom(bool d); + void on_resetTransformOnLoad(bool d); void on_watched_change(const QString& filename); void on_reload(); + void on_common_view_change(QAction* common); 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(); + void on_fullscreen(); void on_hide_menuBar(); + void on_drawModePrefs(); private: void rebuild_recent_files(); @@ -58,24 +64,38 @@ private: QPair get_file_neighbors(); QAction* const open_action; + QAction* const open_external_action; QAction* const about_action; QAction* const quit_action; QAction* const perspective_action; + QAction* const common_view_center_action; + QAction* const common_view_iso_action; + QAction* const common_view_top_action; + QAction* const common_view_bottom_action; + QAction* const common_view_left_action; + QAction* const common_view_right_action; + QAction* const common_view_front_action; + QAction* const common_view_back_action; QAction* const orthographic_action; QAction* const shaded_action; QAction* const wireframe_action; QAction* const surfaceangle_action; + QAction* const meshlight_action; + QAction* const drawModePrefs_action; QAction* const axes_action; QAction* const invert_zoom_action; QAction* const reload_action; QAction* const autoreload_action; QAction* const save_screenshot_action; QAction* const hide_menuBar_action; + QAction* const fullscreen_action; + QAction* const resetTransformOnLoadAction; QMenu* const recent_files; QActionGroup* const recent_files_group; QAction* const recent_files_clear_action; const static int MAX_RECENT_FILES=8; + static const QString OPEN_EXTERNAL_KEY; const static QString RECENT_FILE_KEY; const static QString INVERT_ZOOM_KEY; const static QString AUTORELOAD_KEY; @@ -83,6 +103,7 @@ private: const static QString PROJECTION_KEY; const static QString DRAW_MODE_KEY; const static QString WINDOW_GEOM_KEY; + const static QString RESET_TRANSFORM_ON_LOAD_KEY; QString current_file; QString lookup_folder; @@ -91,6 +112,8 @@ private: QFileSystemWatcher* watcher; Canvas* canvas; + + ShaderLightPrefs* meshlightprefs; }; #endif // WINDOW_H diff --git a/xdg/README.txt b/xdg/README.txt new file mode 100644 index 0000000..7aad2b0 --- /dev/null +++ b/xdg/README.txt @@ -0,0 +1,23 @@ +Linux : +----------- +desktop file and application icons installation. +This tells the system that fstl knows to open stl files and allow stl to +be launched using windows key. + +Install : +./xdg_install.sh fstl + +Uninstall : +./xdg_uninstall.sh fstl + +if runned as regular user this will install locally in : + $HOME/.local/share/mime/ + $HOME/.local/share/applications/ + $HOME/.local/share/icons/ + +if runned as root this will install system-wide in : + /usr/share/mime + /usr/share/applications + /usr/share/icons + +Third script xdg_package_install.sh is to be used when building deb or rpm package. diff --git a/xdg/fstlapp-fstl.desktop b/xdg/fstlapp-fstl.desktop new file mode 100644 index 0000000..b4e93c5 --- /dev/null +++ b/xdg/fstlapp-fstl.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Name=fstl +GenericName=Fast STL Viewer +Exec=fstl %U +Terminal=false +Icon=fstlapp-fstl +Type=Application +MimeType=model/stl; +Categories=Utility; diff --git a/xdg/icons/fstlapp-fstl_128x128.png b/xdg/icons/fstlapp-fstl_128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..0c312003be16819219c8b9af1f3e1b646f4cfb43 GIT binary patch literal 8324 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 P00000NkvXXu0mjf18unx literal 0 HcmV?d00001 diff --git a/xdg/icons/fstlapp-fstl_256x256.png b/xdg/icons/fstlapp-fstl_256x256.png new file mode 100644 index 0000000000000000000000000000000000000000..4235dc581eb675021d24d062b50e91ec7bee873a GIT binary patch literal 18244 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 Ef`QpW1poj5 literal 0 HcmV?d00001 diff --git a/xdg/icons/fstlapp-fstl_64x64.png b/xdg/icons/fstlapp-fstl_64x64.png new file mode 100644 index 0000000000000000000000000000000000000000..0f83e6bf4c2862628b92c4f42284630db36649d8 GIT binary patch literal 4006 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 M07*qoM6N<$f?1hrZ2$lO literal 0 HcmV?d00001 diff --git a/xdg/xdg_install.sh b/xdg/xdg_install.sh new file mode 100755 index 0000000..b9d59b2 --- /dev/null +++ b/xdg/xdg_install.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +# This script will install mimetypes, icons and desktop file, +# it takes a name in argument +# +# if runned as regular user this will install locally in : +# $HOME/.local/share/mime/ +# $HOME/.local/share/applications/ +# $HOME/.local/share/icons/ +# +# if runned as root this will install system-wide in : +# /usr/share/mime +# /usr/share/applications +# /usr/share/icons + +if [ $# != 1 ]; then + echo "You must provide an application name" + exit 1 +fi + +name=$1 + +# echo "Installing mimetypes" +# xdg-mime install fstlapp-$name-mimetypes.xml + +echo "Installing desktop file" +xdg-desktop-menu install fstlapp-$name.desktop + +echo "Installing apps icons" +iclist="fstlapp-$name" +for im in $iclist +do + xdg-icon-resource install --theme hicolor --context apps --size 16 icons/${im}_16x16.png $im + xdg-icon-resource install --theme hicolor --context apps --size 22 icons/${im}_22x22.png $im + xdg-icon-resource install --theme hicolor --context apps --size 32 icons/${im}_32x32.png $im + xdg-icon-resource install --theme hicolor --context apps --size 48 icons/${im}_48x48.png $im + xdg-icon-resource install --theme hicolor --context apps --size 64 icons/${im}_64x64.png $im + xdg-icon-resource install --theme hicolor --context apps --size 128 icons/${im}_128x128.png $im + xdg-icon-resource install --theme hicolor --context apps --size 256 icons/${im}_256x256.png $im +done + +# echo "Installing mimetypes icons" +# iclist="`cat fstlapp-$name-mimetypes.xml | grep "icon name" | sed 's/^.*"\(.*\)".*$/\1/'`" +# for im in $iclist +# do +# xdg-icon-resource install --theme hicolor --context mimetypes --size 16 icons/${im}_16x16.png $im +# xdg-icon-resource install --theme hicolor --context mimetypes --size 22 icons/${im}_22x22.png $im +# xdg-icon-resource install --theme hicolor --context mimetypes --size 32 icons/${im}_32x32.png $im +# xdg-icon-resource install --theme hicolor --context mimetypes --size 48 icons/${im}_48x48.png $im +# xdg-icon-resource install --theme hicolor --context mimetypes --size 64 icons/${im}_64x64.png $im +# done + diff --git a/xdg/xdg_package_install.sh b/xdg/xdg_package_install.sh new file mode 100755 index 0000000..7e19a42 --- /dev/null +++ b/xdg/xdg_package_install.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +# For a package installation (rpm or deb), we must proceed a different way +# This script takes two arguments, the first one is the installation +# prefix and the second is the name + +if [ $# != 2 ]; then + echo "You must provide two arguments" + exit 1 +fi + +base=$1 +name=$2 + +# echo "Drop mimetypes file in /usr/share/mime/packages/" +# mkdir -p $base/usr/share/mime/packages/ +# cp fstlapp-$name-mimetypes.xml $base/usr/share/mime/packages/ + +echo "Drop desktop file in /usr/share/applications/" +mkdir -p $base/usr/share/applications/ +cp fstlapp-$name.desktop $base/usr/share/applications/ + +slist="16 22 32 48 64 128 256" +echo "Installing apps icons" +iclist="fstlapp-$name" +for im in $iclist +do + for s in $slist + do + mkdir -p $base/usr/share/icons/hicolor/${s}x${s}/apps + cp icons/${im}_${s}x${s}.png $base/usr/share/icons/hicolor/${s}x${s}/apps/$im.png + done +done + +# echo "Installing mimetypes icons" +# iclist="`cat fstlapp-$name-mimetypes.xml | grep "icon name" | sed 's/^.*"\(.*\)".*$/\1/'`" +# for im in $iclist +# do +# for s in $slist +# do +# mkdir -p $base/usr/share/icons/hicolor/${s}x${s}/mimetypes +# cp icons/${im}_${s}x${s}.png $base/usr/share/icons/hicolor/${s}x${s}/mimetypes/$im.png +# done +# done + +# +# Put this in the post installation and post uninstallation scripts +# +#echo "Updating mime database" +#update-mime-database /usr/share/mim +# +#echo "Updating desktop database" +#update-desktop-database diff --git a/xdg/xdg_uninstall.sh b/xdg/xdg_uninstall.sh new file mode 100755 index 0000000..1dd7ef4 --- /dev/null +++ b/xdg/xdg_uninstall.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +# This script will uninstall mimetypes, icons and desktop file +# +# if runned as regular user this will uninstall locally from : +# $HOME/.local/share/mime/ +# $HOME/.local/share/applications/ +# $HOME/.local/share/icons/ +# +# if runned as root this will uninstall system-wide from : +# /usr/share/mime +# /usr/share/applications +# /usr/share/icons + +if [ $# != 1 ]; then + echo "You must provide a name" + exit 1 +fi + +name=$1 + +# echo "Uninstalling mimetypes" +# xdg-mime uninstall fstlapp-$name-mimetypes.xml + +echo "Uninstalling desktop file" +xdg-desktop-menu uninstall fstlapp-$name.desktop + +echo "Uninstalling apps icons" +iclist="fstlapp-$name" +for im in $iclist +do + xdg-icon-resource uninstall --theme hicolor --context apps --size 16 $im + xdg-icon-resource uninstall --theme hicolor --context apps --size 22 $im + xdg-icon-resource uninstall --theme hicolor --context apps --size 32 $im + xdg-icon-resource uninstall --theme hicolor --context apps --size 48 $im + xdg-icon-resource uninstall --theme hicolor --context apps --size 64 $im + xdg-icon-resource uninstall --theme hicolor --context apps --size 128 $im + xdg-icon-resource uninstall --theme hicolor --context apps --size 256 $im +done + +# echo "Uninstalling mimetypes icons" +# iclist="`cat fstlapp-$name-mimetypes.xml | grep "icon name" | sed 's/^.*"\(.*\)".*$/\1/'`" +# for im in $iclist +# do +# xdg-icon-resource uninstall --theme hicolor --context mimetypes --size 16 $im +# xdg-icon-resource uninstall --theme hicolor --context mimetypes --size 22 $im +# xdg-icon-resource uninstall --theme hicolor --context mimetypes --size 32 $im +# xdg-icon-resource uninstall --theme hicolor --context mimetypes --size 48 $im +# xdg-icon-resource uninstall --theme hicolor --context mimetypes --size 64 $im +# done -- 2.39.5