Mon, 24 Aug 2020 23:00:50 +0300
merge commit
CMakeLists.txt | file | annotate | diff | comparison | revisions | |
src/basics.h | file | annotate | diff | comparison | revisions | |
src/gl/common.h | file | annotate | diff | comparison | revisions | |
src/gl/partrenderer.cpp | file | annotate | diff | comparison | revisions | |
src/main.cpp | file | annotate | diff | comparison | revisions |
--- a/CMakeLists.txt Mon Aug 24 22:55:37 2020 +0300 +++ b/CMakeLists.txt Mon Aug 24 23:00:50 2020 +0300 @@ -59,7 +59,13 @@ src/settingseditor/settingseditor.cpp src/types/boundingbox.cpp src/ui/canvas.cpp + src/ui/multiplyfactordialog.cpp + src/ui/objecteditor.cpp + src/ui/polygonobjecteditor.cpp src/widgets/colorbutton.cpp + src/widgets/doublespinbox.cpp + src/widgets/matrixeditor.cpp + src/widgets/vec3editor.cpp ) set (LDFORGE_HEADERS src/basics.h @@ -93,6 +99,8 @@ src/linetypes/errorline.h src/linetypes/metacommand.h src/linetypes/object.h + src/linetypes/polygonobject.h + src/linetypes/propertygenerics.h src/linetypes/quadrilateral.h src/linetypes/subfilereference.h src/linetypes/triangle.h @@ -101,13 +109,22 @@ src/settingseditor/settingseditor.h src/types/boundingbox.h src/ui/canvas.h + src/ui/multiplyfactordialog.h + src/ui/objecteditor.h + src/ui/polygonobjecteditor.h src/widgets/colorbutton.h + src/widgets/doublespinbox.h + src/widgets/matrixeditor.h + src/widgets/vec3editor.h ) set (LDFORGE_FORMS src/document.ui src/mainwindow.ui src/settingseditor/librarieseditor.ui src/settingseditor/settingseditor.ui + src/ui/multiplyfactordialog.ui + src/widgets/matrixeditor.ui + src/widgets/vec3editor.ui ) set(LDFORGE_LOCALES
--- a/locale/fi.ts Mon Aug 24 22:55:37 2020 +0300 +++ b/locale/fi.ts Mon Aug 24 23:00:50 2020 +0300 @@ -136,90 +136,134 @@ <translation>Näkymä</translation> </message> <message> - <location filename="../src/mainwindow.ui" line="63"/> + <location filename="../src/mainwindow.ui" line="64"/> <source>Quit</source> <translation>Lopeta</translation> </message> <message> - <location filename="../src/mainwindow.ui" line="68"/> + <location filename="../src/mainwindow.ui" line="69"/> <source>Open…</source> <translation>Avaa...</translation> </message> <message> - <location filename="../src/mainwindow.ui" line="71"/> + <location filename="../src/mainwindow.ui" line="72"/> <source>Ctrl+O</source> <translation>Ctrl+O</translation> </message> <message> - <location filename="../src/mainwindow.ui" line="76"/> + <location filename="../src/mainwindow.ui" line="77"/> <source>New</source> <translation>Uusi</translation> </message> <message> - <location filename="../src/mainwindow.ui" line="79"/> + <location filename="../src/mainwindow.ui" line="80"/> <source>Ctrl+N</source> <translation>Ctrl+N</translation> </message> <message> - <location filename="../src/mainwindow.ui" line="84"/> + <location filename="../src/mainwindow.ui" line="85"/> <source>Preferences…</source> <translation>Asetukset...</translation> </message> <message> - <location filename="../src/mainwindow.ui" line="92"/> + <location filename="../src/mainwindow.ui" line="93"/> <source>Normal colours</source> <translation type="unfinished">Perusvärit</translation> </message> <message> - <location filename="../src/mainwindow.ui" line="100"/> + <location filename="../src/mainwindow.ui" line="101"/> <source>BFC color coding</source> <translation type="unfinished">BFC-värikoodaus</translation> </message> <message> - <location filename="../src/mainwindow.ui" line="108"/> + <location filename="../src/mainwindow.ui" line="109"/> <source>Random colours</source> <translation type="unfinished">Satunnaiset värit</translation> </message> <message> - <location filename="../src/mainwindow.cpp" line="97"/> + <location filename="../src/mainwindow.ui" line="117"/> + <source>Pick scene colours</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/mainwindow.cpp" line="98"/> <source>Open model</source> <translation>Avaa malli</translation> </message> <message> - <location filename="../src/mainwindow.cpp" line="99"/> + <location filename="../src/mainwindow.cpp" line="100"/> <source>LDraw models (*.ldr *.dat)</source> <translation>LDraw-mallit (*.ldr *.dat)</translation> </message> <message> - <location filename="../src/mainwindow.cpp" line="118"/> + <location filename="../src/mainwindow.cpp" line="119"/> <source>Problem loading references</source> <translation type="unfinished">Ongelma viitteiden lataamisessa</translation> </message> <message> - <location filename="../src/mainwindow.cpp" line="128"/> + <location filename="../src/mainwindow.cpp" line="129"/> <source>Problem opening file</source> <translation>Ongelma tiedoston avaamisessa</translation> </message> <message> - <location filename="../src/mainwindow.cpp" line="130"/> + <location filename="../src/mainwindow.cpp" line="131"/> <source>Could not open %1: %2</source> <translation>Ei voitu avata %1: %2</translation> </message> </context> <context> + <name>MatrixEditor</name> + <message> + <location filename="../src/widgets/matrixeditor.ui" line="14"/> + <source>Form</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/widgets/matrixeditor.ui" line="87"/> + <location filename="../src/widgets/matrixeditor.ui" line="94"/> + <location filename="../src/widgets/matrixeditor.ui" line="101"/> + <location filename="../src/widgets/matrixeditor.ui" line="108"/> + <source>×</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>MultiplyFactorDialog</name> + <message> + <location filename="../src/ui/multiplyfactordialog.ui" line="14"/> + <source>Multiply with a scalar</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/ui/multiplyfactordialog.ui" line="24"/> + <source>Factor:</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/ui/multiplyfactordialog.ui" line="34"/> + <source>Invert</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/ui/multiplyfactordialog.ui" line="43"/> + <source>Preview</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> <name>QObject</name> <message> - <location filename="../src/gl/partrenderer.cpp" line="244"/> + <location filename="../src/gl/partrenderer.cpp" line="250"/> <source>OpenGL error: %1</source> <translation>OpenGL-virhe: %1</translation> </message> <message> - <location filename="../src/gl/partrenderer.cpp" line="245"/> + <location filename="../src/gl/partrenderer.cpp" line="251"/> <source>OpenGL error</source> <translation>OpenGL-virhe</translation> </message> <message> - <location filename="../src/gl/partrenderer.cpp" line="247"/> + <location filename="../src/gl/partrenderer.cpp" line="253"/> <source>Damn it</source> <translation>Hemmetti</translation> </message> @@ -328,4 +372,32 @@ <translation>Näppäinyhdistelmät</translation> </message> </context> +<context> + <name>Vec3Editor</name> + <message> + <location filename="../src/widgets/vec3editor.ui" line="14"/> + <source>Form</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/widgets/vec3editor.ui" line="20"/> + <source>x = </source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/widgets/vec3editor.ui" line="36"/> + <source>y = </source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/widgets/vec3editor.ui" line="52"/> + <source>z = </source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/widgets/vec3editor.ui" line="68"/> + <source>×</source> + <translation type="unfinished"></translation> + </message> +</context> </TS>
--- a/locale/sv.ts Mon Aug 24 22:55:37 2020 +0300 +++ b/locale/sv.ts Mon Aug 24 23:00:50 2020 +0300 @@ -224,6 +224,40 @@ <source>Random colours</source> <translation>Slumpmässiga färger</translation> </message> + <message> + <source>Pick scene colours</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>MatrixEditor</name> + <message> + <source>Form</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>×</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>MultiplyFactorDialog</name> + <message> + <source>Multiply with a scalar</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Factor:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Invert</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Preview</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>PartRenderer</name> @@ -335,6 +369,29 @@ </message> </context> <context> + <name>Vec3Editor</name> + <message> + <source>Form</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>x = </source> + <translation type="unfinished"></translation> + </message> + <message> + <source>y = </source> + <translation type="unfinished"></translation> + </message> + <message> + <source>z = </source> + <translation type="unfinished"></translation> + </message> + <message> + <source>×</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> <name>gl::Compiler</name> <message> <source>Vertex shader:</source>
--- a/src/basics.h Mon Aug 24 22:55:37 2020 +0300 +++ b/src/basics.h Mon Aug 24 23:00:50 2020 +0300 @@ -129,6 +129,17 @@ return static_cast<double>(x); } +/** +* @brief casts floating point values to qreal, converting non-floating point values causes an error +* @param[in] x floating point value to cast +* @returns qreal +*/ +template<typename T> +auto toQreal(T x) -> std::enable_if_t<std::is_floating_point_v<T>, qreal> +{ + return static_cast<qreal>(x); +} + template<int N, typename T, glm::qualifier Q> inline QPoint toQPoint(const glm::vec<N, T, Q>& vec) {
--- a/src/colors.h Mon Aug 24 22:55:37 2020 +0300 +++ b/src/colors.h Mon Aug 24 23:00:50 2020 +0300 @@ -28,9 +28,11 @@ struct ldraw::Color { - qint32 index; + qint32 index = 0; }; +Q_DECLARE_METATYPE(ldraw::Color) + class ldraw::ColorTable { public:
--- a/src/document.cpp Mon Aug 24 22:55:37 2020 +0300 +++ b/src/document.cpp Mon Aug 24 23:00:50 2020 +0300 @@ -31,13 +31,15 @@ documents{documents}, colorTable{colorTable}, renderer{new Canvas{model, documents, colorTable, this}}, - ui{*new Ui::Document} + ui{*new Ui::Document}, + objectEditor{model, ldraw::NULL_ID, this} { this->ui.setupUi(this); this->ui.listView->setModel(model); - QVBoxLayout* layout = new QVBoxLayout; - layout->addWidget(this->renderer); - this->ui.viewportFrame->setLayout(layout); + this->ui.viewportFrame->setLayout(new QVBoxLayout{this->ui.listView}); + this->ui.viewportFrame->layout()->addWidget(this->renderer); + this->ui.objectEditorFrame->setLayout(new QVBoxLayout{this->ui.objectEditorFrame}); + this->ui.objectEditorFrame->layout()->addWidget(&this->objectEditor); this->setMouseTracking(true); connect(this->ui.splitter, &QSplitter::splitterMoved, this, &Document::splitterChanged); connect(this->renderer, &Canvas::newStatusText, this, &Document::newStatusText); @@ -53,7 +55,9 @@ selection.select(index, index); } } + QSignalBlocker blocker{this}; selectionModel->select(selection, QItemSelectionModel::ClearAndSelect); + this->selectionChanged(newSelection); }); connect(this->ui.listView->selectionModel(), &QItemSelectionModel::selectionChanged, [&](const QItemSelection& selected, const QItemSelection& deselected) @@ -64,7 +68,9 @@ return fn::map<QSet<ldraw::id_t>>(selection.indexes(), resolveIndex); }; this->renderer->handleSelectionChange(resolve(selected), resolve(deselected)); + this->selectionChanged(resolve(this->ui.listView->selectionModel()->selection())); }); + connect(this->model, &Model::dataChanged, this->renderer, qOverload<>(&Canvas::update)); } Document::~Document() @@ -86,3 +92,15 @@ { this->renderer->setRenderPreferences(newPreferences); } + +void Document::selectionChanged(const QSet<ldraw::id_t>& newSelection) +{ + if (newSelection.size() == 1) + { + this->objectEditor.setObjectId(*newSelection.begin()); + } + else + { + this->objectEditor.setObjectId(ldraw::NULL_ID); + } +}
--- a/src/document.h Mon Aug 24 22:55:37 2020 +0300 +++ b/src/document.h Mon Aug 24 23:00:50 2020 +0300 @@ -20,6 +20,7 @@ #include <memory> #include <QWidget> #include "ui/canvas.h" +#include "ui/objecteditor.h" namespace Ui { @@ -45,9 +46,11 @@ void newStatusText(const QString& newStatusText); void splitterChanged(); private: + void selectionChanged(const QSet<ldraw::id_t>& newSelection); Model* model; DocumentManager* const documents; const ldraw::ColorTable& colorTable; Canvas* renderer; Ui::Document& ui; + ObjectEditor objectEditor; };
--- a/src/document.ui Mon Aug 24 22:55:37 2020 +0300 +++ b/src/document.ui Mon Aug 24 23:00:50 2020 +0300 @@ -19,6 +19,14 @@ <property name="orientation"> <enum>Qt::Horizontal</enum> </property> + <widget class="QFrame" name="viewportFrame"> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + </widget> <widget class="QListView" name="listView"> <property name="alternatingRowColors"> <bool>true</bool> @@ -30,7 +38,7 @@ <enum>QAbstractItemView::SelectRows</enum> </property> </widget> - <widget class="QFrame" name="viewportFrame"> + <widget class="QFrame" name="objectEditorFrame"> <property name="frameShape"> <enum>QFrame::StyledPanel</enum> </property>
--- a/src/geometry.h Mon Aug 24 22:55:37 2020 +0300 +++ b/src/geometry.h Mon Aug 24 23:00:50 2020 +0300 @@ -26,7 +26,7 @@ template<int N> struct Polygon { - glm::vec3 points[N]; + std::array<glm::vec3, N> points; }; template<int N>
--- a/src/gl/common.h Mon Aug 24 22:55:37 2020 +0300 +++ b/src/gl/common.h Mon Aug 24 23:00:50 2020 +0300 @@ -73,8 +73,7 @@ Triangle, Quadrilateral, ConditionalEdge - }; - Type type; + } type; glm::vec3 vertices[4]; ldraw::Color color; ldraw::id_t id; @@ -113,6 +112,16 @@ namespace gl { + constexpr Polygon::Type POLYGON_TYPES[] = + { + Polygon::Type::EdgeLine, + Polygon::Type::Triangle, + Polygon::Type::Quadrilateral, + Polygon::Type::ConditionalEdge + }; + + constexpr int NUM_POLYGON_TYPES = countof(POLYGON_TYPES); + inline Polygon edgeLine(const glm::vec3& v_1, const glm::vec3& v_2, ldraw::Color color, ldraw::id_t id) { return {Polygon::EdgeLine, {v_1, v_2}, color, id};
--- a/src/gl/compiler.cpp Mon Aug 24 22:55:37 2020 +0300 +++ b/src/gl/compiler.cpp Mon Aug 24 23:00:50 2020 +0300 @@ -171,9 +171,8 @@ if (not this->initialized) { this->initializeOpenGLFunctions(); - for (int i = 0; i < gl::NUM_ARRAY_CLASSES; i += 1) + for (auto& object : this->glObjects) { - auto& object = this->glObjects[i]; object.program = new QOpenGLShaderProgram; gl::buildShaders(object.program, ::vertexShaderSource, ::fragmentShaderSource); object.program->bind(); @@ -186,12 +185,12 @@ { object.program->enableAttributeArray(k); } - constexpr int stride = sizeof(gl::Vertex); - object.program->setAttributeBuffer(0, GL_FLOAT, offsetof(gl::Vertex, position), 3, stride); - object.program->setAttributeBuffer(1, GL_FLOAT, offsetof(gl::Vertex, color), 4, stride); - object.program->setAttributeBuffer(2, GL_FLOAT, offsetof(gl::Vertex, normal), 3, stride); - glVertexAttribIPointer(3, 1, GL_INT, stride, reinterpret_cast<void*>(offsetof(gl::Vertex, id))); - glVertexAttribIPointer(4, 1, GL_INT, stride, reinterpret_cast<void*>(offsetof(gl::Vertex, selected))); + constexpr int stride = sizeof(Vertex); + object.program->setAttributeBuffer(0, GL_FLOAT, offsetof(Vertex, position), 3, stride); + object.program->setAttributeBuffer(1, GL_FLOAT, offsetof(Vertex, color), 4, stride); + object.program->setAttributeBuffer(2, GL_FLOAT, offsetof(Vertex, normal), 3, stride); + glVertexAttribIPointer(3, 1, GL_INT, stride, reinterpret_cast<void*>(offsetof(Vertex, id))); + glVertexAttribIPointer(4, 1, GL_INT, stride, reinterpret_cast<void*>(offsetof(Vertex, selected))); object.vertexArray.release(); object.buffer.release(); object.program->release(); @@ -203,13 +202,13 @@ void gl::Compiler::build(Model* model, DocumentManager* context, const gl::RenderPreferences& preferences) { this->boundingBox = {}; - std::vector<gl::Vertex> vboData[gl::NUM_ARRAY_CLASSES]; + std::vector<Vertex> vboData[gl::NUM_POLYGON_TYPES]; const std::vector<gl::Polygon> polygons = model->getPolygons(context); for (const gl::Polygon& polygon : polygons) { this->buildPolygon(polygon, vboData, preferences); } - for (int arrayId = 0; arrayId < gl::NUM_ARRAY_CLASSES; arrayId += 1) + for (int arrayId = 0; arrayId < gl::NUM_POLYGON_TYPES; arrayId += 1) { auto& buffer = this->glObjects[arrayId].buffer; auto& vector = vboData[arrayId]; @@ -237,18 +236,18 @@ return gl::ArrayClass::Lines; } -ldraw::id_t gl::Compiler::idFromColor(const std::array<GLbyte, 3>& data) +ldraw::id_t gl::Compiler::idFromColor(const std::array<GLubyte, 3>& data) { return {data[0] * std::int32_t{0x10000} + data[1] * std::int32_t{0x100} + data[2]}; } void gl::Compiler::buildPolygon( gl::Polygon polygon, - std::vector<gl::Vertex>* vboData, + std::vector<Vertex>* vboData, const gl::RenderPreferences& preferences) { const gl::ArrayClass vboClass = classifyPolygon(polygon); - std::vector<gl::Vertex>& vertexBuffer = vboData[static_cast<int>(vboClass)]; + std::vector<Vertex>& vertexBuffer = vboData[static_cast<int>(vboClass)]; auto vertexRing = iter::ring(polygon.vertices, polygon.numPolygonVertices()); reserveMore(vertexBuffer, polygon.numPolygonVertices()); const QColor color = this->getColorForPolygon(polygon, preferences); @@ -258,7 +257,7 @@ const glm::vec3& v2 = vertexRing[i]; const glm::vec3& v3 = vertexRing[i + 1]; this->boundingBox.consider(polygon.vertices[i]); - gl::Vertex& vertex = vertexBuffer.emplace_back(); + Vertex& vertex = vertexBuffer.emplace_back(); vertex.position = polygon.vertices[i]; vertex.normal = glm::normalize(glm::cross(v1 - v2, v3 - v2)); vertex.color = glm::vec4{color.redF(), color.greenF(), color.blueF(), color.alphaF()}; @@ -313,16 +312,17 @@ void gl::Compiler::setSelectedObjects(const QSet<ldraw::id_t> ids) { - for (int i = 0; i < gl::NUM_ARRAY_CLASSES; i += 1) + for (auto& object : this->glObjects) { - auto& vector = this->glObjects[i].cachedData; - for (gl::Vertex& vertex : vector) + std::vector<Vertex>& vector = object.cachedData; + for (Vertex& vertex : vector) { vertex.selected = (ids.contains({vertex.id})) ? 1 : 0; } const GLsizeiptr size = static_cast<int>(vector.size() * sizeof vector[0]); - this->glObjects[i].buffer.bind(); + object.buffer.bind(); glBufferSubData(GL_ARRAY_BUFFER, 0, size, vector.data()); + object.buffer.release(); } }
--- a/src/gl/compiler.h Mon Aug 24 22:55:37 2020 +0300 +++ b/src/gl/compiler.h Mon Aug 24 23:00:50 2020 +0300 @@ -34,15 +34,6 @@ namespace gl { class Compiler; - - struct Vertex - { - glm::vec3 position; - glm::vec4 color; - glm::vec3 normal; - glm::int32 id; - glm::int32 selected = 0; - }; } class gl::Compiler : public QObject, protected QOpenGLExtraFunctions @@ -52,7 +43,6 @@ Compiler(const ldraw::ColorTable& colorTable, QObject* parent); ~Compiler(); void build(Model* model, DocumentManager* context, const RenderPreferences& preferences); - void buildPolygon(Polygon polygon, std::vector<Vertex>* vboData, const gl::RenderPreferences& preferences); std::size_t vertexCount(gl::ArrayClass arrayClass) const; QColor getColorForPolygon(const gl::Polygon& polygon, const RenderPreferences& preferences); glm::vec3 modelCenter() const; @@ -63,7 +53,7 @@ void buildShaders(int arrayId); void setSelectedObjects(const QSet<ldraw::id_t> ids); - static ldraw::id_t idFromColor(const std::array<GLbyte, 3>& data); + static ldraw::id_t idFromColor(const std::array<GLubyte, 3>& data); template<typename T> void setUniform(const char* uniformName, T&& value) @@ -86,7 +76,16 @@ this->setUniform(uniformName, *array); } private: - std::size_t storedVertexCounts[gl::NUM_ARRAY_CLASSES] = {0_z}; + struct Vertex + { + glm::vec3 position; + glm::vec4 color; + glm::vec3 normal; + glm::int32 id; + glm::int32 selected = 0; + }; + void buildPolygon(Polygon polygon, std::vector<Vertex>* vboData, const gl::RenderPreferences& preferences); + std::size_t storedVertexCounts[gl::NUM_POLYGON_TYPES] = {0}; bool initialized = false; BoundingBox boundingBox; const ldraw::ColorTable& colorTable; @@ -97,8 +96,8 @@ QOpenGLShaderProgram* pickSceneProgram = nullptr; QOpenGLBuffer buffer{QOpenGLBuffer::VertexBuffer}; QOpenGLVertexArrayObject vertexArray; - std::vector<gl::Vertex> cachedData; - } glObjects[gl::NUM_ARRAY_CLASSES]; + std::vector<Vertex> cachedData; + } glObjects[gl::NUM_POLYGON_TYPES]; }; #define CHECK_GL_ERROR() { checkGLError(__FILE__, __LINE__); }
--- a/src/gl/partrenderer.cpp Mon Aug 24 22:55:37 2020 +0300 +++ b/src/gl/partrenderer.cpp Mon Aug 24 23:00:50 2020 +0300 @@ -60,11 +60,7 @@ abort(); } this->compiler->initialize(); - this->compiler->build(this->model, this->documents, this->renderPreferences); - connect(this->model, &Model::dataChanged, [&]() - { - this->compiler->build(this->model, this->documents, this->renderPreferences); - }); + connect(this->model, &Model::dataChanged, this, &PartRenderer::build); this->initialized = true; this->modelQuaternion = glm::angleAxis(glm::radians(30.0f), glm::vec3{-1, 0, 0}); this->modelQuaternion *= glm::angleAxis(glm::radians(225.0f), glm::vec3{-0, 1, 0}); @@ -111,6 +107,11 @@ void PartRenderer::renderScene() { + if (this->needBuild) + { + this->compiler->build(this->model, this->documents, this->renderPreferences); + this->needBuild = false; + } this->checkForGLErrors(); if (this->renderPreferences.lineAntiAliasing && this->renderPreferences.style != gl::RenderStyle::PickScene) { @@ -211,6 +212,11 @@ { } +void PartRenderer::build() +{ + this->needBuild = true; +} + void PartRenderer::renderVao(const gl::ArrayClass arrayClass) { this->compiler->bindVertexArray(arrayClass); @@ -340,7 +346,7 @@ this->renderPreferences.style = gl::RenderStyle::PickScene; this->makeCurrent(); this->renderScene(); - std::array<GLbyte, 3> data; + std::array<GLubyte, 3> data; this->checkForGLErrors(); glReadPixels(where.x(), this->height() - where.y(), 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &data[0]); this->checkForGLErrors(); @@ -369,7 +375,7 @@ this->renderPreferences = newPreferences; if (mainColorChanged or backgroundColorChanged) { - this->compiler->build(this->model, this->documents, this->renderPreferences); + this->build(); this->setupBackgroundColor(); } emit this->renderPreferencesChanged();
--- a/src/gl/partrenderer.h Mon Aug 24 22:55:37 2020 +0300 +++ b/src/gl/partrenderer.h Mon Aug 24 23:00:50 2020 +0300 @@ -57,10 +57,12 @@ void updateViewMatrix(); void updateModelMatrix(); void setupBackgroundColor(); + Q_SLOT void build(); static constexpr double MIN_ZOOM = 0.0; static constexpr double MAX_ZOOM = 3.0; double zoom = 1.0; bool initialized = false; + bool needBuild = true; void renderVao(const gl::ArrayClass arrayClass); void checkForGLErrors(); };
--- a/src/linetypes/conditionaledge.cpp Mon Aug 24 22:55:37 2020 +0300 +++ b/src/linetypes/conditionaledge.cpp Mon Aug 24 23:00:50 2020 +0300 @@ -1,56 +1,10 @@ #include "conditionaledge.h" -ldraw::ConditionalEdge::ConditionalEdge( - const glm::vec3& point_1, - const glm::vec3& point_2, - const glm::vec3& controlPoint_1, - const glm::vec3& controlPoint_2, - const Color color_index) : - Edge{point_1, point_2, color_index}, - controlPoint_1{controlPoint_1}, - controlPoint_2{controlPoint_2} -{ -} - -ldraw::ConditionalEdge::ConditionalEdge(const std::array<glm::vec3, 4>& vertices, const Color color) : - Edge{vertices[0], vertices[1], color}, - controlPoint_1{vertices[2]}, - controlPoint_2{vertices[3]} -{ -} - -QVariant ldraw::ConditionalEdge::getProperty(Property property) const -{ - switch (property) - { - case Property::ControlPoint1: - return QVariant::fromValue(controlPoint_1); - case Property::ControlPoint2: - return QVariant::fromValue(controlPoint_2); - default: - return Edge::getProperty(property); - } -} - -auto ldraw::ConditionalEdge::setProperty( - Property property, - const QVariant& value) - -> SetPropertyResult -{ - switch (property) - { - case Property::ControlPoint1: - controlPoint_1 = value.value<glm::vec3>(); - case Property::ControlPoint2: - controlPoint_2 = value.value<glm::vec3>(); - default: - return Edge::setProperty(property, value); - } -} - QString ldraw::ConditionalEdge::textRepresentation() const { - return Edge::textRepresentation() + utility::format("%1 %2", - utility::vertexToStringParens(controlPoint_1), - utility::vertexToStringParens(controlPoint_2)); + return utility::format("%1 %2 %3 %4", + utility::vertexToStringParens(this->points[0]), + utility::vertexToStringParens(this->points[1]), + utility::vertexToStringParens(this->points[2]), + utility::vertexToStringParens(this->points[3])); }
--- a/src/linetypes/conditionaledge.h Mon Aug 24 22:55:37 2020 +0300 +++ b/src/linetypes/conditionaledge.h Mon Aug 24 23:00:50 2020 +0300 @@ -1,27 +1,14 @@ #pragma once -#include "edge.h" +#include "polygonobject.h" namespace ldraw { class ConditionalEdge; } -class ldraw::ConditionalEdge : public Edge +class ldraw::ConditionalEdge : public PolygonObject<4> { public: - ConditionalEdge() = default; - ConditionalEdge( - const glm::vec3& point_1, - const glm::vec3& point_2, - const glm::vec3& controlPoint_1, - const glm::vec3& controlPoint_2, - const Color colorIndex = ldraw::edgeColor); - ConditionalEdge(const std::array<glm::vec3, 4>& vertices, const Color color); - QVariant getProperty(Property property) const override; - SetPropertyResult setProperty( - Property property, - const QVariant& value) override; + using PolygonObject<4>::PolygonObject; QString textRepresentation() const override; - glm::vec3 controlPoint_1 = {}; - glm::vec3 controlPoint_2 = {}; };
--- a/src/linetypes/edge.cpp Mon Aug 24 22:55:37 2020 +0300 +++ b/src/linetypes/edge.cpp Mon Aug 24 23:00:50 2020 +0300 @@ -1,52 +1,11 @@ #include "edge.h" -ldraw::Edge::Edge( - const glm::vec3& point_1, - const glm::vec3& point_2, - const Color color_index) : - ColoredObject{color_index}, - point_1{point_1}, - point_2{point_2} {} - -ldraw::Edge::Edge(const std::array<glm::vec3, 2>& vertices, const Color color) : - ColoredObject{color}, - point_1{vertices[0]}, - point_2{vertices[1]} -{ -} - -QVariant ldraw::Edge::getProperty(Property property) const -{ - switch (property) - { - case Property::Point1: - return QVariant::fromValue(point_1); - case Property::Point2: - return QVariant::fromValue(point_2); - default: - return BaseClass::getProperty(property); - } -} - -auto ldraw::Edge::setProperty(Property property, const QVariant& value) - -> SetPropertyResult -{ - switch (property) - { - case Property::Point1: - point_1 = value.value<glm::vec3>(); - return SetPropertyResult::Success; - case Property::Point2: - point_2 = value.value<glm::vec3>(); - return SetPropertyResult::Success; - default: - return BaseClass::setProperty(property, value); - } -} - QString ldraw::Edge::textRepresentation() const { - return utility::format("%1 %2", utility::vertexToStringParens(point_1), utility::vertexToStringParens(point_2)); + return utility::format( + "%1 %2", + utility::vertexToStringParens(this->points[0]), + utility::vertexToStringParens(this->points[1])); } void ldraw::Edge::getPolygons( @@ -54,5 +13,5 @@ GetPolygonsContext* context) const { Q_UNUSED(context) - polygons.push_back(gl::edgeLine(this->point_1, this->point_2, this->colorIndex, this->id)); + polygons.push_back(gl::edgeLine(this->points[0], this->points[1], this->colorIndex, this->id)); }
--- a/src/linetypes/edge.h Mon Aug 24 22:55:37 2020 +0300 +++ b/src/linetypes/edge.h Mon Aug 24 23:00:50 2020 +0300 @@ -1,25 +1,15 @@ #pragma once -#include "object.h" +#include "polygonobject.h" namespace ldraw { class Edge; } -class ldraw::Edge : public ColoredObject +class ldraw::Edge : public PolygonObject<2> { public: - using BaseClass = ColoredObject; - Edge() = default; - Edge(const glm::vec3& point_1, const glm::vec3& point_2, - const Color colorIndex = ldraw::edgeColor); - Edge(const std::array<glm::vec3, 2>& vertices, const Color color); - QVariant getProperty(Property property) const override; - SetPropertyResult setProperty( - Property property, - const QVariant& value) override; + using PolygonObject::PolygonObject; QString textRepresentation() const override; void getPolygons(std::vector<gl::Polygon>& polygons, GetPolygonsContext* context) const override; - glm::vec3 point_1 = {}; - glm::vec3 point_2 = {}; };
--- a/src/linetypes/errorline.cpp Mon Aug 24 22:55:37 2020 +0300 +++ b/src/linetypes/errorline.cpp Mon Aug 24 23:00:50 2020 +0300 @@ -20,22 +20,11 @@ } } -auto ldraw::ErrorLine::setProperty( - Property property, - const QVariant& value) - -> SetPropertyResult +void ldraw::ErrorLine::setProperty(SetPropertyResult* result, const PropertyKeyValue& pair) { - switch (property) - { - case Property::Text: - this->text = value.toString(); - return SetPropertyResult::Success; - case Property::ErrorMessage: - this->message = value.toString(); - return SetPropertyResult::Success; - default: - return Object::setProperty(property, value); - } + LDRAW_OBJECT_HANDLE_SET_PROPERTY(Text, {this->text = value;}); + LDRAW_OBJECT_HANDLE_SET_PROPERTY(ErrorMessage, {this->message = value;}); + BaseClass::setProperty(result, pair); } QString ldraw::ErrorLine::textRepresentation() const
--- a/src/linetypes/errorline.h Mon Aug 24 22:55:37 2020 +0300 +++ b/src/linetypes/errorline.h Mon Aug 24 23:00:50 2020 +0300 @@ -9,14 +9,14 @@ class ldraw::ErrorLine : public Object { public: + using BaseClass = Object; ErrorLine(QStringView text = u"", QStringView message = u""); QVariant getProperty(Property property) const override; - SetPropertyResult setProperty( - Property property, - const QVariant& value) override; QString textRepresentation() const override; QBrush textRepresentationForeground() const override; QBrush textRepresentationBackground() const override; QString text; QString message; +protected: + void setProperty(SetPropertyResult* result, const PropertyKeyValue& pair) override; };
--- a/src/linetypes/metacommand.cpp Mon Aug 24 22:55:37 2020 +0300 +++ b/src/linetypes/metacommand.cpp Mon Aug 24 23:00:50 2020 +0300 @@ -15,20 +15,14 @@ } } -auto ldraw::MetaCommand::setProperty(Property property, const QVariant& value) - -> SetPropertyResult +void ldraw::MetaCommand::setProperty(ldraw::Object::SetPropertyResult* result, const PropertyKeyValue& pair) { - switch (property) - { - case Property::Text: - storedText = value.toString(); - return SetPropertyResult::Success; - default: - return Object::setProperty(property, value); - } + LDRAW_OBJECT_HANDLE_SET_PROPERTY(Text, {this->storedText = value;}); + BaseClass::setProperty(result, pair); } QString ldraw::MetaCommand::textRepresentation() const { return this->storedText; } +
--- a/src/linetypes/metacommand.h Mon Aug 24 22:55:37 2020 +0300 +++ b/src/linetypes/metacommand.h Mon Aug 24 23:00:50 2020 +0300 @@ -8,12 +8,12 @@ class ldraw::MetaCommand : public Object { public: + using BaseClass = Object; MetaCommand() = default; MetaCommand(QStringView text); QVariant getProperty(Property property) const override; - SetPropertyResult setProperty( - Property property, - const QVariant& value) override; QString textRepresentation() const override; QString storedText = ""; +protected: + void setProperty(SetPropertyResult* result, const PropertyKeyValue& pair) override; };
--- a/src/linetypes/object.cpp Mon Aug 24 22:55:37 2020 +0300 +++ b/src/linetypes/object.cpp Mon Aug 24 23:00:50 2020 +0300 @@ -1,6 +1,8 @@ #include <QBrush> #include <QFont> #include "object.h" +#include "widgets/vec3editor.h" +#include "modeleditcontext.h" static std::int32_t getIdForNewObject() { @@ -29,12 +31,20 @@ return {}; } -auto ldraw::Object::setProperty(Property id, const QVariant& value) - -> SetPropertyResult +void ldraw::Object::setProperty(SetPropertyResult* result, const PropertyKeyValue& pair) { - Q_UNUSED(id) - Q_UNUSED(value) - return SetPropertyResult::PropertyNotHandled; + Q_UNUSED(result) + Q_UNUSED(pair) +} + +/** + * @brief public interface to setProperty + */ +ldraw::Object::SetPropertyResult ldraw::Object::setProperty(const PropertyKeyValue& pair) +{ + SetPropertyResult result; + this->setProperty(&result, pair); + return result; } QBrush ldraw::Object::textRepresentationForeground() const @@ -58,6 +68,12 @@ Q_UNUSED(context) } +const glm::vec3& ldraw::Object::getPoint(int index) const +{ + Q_UNUSED(index); + throw BadPointIndex{}; +} + ldraw::ColoredObject::ColoredObject(const Color color_index) : colorIndex{color_index} { @@ -73,34 +89,16 @@ switch (id) { case Property::Color: - return colorIndex.index; + return QVariant::fromValue<Color>(colorIndex); default: return Object::getProperty(id); } } -auto ldraw::ColoredObject::setProperty(Property id, const QVariant& value) - -> SetPropertyResult +void ldraw::ColoredObject::setProperty(SetPropertyResult* result, const PropertyKeyValue& pair) { - switch (id) - { - case Property::Color: - { - bool ok; - const int value_int = value.toInt(&ok); - if (ok) - { - colorIndex.index = value_int; - return SetPropertyResult::Success; - } - else - { - return SetPropertyResult::InvalidValue; - } - } - default: - return Object::setProperty(id, value); - } + LDRAW_OBJECT_HANDLE_SET_PROPERTY(Color, {colorIndex = value;}); + Object::setProperty(result, pair); } QString ldraw::Empty::textRepresentation() const
--- a/src/linetypes/object.h Mon Aug 24 22:55:37 2020 +0300 +++ b/src/linetypes/object.h Mon Aug 24 23:00:50 2020 +0300 @@ -5,11 +5,13 @@ #include "main.h" #include "colors.h" #include "gl/common.h" +#include "linetypes/propertygenerics.h" + +class Model; namespace ldraw { struct GetPolygonsContext; - enum class Property; class Object; class ColoredObject; class Empty; @@ -23,88 +25,71 @@ ::DocumentManager* documents; }; -/** - * @brief Different properties that can be queried with getProperty - */ -enum class ldraw::Property -{ - Color, // Color of the object - Text, // Text contained in a comment - Point1, // First vertex in a polygon or edge line - Point2, // Second vertex in a polygon or edge line - Point3, // Third vertex in a polygon - Point4, // Fourth vertex in a quadrilateral - ControlPoint1, // First control point in a conditional edge line - ControlPoint2, // Second control point in a conditional edge line - Transformation, // 4x4 transformation matrix of a subfile reference - ReferenceName, // Subfile reference name - IsInverted, // Whether or not the object has been inverted with BFC INVERTNEXT - ErrorMessage // For error lines, why parsing failed -}; - -// Mapping of properties to types -#define LDFORGE_DEFINE_PROPERTY_TYPE(PROPERTY, TYPE) \ - namespace ldraw { \ - template<> struct PropertyType<ldraw::Property::PROPERTY> { using type = TYPE; }; \ - } - -namespace ldraw -{ - template<ldraw::Property property> - struct PropertyType - { - }; - - template<ldraw::Property property> - using PropertyType_t = typename PropertyType<property>::type; -} - -LDFORGE_DEFINE_PROPERTY_TYPE(Color, int) -LDFORGE_DEFINE_PROPERTY_TYPE(Text, QString) -LDFORGE_DEFINE_PROPERTY_TYPE(Point1, glm::vec3) -LDFORGE_DEFINE_PROPERTY_TYPE(Point2, glm::vec3) -LDFORGE_DEFINE_PROPERTY_TYPE(Point3, glm::vec3) -LDFORGE_DEFINE_PROPERTY_TYPE(Point4, glm::vec3) -LDFORGE_DEFINE_PROPERTY_TYPE(ControlPoint1, glm::vec3) -LDFORGE_DEFINE_PROPERTY_TYPE(ControlPoint2, glm::vec3) -LDFORGE_DEFINE_PROPERTY_TYPE(Transformation, glm::mat4) -LDFORGE_DEFINE_PROPERTY_TYPE(ReferenceName, QString) -LDFORGE_DEFINE_PROPERTY_TYPE(IsInverted, bool) -LDFORGE_DEFINE_PROPERTY_TYPE(ErrorMessage, QString) - class ldraw::Object { public: enum class SetPropertyResult { Success = 0, - PropertyNotHandled, - InvalidValue + PropertyNotHandled + }; + friend bool handled(SetPropertyResult result) + { + return result == SetPropertyResult::Success; + } + class BadPointIndex : public std::exception + { }; Object(); Object(const Object&) = delete; virtual ~Object(); const id_t id; - //virtual void toString(QTextStream &out) = 0; virtual bool hasColor() const; virtual QVariant getProperty(Property id) const; - virtual SetPropertyResult setProperty(Property id, const QVariant& value); + template<ldraw::Property property> + SetPropertyResult setProperty(const PropertyType<property>& value); + SetPropertyResult setProperty(const PropertyKeyValue& pair); virtual QString textRepresentation() const = 0; virtual QBrush textRepresentationForeground() const; virtual QBrush textRepresentationBackground() const; virtual QFont textRepresentationFont() const; virtual void getPolygons(std::vector<gl::Polygon>& polygons, GetPolygonsContext* context) const; virtual void invert() {} + virtual int numPoints() const { return 0; } + virtual const glm::vec3& getPoint(int index) const; +protected: + template<Property property, typename Function> + void handle(SetPropertyResult* result, const PropertyKeyValue& pair, Function function); + virtual void setProperty(SetPropertyResult* result, const PropertyKeyValue& pair); }; +template<ldraw::Property property> +ldraw::Object::SetPropertyResult ldraw::Object::setProperty(const ldraw::PropertyType<property>& value) +{ + SetPropertyResult result = SetPropertyResult::PropertyNotHandled; + this->setProperty(&result, PropertyKeyValue{property, QVariant::fromValue(value)}); + return result; +} + +template<ldraw::Property property, typename Function> +void ldraw::Object::handle(SetPropertyResult* result, const PropertyKeyValue& pair, Function function) +{ + if (pair.key == property) + { + function(pair.value.value<ldraw::PropertyType<property>>()); + *result = SetPropertyResult::Success; + } +} + class ldraw::ColoredObject : public Object { public: ColoredObject(const Color colorIndex = ldraw::mainColor); bool hasColor() const override final; QVariant getProperty(Property id) const override; - SetPropertyResult setProperty(Property id, const QVariant& value) override; Color colorIndex = ldraw::mainColor; +protected: + void setProperty(SetPropertyResult* result, const PropertyKeyValue& pair) override; }; /**
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/linetypes/polygonobject.h Mon Aug 24 23:00:50 2020 +0300 @@ -0,0 +1,70 @@ +#pragma once +#include "object.h" +#include "widgets/vec3editor.h" +#include "model.h" +#include "modeleditcontext.h" + +namespace ldraw +{ + template<int N, typename> + class PolygonObject; +} + +template<int N, typename = std::enable_if_t<(N > 0 and N <= 4)>> +class ldraw::PolygonObject : public ColoredObject +{ +public: + using BaseClass = ColoredObject; + PolygonObject(const std::array<glm::vec3, N>& points, const Color color) : + ColoredObject{color}, + points{points} {} + int numPoints() const override + { + return N; + } + const glm::vec3& getPoint(int index) const override + { + Q_ASSERT(index >= 0 and index < N); + return this->points[index]; + } + QVariant getProperty(const Property id) const override + { + switch (id) + { + case Property::Point0: + return QVariant::fromValue(points[0]); + case Property::Point1: + return QVariant::fromValue(points[1]); + case Property::Point2: + if (N >= 3) + { + return QVariant::fromValue(points[2]); + } + break; + case Property::Point3: + if (N >= 4) + { + return QVariant::fromValue(points[3]); + } + break; + default: + break; + } + return BaseClass::getProperty(id); + } + void setProperty(SetPropertyResult* result, const PropertyKeyValue& pair) + { + LDRAW_OBJECT_HANDLE_SET_PROPERTY(Point0, {points[0] = value;}) + LDRAW_OBJECT_HANDLE_SET_PROPERTY(Point1, {points[1] = value;}) + if constexpr (N >= 3) + { + LDRAW_OBJECT_HANDLE_SET_PROPERTY(Point2, {points[2] = value;}) + } + if constexpr (N >= 4) + { + LDRAW_OBJECT_HANDLE_SET_PROPERTY(Point3, {points[3] = value;}) + } + ColoredObject::setProperty(result, pair); + } + std::array<glm::vec3, N> points; +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/linetypes/propertygenerics.h Mon Aug 24 23:00:50 2020 +0300 @@ -0,0 +1,153 @@ +#pragma once +#include "main.h" +#include "colors.h" + +class Vec3Editor; +class MatrixEditor; + +namespace ldraw +{ + enum class Property; + struct PropertyKeyValue; + template<Property property> + struct PropertyTraits + { + static constexpr bool defined = false; + }; +} + +/** + * Different properties + */ +enum class ldraw::Property +{ + Color, // Color of the object + Text, // Text contained in a comment + Point0, // First vertex in a polygon or edge line + Point1, // Second vertex in a polygon or edge line + Point2, // Third vertex in a polygon + Point3, // Fourth vertex in a quadrilateral + Transformation, // 4x4 transformation matrix of a subfile reference + ReferenceName, // Subfile reference name + IsInverted, // Whether or not the object has been inverted with BFC INVERTNEXT + ErrorMessage // For error lines, why parsing failed +}; + +Q_DECLARE_METATYPE(ldraw::Property) + +// Mapping of properties to types +#define LDFORGE_DEFINE_PROPERTY_TYPE(PROPERTY, TYPE) \ + namespace ldraw \ + { \ + template<> struct PropertyTraits<ldraw::Property::PROPERTY> \ + { \ + using type = TYPE; \ + static constexpr std::array<char, 256> name{#PROPERTY}; \ + static constexpr bool defined = true; \ + }; \ + } + +LDFORGE_DEFINE_PROPERTY_TYPE(Color, ldraw::Color) +LDFORGE_DEFINE_PROPERTY_TYPE(Text, QString) +LDFORGE_DEFINE_PROPERTY_TYPE(Point0, glm::vec3) +LDFORGE_DEFINE_PROPERTY_TYPE(Point1, glm::vec3) +LDFORGE_DEFINE_PROPERTY_TYPE(Point2, glm::vec3) +LDFORGE_DEFINE_PROPERTY_TYPE(Point3, glm::vec3) +LDFORGE_DEFINE_PROPERTY_TYPE(Transformation, glm::mat4) +LDFORGE_DEFINE_PROPERTY_TYPE(ReferenceName, QString) +LDFORGE_DEFINE_PROPERTY_TYPE(IsInverted, bool) +LDFORGE_DEFINE_PROPERTY_TYPE(ErrorMessage, QString) + +#define LDRAW_OBJECT_HANDLE_SET_PROPERTY(PROPERTY, HANDLER) \ + {this->handle<ldraw::Property::PROPERTY>(result, pair, \ + [&](const ldraw::PropertyType<ldraw::Property::PROPERTY>& value) HANDLER);} + +// Generics +namespace ldraw +{ + template<ldraw::Property property> + using PropertyType = typename PropertyTraits<property>::type; + + template<ldraw::Property property> + inline const char* PROPERTY_NAME = PropertyTraits<property>::name; + + struct PropertyKeyValue + { + Property key; + QVariant value; + }; + + constexpr Property pointProperty(int n) + { + Q_ASSERT(n >= 0 and n < 4); + return static_cast<Property>(static_cast<int>(Property::Point0) + n); + } + + struct PropertyTrait + { + ldraw::Property property; + std::array<char, 256> name; + int type; + }; + + namespace detail + { + template<int N> + constexpr int propertyCountHelper() + { + if constexpr (ldraw::PropertyTraits<static_cast<Property>(N)>::defined) + { + return propertyCountHelper<N + 1>(); + } + else + { + return N; + } + } + + template<int k> + constexpr PropertyTrait getPropertyTrait() + { + constexpr auto property = static_cast<ldraw::Property>(k); + using trait = ldraw::PropertyTraits<property>; + return PropertyTrait{ + property, + trait::name, + qMetaTypeId<typename trait::type>() + }; + } + + template<int... Ints> + auto getPropertyTraits(std::integer_sequence<int, Ints...>) + { + return std::array<PropertyTrait, sizeof...(Ints)>{getPropertyTrait<Ints>()...}; + } + } + + constexpr int NUM_PROPERTIES = detail::propertyCountHelper<0>(); + inline const auto& traits() + { + static std::array<PropertyTrait, NUM_PROPERTIES> result = + detail::getPropertyTraits(std::make_integer_sequence<int, NUM_PROPERTIES>()); + return result; + } + + inline const auto& traits(ldraw::Property property) + { + return traits()[static_cast<int>(property)]; + } + + template<typename T, std::size_t... Ints> + constexpr auto makeIndexArray(std::index_sequence<Ints...>) + { + return std::array{static_cast<T>(Ints)...}; + } + + constexpr auto ALL_PROPERTIES = makeIndexArray<Property>(std::make_index_sequence<NUM_PROPERTIES>{}); + + template<typename T> + bool testPropertyType(ldraw::Property property) + { + return qMetaTypeId<T>() == ldraw::traits(property).type; + } +}
--- a/src/linetypes/quadrilateral.cpp Mon Aug 24 22:55:37 2020 +0300 +++ b/src/linetypes/quadrilateral.cpp Mon Aug 24 23:00:50 2020 +0300 @@ -1,70 +1,12 @@ #include "quadrilateral.h" -ldraw::Quadrilateral::Quadrilateral( - const glm::vec3& point_1, - const glm::vec3& point_2, - const glm::vec3& point_3, - const glm::vec3& point_4, - Color color_index) : - ColoredObject{color_index}, - points{point_1, point_2, point_3, point_4} -{ -} - -ldraw::Quadrilateral::Quadrilateral(const std::array<glm::vec3, 4>& vertices, const Color color) : - ColoredObject{color}, - points{vertices[0], vertices[1], vertices[2], vertices[3]} -{ -} - -QVariant ldraw::Quadrilateral::getProperty(const Property id) const -{ - switch (id) - { - case Property::Point1: - return QVariant::fromValue(points[0]); - case Property::Point2: - return QVariant::fromValue(points[1]); - case Property::Point3: - return QVariant::fromValue(points[2]); - case Property::Point4: - return QVariant::fromValue(points[3]); - default: - return ColoredObject::getProperty(id); - } -} - -auto ldraw::Quadrilateral::setProperty( - const Property id, - const QVariant& value) - -> SetPropertyResult -{ - switch (id) - { - case Property::Point1: - points[0] = value.value<glm::vec3>(); - return SetPropertyResult::Success; - case Property::Point2: - points[1] = value.value<glm::vec3>(); - return SetPropertyResult::Success; - case Property::Point3: - points[2] = value.value<glm::vec3>(); - return SetPropertyResult::Success; - case Property::Point4: - points[3] = value.value<glm::vec3>(); - return SetPropertyResult::Success; - default: - return ColoredObject::setProperty(id, value); - } -} - QString ldraw::Quadrilateral::textRepresentation() const { return utility::format("%1 %2 %3 %4", - utility::vertexToStringParens(points[0]), - utility::vertexToStringParens(points[1]), - utility::vertexToStringParens(points[2]), - utility::vertexToStringParens(points[3])); + utility::vertexToStringParens(this->points[0]), + utility::vertexToStringParens(this->points[1]), + utility::vertexToStringParens(this->points[2]), + utility::vertexToStringParens(this->points[3])); } void ldraw::Quadrilateral::getPolygons( @@ -78,7 +20,7 @@ this->points[2], this->points[3], this->colorIndex, - this->id)); + this->id)); } void ldraw::Quadrilateral::invert()
--- a/src/linetypes/quadrilateral.h Mon Aug 24 22:55:37 2020 +0300 +++ b/src/linetypes/quadrilateral.h Mon Aug 24 23:00:50 2020 +0300 @@ -1,26 +1,16 @@ #pragma once -#include "object.h" +#include "polygonobject.h" namespace ldraw { class Quadrilateral; } -class ldraw::Quadrilateral : public ColoredObject +class ldraw::Quadrilateral : public PolygonObject<4> { public: - Quadrilateral() = default; - Quadrilateral( - const glm::vec3 &point_1, - const glm::vec3 &point_2, - const glm::vec3 &point_3, - const glm::vec3 &point_4, - Color colorIndex = ldraw::mainColor); - Quadrilateral(const std::array<glm::vec3, 4>& vertices, const Color color); - QVariant getProperty(Property id) const override; - SetPropertyResult setProperty(Property id, const QVariant& value) override; + using PolygonObject<4>::PolygonObject; QString textRepresentation() const override; void getPolygons(std::vector<gl::Polygon>& polygons, GetPolygonsContext* context) const override; void invert() override; - glm::vec3 points[4] = {{}}; };
--- a/src/linetypes/subfilereference.cpp Mon Aug 24 22:55:37 2020 +0300 +++ b/src/linetypes/subfilereference.cpp Mon Aug 24 23:00:50 2020 +0300 @@ -2,9 +2,12 @@ #include "documentmanager.h" #include "invert.h" -ldraw::SubfileReference::SubfileReference(const glm::mat4& transformation, +ldraw::SubfileReference::SubfileReference +( + const glm::mat4& transformation, const QString& referenceName, - const Color color) : + const Color color +) : ColoredObject{color}, transformation{transformation}, referenceName{referenceName} @@ -24,22 +27,11 @@ } } -auto ldraw::SubfileReference::setProperty( - Property property, - const QVariant& value) - -> SetPropertyResult +void ldraw::SubfileReference::setProperty(SetPropertyResult* result, const PropertyKeyValue& pair) { - switch (property) - { - case Property::Transformation: - this->transformation = value.value<glm::mat4>(); - return SetPropertyResult::Success; - case Property::ReferenceName: - this->referenceName = value.toString(); - return SetPropertyResult::Success; - default: - return ColoredObject::setProperty(property, value); - } + LDRAW_OBJECT_HANDLE_SET_PROPERTY(Transformation, {this->transformation = value;}); + LDRAW_OBJECT_HANDLE_SET_PROPERTY(ReferenceName, {this->referenceName = value;}); + ldraw::ColoredObject::setProperty(result, pair); } QString ldraw::SubfileReference::textRepresentation() const @@ -47,9 +39,11 @@ return referenceName + " " + utility::vertexToStringParens(this->position()); } -void ldraw::SubfileReference::getPolygons( +void ldraw::SubfileReference::getPolygons +( std::vector<gl::Polygon>& polygons, - GetPolygonsContext* context) const + GetPolygonsContext* context +) const { Model* model = this->resolve(context->documents); if (model != nullptr) @@ -81,7 +75,7 @@ glm::vec3 ldraw::SubfileReference::position() const { - return {this->transformation[3][0], this->transformation[3][1], this->transformation[3][2]}; + return this->transformation[3]; } void ldraw::SubfileReference::invert()
--- a/src/linetypes/subfilereference.h Mon Aug 24 22:55:37 2020 +0300 +++ b/src/linetypes/subfilereference.h Mon Aug 24 23:00:50 2020 +0300 @@ -17,7 +17,6 @@ const QString &referenceName, const Color color = ldraw::mainColor); QVariant getProperty(Property property) const override; - SetPropertyResult setProperty(Property property, const QVariant& value) override; QString textRepresentation() const override; void getPolygons(std::vector<gl::Polygon>& polygons, GetPolygonsContext* context) const override; glm::vec3 position() const; @@ -26,4 +25,6 @@ glm::mat4 transformation; QString referenceName; bool isInverted = false; +protected: + void setProperty(SetPropertyResult* result, const PropertyKeyValue& pair) override; };
--- a/src/linetypes/triangle.cpp Mon Aug 24 22:55:37 2020 +0300 +++ b/src/linetypes/triangle.cpp Mon Aug 24 23:00:50 2020 +0300 @@ -1,61 +1,5 @@ #include "triangle.h" -ldraw::Triangle::Triangle( - const glm::vec3& point_1, - const glm::vec3& point_2, - const glm::vec3& point_3, - Color color_index) : - ColoredObject{color_index}, - points{point_1, point_2, point_3} -{ -} - -ldraw::Triangle::Triangle(const std::array<glm::vec3, 3>& vertices, const Color color) : - ColoredObject{color}, - points{vertices[0], vertices[1], vertices[2]} -{ -} - -ldraw::Triangle::Triangle(const glm::vec3 (&vertices)[3], const Color color) : - ColoredObject{color}, - points{vertices[0], vertices[1], vertices[2]} -{ -} - -QVariant ldraw::Triangle::getProperty(const Property id) const -{ - switch (id) - { - case Property::Point1: - return QVariant::fromValue(points[0]); - case Property::Point2: - return QVariant::fromValue(points[1]); - case Property::Point3: - return QVariant::fromValue(points[2]); - default: - return ColoredObject::getProperty(id); - } -} - -auto ldraw::Triangle::setProperty(Property id, const QVariant& value) - -> SetPropertyResult -{ - switch (id) - { - case Property::Point1: - points[0] = value.value<glm::vec3>(); - return SetPropertyResult::Success; - case Property::Point2: - points[1] = value.value<glm::vec3>(); - return SetPropertyResult::Success; - case Property::Point3: - points[2] = value.value<glm::vec3>(); - return SetPropertyResult::Success; - default: - return ColoredObject::setProperty(id, value); - } -} - QString ldraw::Triangle::textRepresentation() const { return utility::format("%1 %2 %3", @@ -74,7 +18,7 @@ this->points[1], this->points[2], this->colorIndex, - this->id)); + this->id)); } void ldraw::Triangle::invert()
--- a/src/linetypes/triangle.h Mon Aug 24 22:55:37 2020 +0300 +++ b/src/linetypes/triangle.h Mon Aug 24 23:00:50 2020 +0300 @@ -1,27 +1,17 @@ #pragma once -#include "object.h" +#include "polygonobject.h" namespace ldraw { class Triangle; } -class ldraw::Triangle : public ColoredObject +class ldraw::Triangle : public PolygonObject<3> { public: - Triangle() = default; - Triangle( - const glm::vec3 &point_1, - const glm::vec3 &point_2, - const glm::vec3 &point_3, - Color colorIndex = ldraw::mainColor); - Triangle(const std::array<glm::vec3, 3>& vertices, const Color color); - Triangle(const glm::vec3 (&vertices)[3], const Color color); - QVariant getProperty(Property id) const override; - SetPropertyResult setProperty(Property id, const QVariant& value) override; + using PolygonObject<3>::PolygonObject; QString textRepresentation() const override; void getPolygons(std::vector<gl::Polygon>& polygons, GetPolygonsContext* context) const override; void invert() override; - glm::vec3 points[3] = {{}}; };
--- a/src/main.cpp Mon Aug 24 22:55:37 2020 +0300 +++ b/src/main.cpp Mon Aug 24 23:00:50 2020 +0300 @@ -19,6 +19,8 @@ #include <QApplication> #include "mainwindow.h" #include "version.h" +#include <iostream> +#include <QMessageBox> int main(int argc, char *argv[]) { @@ -28,6 +30,9 @@ ::qRegisterMetaTypeStreamOperators<Library>("Library"); ::qRegisterMetaTypeStreamOperators<Libraries>("Libraries"); QApplication app{argc, argv}; + /* + QMessageBox::information(nullptr, "", QMetaType::typeName( qMetaTypeId<ldraw::Color>() )); + */ MainWindow mainwindow; mainwindow.show(); return app.exec();
--- a/src/main.h Mon Aug 24 22:55:37 2020 +0300 +++ b/src/main.h Mon Aug 24 23:00:50 2020 +0300 @@ -74,13 +74,14 @@ using conditionaledgeid_t = Id<class ConditionalEdge>; using subfileid_t = Id<class SubfileReference>; - constexpr struct + constexpr struct NullId { template<typename T> constexpr operator Id<T>() const { return Id<T>{0}; } + static constexpr decltype(ldraw::id_t::value) value = 0; } NULL_ID = {}; template<typename T>
--- a/src/mainwindow.cpp Mon Aug 24 22:55:37 2020 +0300 +++ b/src/mainwindow.cpp Mon Aug 24 23:00:50 2020 +0300 @@ -43,6 +43,7 @@ { offsetof(Ui_MainWindow, actionRenderStyleNormal), gl::RenderStyle::Normal }, { offsetof(Ui_MainWindow, actionRenderStyleBfc), gl::RenderStyle::BfcRedGreen }, { offsetof(Ui_MainWindow, actionRenderStyleRandom), gl::RenderStyle::RandomColors }, + { offsetof(Ui_MainWindow, actionRenderStylePickScene), gl::RenderStyle::PickScene }, }; class A : public QSettings
--- a/src/mainwindow.ui Mon Aug 24 22:55:37 2020 +0300 +++ b/src/mainwindow.ui Mon Aug 24 23:00:50 2020 +0300 @@ -26,7 +26,7 @@ <x>0</x> <y>0</y> <width>800</width> - <height>22</height> + <height>24</height> </rect> </property> <widget class="QMenu" name="menuFile"> @@ -53,6 +53,7 @@ <addaction name="actionRenderStyleNormal"/> <addaction name="actionRenderStyleBfc"/> <addaction name="actionRenderStyleRandom"/> + <addaction name="actionRenderStylePickScene"/> </widget> <addaction name="menuFile"/> <addaction name="menuView"/> @@ -108,6 +109,14 @@ <string>Random colours</string> </property> </action> + <action name="actionRenderStylePickScene"> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="text"> + <string>Pick scene colours</string> + </property> + </action> </widget> <resources/> <connections/>
--- a/src/model.cpp Mon Aug 24 22:55:37 2020 +0300 +++ b/src/model.cpp Mon Aug 24 23:00:50 2020 +0300 @@ -22,7 +22,10 @@ #include "modeleditcontext.h" Model::Model(QObject* parent) : - QAbstractListModel{parent} {} + QAbstractListModel{parent} +{ + connect(this, &Model::dataChanged, [&](){ this->needRecache = true; }); +} int Model::size() const {
--- a/src/model.h Mon Aug 24 22:55:37 2020 +0300 +++ b/src/model.h Mon Aug 24 23:00:50 2020 +0300 @@ -135,5 +135,12 @@ { *index_out = index; } - return static_cast<const R*>(this->objectAt(index)); + if (index.isValid()) + { + return static_cast<const R*>(this->objectAt(index)); + } + else + { + return nullptr; + } }
--- a/src/modeleditcontext.cpp Mon Aug 24 22:55:37 2020 +0300 +++ b/src/modeleditcontext.cpp Mon Aug 24 23:00:50 2020 +0300 @@ -17,12 +17,23 @@ */ #include "modeleditcontext.h" +#include "linetypes/triangle.h" +#include "linetypes/quadrilateral.h" Model::EditContext::EditContext(Model& model) : storedModel{model} { } +Model::EditContext::~EditContext() +{ + for (ldraw::id_t id : this->modifiedObjects) + { + const QModelIndex index = this->model().lookup(id); + emit this->model().dataChanged(index, index); + } +} + ldraw::id_t Model::EditContext::append(std::unique_ptr<ldraw::Object>&& object) { const ldraw::id_t id = object->id; @@ -36,15 +47,32 @@ this->model().remove(position); } -void Model::EditContext::setObjectProperty( - ldraw::id_t id, - ldraw::Property property, +auto Model::EditContext::setObjectProperty( + const ldraw::id_t id, + const ldraw::Property property, const QVariant& value) + -> ldraw::Object::SetPropertyResult +{ + ldraw::Object* const object = this->model().objectAt(id); + if (object != nullptr) + { + const ldraw::Object::SetPropertyResult result = object->setProperty(ldraw::PropertyKeyValue{property, value}); + modifiedObjects.insert(id); + return result; + } + else + { + return ldraw::Object::SetPropertyResult::PropertyNotHandled; + } +} + +void Model::EditContext::setObjectPoint(ldraw::id_t id, int pointId, const glm::vec3& value) { ldraw::Object* object = this->model().objectAt(id); if (object != nullptr) { - object->setProperty(property, value); + object->setProperty(ldraw::PropertyKeyValue{ldraw::pointProperty(pointId), QVariant::fromValue(value)}); + modifiedObjects.insert(id); } } @@ -63,7 +91,7 @@ return this->storedModel; } -static std::array<geom::Triangle, 2> splitTriangles(ldraw::Diagonal diagonal, const glm::vec3(&points)[4]) +static std::array<geom::Triangle, 2> splitTriangles(ldraw::Diagonal diagonal, const std::array<glm::vec3, 4>& points) { std::array<geom::Triangle, 2> result; switch (diagonal)
--- a/src/modeleditcontext.h Mon Aug 24 22:55:37 2020 +0300 +++ b/src/modeleditcontext.h Mon Aug 24 23:00:50 2020 +0300 @@ -18,31 +18,42 @@ #pragma once #include "model.h" -#include "linetypes/object.h" -#include "linetypes/quadrilateral.h" -#include "linetypes/triangle.h" class Model::EditContext { public: + ~EditContext(); template<typename T, typename... Args> ldraw::Id<T> append(Args&&... args); ldraw::id_t append(std::unique_ptr<ldraw::Object>&& object); template<typename T, typename... Args> ldraw::Id<T> insert(int position, Args&&... args); void remove(int position); - void setObjectProperty( - ldraw::id_t object, - ldraw::Property property, - const QVariant &value); + template<ldraw::Property property> + void setObjectProperty(ldraw::id_t id, const ldraw::PropertyType<property>& value); + auto setObjectProperty(ldraw::id_t id, ldraw::Property property, const QVariant& value) + -> ldraw::Object::SetPropertyResult; + void setObjectPoint(ldraw::id_t id, int pointId, const glm::vec3& value); void invertObject(ldraw::id_t id); Model& model(); private: EditContext(Model& model); friend class Model; + QSet<ldraw::id_t> modifiedObjects; Model& storedModel; }; +template<ldraw::Property Property> +void Model::EditContext::setObjectProperty(const ldraw::id_t id, const ldraw::PropertyType<Property>& value) +{ + ldraw::Object* object = this->model().objectAt(id); + if (object != nullptr) + { + object->setProperty<Property>(value); + modifiedObjects.insert(id); + } +} + template<typename T, typename... Args> ldraw::Id<T> Model::EditContext::append(Args&&... args) {
--- a/src/ui/canvas.cpp Mon Aug 24 22:55:37 2020 +0300 +++ b/src/ui/canvas.cpp Mon Aug 24 23:00:50 2020 +0300 @@ -154,84 +154,87 @@ void Canvas::paintGL() { PartRenderer::paintGL(); - // Render axes - { - glLineWidth(5); - glEnable(GL_LINE_SMOOTH); - glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); - this->axesProgram->draw(); - glDisable(GL_LINE_SMOOTH); - } - // Render grid - { - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - this->gridProgram->draw(); - glDisable(GL_BLEND); - } - if (this->worldPosition.has_value()) + if (this->renderPreferences.style != gl::RenderStyle::PickScene) { - QPainter painter{this}; - painter.setRenderHint(QPainter::Antialiasing); - painter.setPen(Qt::black); - painter.setBrush(Qt::green); - const QPointF pos = this->modelToScreenCoordinates(*this->worldPosition); - painter.drawEllipse(pos, 5, 5); - painter.setPen(Qt::white); - painter.drawText(pos + QPointF{5, 5}, vectorToString(*this->worldPosition)); - } - { - QPainter painter{this}; - QFont font; - //font.setStyle(QFont::StyleItalic); - painter.setFont(font); - QFontMetrics fontMetrics{font}; - const auto renderText = [&](const QString& text, const geom::PointOnRectagle& intersection) + // Render axes + { + glLineWidth(5); + glEnable(GL_LINE_SMOOTH); + glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); + this->axesProgram->draw(); + glDisable(GL_LINE_SMOOTH); + } + // Render grid + { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + this->gridProgram->draw(); + glDisable(GL_BLEND); + } + if (this->worldPosition.has_value()) { - QPointF position = toQPointF(intersection.position); - const geom::RectangleSide side = intersection.side; - switch (side) + QPainter painter{this}; + painter.setRenderHint(QPainter::Antialiasing); + painter.setPen(Qt::black); + painter.setBrush(Qt::green); + const QPointF pos = this->modelToScreenCoordinates(*this->worldPosition); + painter.drawEllipse(pos, 5, 5); + painter.setPen(Qt::white); + painter.drawText(pos + QPointF{5, 5}, vectorToString(*this->worldPosition)); + } + { + QPainter painter{this}; + QFont font; + //font.setStyle(QFont::StyleItalic); + painter.setFont(font); + QFontMetrics fontMetrics{font}; + const auto renderText = [&](const QString& text, const geom::PointOnRectagle& intersection) { - case geom::RectangleSide::Top: - position += QPointF{0, static_cast<qreal>(fontMetrics.ascent())}; - break; - case geom::RectangleSide::Left: - break; - case geom::RectangleSide::Bottom: - position += QPointF{0, static_cast<qreal>(-fontMetrics.descent())}; - break; - case geom::RectangleSide::Right: - position += QPointF{static_cast<qreal>(-fontMetrics.width(text)), 0}; - break; - } - painter.drawText(position, text); - }; - const QRectF box { - QPointF{0, 0}, - QPointF{static_cast<qreal>(this->width()), static_cast<qreal>(this->height())} - }; - const QPointF p1 = this->modelToScreenCoordinates(glm::vec3{0, 0, 0}); + QPointF position = toQPointF(intersection.position); + const geom::RectangleSide side = intersection.side; + switch (side) + { + case geom::RectangleSide::Top: + position += QPointF{0, static_cast<qreal>(fontMetrics.ascent())}; + break; + case geom::RectangleSide::Left: + break; + case geom::RectangleSide::Bottom: + position += QPointF{0, static_cast<qreal>(-fontMetrics.descent())}; + break; + case geom::RectangleSide::Right: + position += QPointF{static_cast<qreal>(-fontMetrics.width(text)), 0}; + break; + } + painter.drawText(position, text); + }; + const QRectF box { + QPointF{0, 0}, + QPointF{static_cast<qreal>(this->width()), static_cast<qreal>(this->height())} + }; + const QPointF p1 = this->modelToScreenCoordinates(glm::vec3{0, 0, 0}); - static const struct - { - QString text; - glm::vec3 direction; - } directions[] = - { - {"+𝑥", {1, 0, 0}}, - {"-𝑥", {-1, 0, 0}}, - {"+𝑦", {0, 1, 0}}, - {"-𝑦", {0, -1, 0}}, - {"+𝑧", {0, 0, 1}}, - {"-𝑧", {0, 0, -1}}, - }; - for (const auto& axis : directions) - { - const QPointF x_p = this->modelToScreenCoordinates(axis.direction); - const auto intersection = geom::rayRectangleIntersection(geom::rayFromPoints(toVec2(p1), toVec2(x_p)), box); - if (intersection.has_value()) + static const struct + { + QString text; + glm::vec3 direction; + } directions[] = { - renderText(axis.text, *intersection); + {"+𝑥", {1, 0, 0}}, + {"-𝑥", {-1, 0, 0}}, + {"+𝑦", {0, 1, 0}}, + {"-𝑦", {0, -1, 0}}, + {"+𝑧", {0, 0, 1}}, + {"-𝑧", {0, 0, -1}}, + }; + for (const auto& axis : directions) + { + const QPointF x_p = this->modelToScreenCoordinates(axis.direction); + const auto intersection = geom::rayRectangleIntersection(geom::rayFromPoints(toVec2(p1), toVec2(x_p)), box); + if (intersection.has_value()) + { + renderText(axis.text, *intersection); + } } } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ui/multiplyfactordialog.cpp Mon Aug 24 23:00:50 2020 +0300 @@ -0,0 +1,94 @@ +#include "multiplyfactordialog.h" +#include "ui_multiplyfactordialog.h" + +MultiplyFactorDialog::MultiplyFactorDialog(const glm::vec3& baseVector, QWidget* parent) : + QDialog{parent}, + baseVector{baseVector}, + preview{baseVector, parent, Vec3Editor::NoMultiplyButton} +{ + ui = std::make_unique<Ui::MultiplyFactorDialog>(); + ui->setupUi(this); + this->preview.setEnabled(false); + this->ui->previewGroupBox->setLayout(new QVBoxLayout{parent}); + this->ui->previewGroupBox->layout()->addWidget(&this->preview); + connect(this->ui->invert, &QCheckBox::clicked, this, &MultiplyFactorDialog::updatePreview); + connect(this->ui->factor, qOverload<double>(&DoubleSpinBox::valueChanged), this, &MultiplyFactorDialog::updatePreview); +} + +/** + * @brief empty destructor, necessary because std::unique_ptr is used with a forward declaration + */ +MultiplyFactorDialog::~MultiplyFactorDialog() +{ +} + +/** + * @brief Computes the resulting vector + * @return the input vector multiplied by the specified vector + */ +glm::vec3 MultiplyFactorDialog::value() const +{ + glm::vec3 result = baseVector; + if (this->ui->invert->isChecked()) + { + if (qFuzzyIsNull(this->ui->factor->value())) + { + constexpr double infinity = std::numeric_limits<double>::quiet_NaN(); + result = {infinity, infinity, infinity}; + } + else + { + result /= this->ui->factor->value(); + } + } + else + { + result *= this->ui->factor->value(); + } + return result; +} + +/** + * @brief Makes a string that is prefixed to the factor input. + * @param ui + * @return prefix string + */ +QString prefixForFactorInput(const Ui::MultiplyFactorDialog& ui) +{ + if (ui.invert->isChecked()) + { + return "1 : "; + } + else + { + return ""; + } +} + +/** + * @brief Makes a string that is suffixed to the factor input. + * @param ui + * @return prefix string + */ +QString suffixForFactorInput(const Ui::MultiplyFactorDialog& ui) +{ + if (ui.invert->isChecked()) + { + // render the actual factor that stuff gets effectively multiplied by + return " = " + QString::number(1.0 / (ui.factor->value())); + } + else + { + return ""; + } +} + +/** + * @brief Responds to changes in the value and updates previews accordingly + */ +void MultiplyFactorDialog::updatePreview() +{ + this->ui->factor->setPrefix(::prefixForFactorInput(*this->ui)); + this->ui->factor->setSuffix(::suffixForFactorInput(*this->ui)); + this->preview.setValue(this->value()); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ui/multiplyfactordialog.h Mon Aug 24 23:00:50 2020 +0300 @@ -0,0 +1,23 @@ +#pragma once +#include <QDialog> +#include "../main.h" +#include "../widgets/vec3editor.h" + +namespace Ui +{ + class MultiplyFactorDialog; +} + +class MultiplyFactorDialog : public QDialog +{ + Q_OBJECT +public: + explicit MultiplyFactorDialog(const glm::vec3& baseVector = glm::vec3{}, QWidget *parent = nullptr); + ~MultiplyFactorDialog(); + glm::vec3 value() const; +private: + Q_SLOT void updatePreview(); + std::unique_ptr<Ui::MultiplyFactorDialog> ui; + const glm::vec3 baseVector; + Vec3Editor preview; +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ui/multiplyfactordialog.ui Mon Aug 24 23:00:50 2020 +0300 @@ -0,0 +1,103 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MultiplyFactorDialog</class> + <widget class="QDialog" name="MultiplyFactorDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>286</width> + <height>169</height> + </rect> + </property> + <property name="windowTitle"> + <string>Multiply with a scalar</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QFormLayout" name="formLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Factor:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="DoubleSpinBox" name="factor"> + <property name="value"> + <double>1.000000000000000</double> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QCheckBox" name="invert"> + <property name="text"> + <string>Invert</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QGroupBox" name="previewGroupBox"> + <property name="title"> + <string>Preview</string> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>DoubleSpinBox</class> + <extends>QDoubleSpinBox</extends> + <header>widgets/doublespinbox.h</header> + </customwidget> + </customwidgets> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>MultiplyFactorDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>MultiplyFactorDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ui/objecteditor.cpp Mon Aug 24 23:00:50 2020 +0300 @@ -0,0 +1,32 @@ +#include <QVBoxLayout> +#include "objecteditor.h" + +ObjectEditor::ObjectEditor(Model* model, const ldraw::id_t id, QWidget *parent) : + QWidget{parent}, + model{model} +{ + this->setObjectId(id); + this->setLayout(new QVBoxLayout{this}); +} + +void ObjectEditor::setObjectId(const ldraw::id_t id) +{ + this->objectId = id; + const ldraw::Object* object = this->model->get(id); + if (object != nullptr and object->numPoints() > 0) + { + if (not this->polygonEditor.has_value()) + { + this->polygonEditor.emplace(this->model, id); + this->layout()->addWidget(&*this->polygonEditor); + } + else + { + this->polygonEditor->setObjectId(id); + } + } + else + { + this->polygonEditor.reset(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ui/objecteditor.h Mon Aug 24 23:00:50 2020 +0300 @@ -0,0 +1,17 @@ +#pragma once +#include <QWidget> +#include "../main.h" +#include "../model.h" +#include "polygonobjecteditor.h" + +class ObjectEditor : public QWidget +{ + Q_OBJECT +public: + explicit ObjectEditor(Model* model, ldraw::id_t id = ldraw::NULL_ID, QWidget* parent = nullptr); + void setObjectId(ldraw::id_t id); +private: + Model* const model; + ldraw::id_t objectId = ldraw::NULL_ID; + std::optional<PolygonObjectEditor> polygonEditor; +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ui/polygonobjecteditor.cpp Mon Aug 24 23:00:50 2020 +0300 @@ -0,0 +1,77 @@ +#include <QFormLayout> +#include <QSplitter> +#include "model.h" +#include "modeleditcontext.h" +#include "widgets/vec3editor.h" +#include "ui/polygonobjecteditor.h" + +static constexpr char INDEX_NAME[] = "_ldforge_index"; +static constexpr char PROPERTY_NAME[] = "_ldforge_property"; +static constexpr char OBJECT_ID_NAME[] = "_ldforge_id"; +static constexpr char LABEL_NAME[] = "_ldforge_label"; + +PolygonObjectEditor::PolygonObjectEditor(Model* model, ldraw::id_t id, QWidget* parent) : + QWidget{parent}, + model{model}, + storedObjectId{ldraw::NULL_ID.value} +{ + this->splitter.emplace(Qt::Vertical, this); + this->setObjectId(id); +} + +// destructor needed for std::unique_ptr +PolygonObjectEditor::~PolygonObjectEditor() +{ +} + +ldraw::id_t PolygonObjectEditor::objectId() const +{ + return this->storedObjectId; +} + +void PolygonObjectEditor::setObjectId(ldraw::id_t id) +{ + this->storedObjectId = id; + this->buildWidgets(); +} + +void PolygonObjectEditor::buildWidgets() +{ + this->widgets.clear(); + delete this->layout(); + QFormLayout* layout = new QFormLayout{this}; + this->setLayout(layout); + for (int n : {0, 1, 2, 3}) + { + this->setupPointWidget(n); + } + for (std::unique_ptr<QWidget>& widget : this->widgets) + { + const QString label = widget->property(LABEL_NAME).toString(); + layout->addRow(label, widget.get()); + } + layout->addRow("", &*this->splitter); +} + +void PolygonObjectEditor::setupPointWidget(int n) +{ + const ldraw::Object* const object = this->model->get(this->objectId()); + const ldraw::Property property = ldraw::pointProperty(n); + const QVariant value = object->getProperty(property); + if (value.isValid()) + { + std::unique_ptr<Vec3Editor> editor = std::make_unique<Vec3Editor>(value.value<glm::vec3>(), this); + QObject::connect(editor.get(), &Vec3Editor::valueChanged, this, &PolygonObjectEditor::pointChanged); + editor->setProperty(INDEX_NAME, QVariant::fromValue(n)); + editor->setProperty(LABEL_NAME, &ldraw::traits(property).name[0]); + this->widgets.push_back(std::move(editor)); + } +} + +void PolygonObjectEditor::pointChanged(const glm::vec3& value) +{ + Model::EditContext editcontext = this->model->edit(); + const int n = this->sender()->property(INDEX_NAME).toInt(); + const ldraw::Property property = ldraw::pointProperty(n); + editcontext.setObjectProperty(this->objectId(), property, QVariant::fromValue(value)); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ui/polygonobjecteditor.h Mon Aug 24 23:00:50 2020 +0300 @@ -0,0 +1,23 @@ +#pragma once +#include <QWidget> +#include "main.h" +#include "../widgets/vec3editor.h" + +class Model; + +class PolygonObjectEditor : public QWidget +{ +public: + PolygonObjectEditor(Model* model, ldraw::id_t id, QWidget* parent = nullptr); + ~PolygonObjectEditor(); + ldraw::id_t objectId() const; + void setObjectId(ldraw::id_t id); +private: + void buildWidgets(); + void setupPointWidget(int n); + Q_SLOT void pointChanged(const glm::vec3& value); + Model* model; + ldraw::id_t storedObjectId; + std::vector<std::unique_ptr<QWidget>> widgets; + std::optional<class QSplitter> splitter; +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/widgets/doublespinbox.cpp Mon Aug 24 23:00:50 2020 +0300 @@ -0,0 +1,58 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2020 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "doublespinbox.h" + +/* + * Constructs a new double spin box. The locale is fixed to system default "C". + */ +DoubleSpinBox::DoubleSpinBox(QWidget* parent) : + QDoubleSpinBox {parent} +{ + this->setLocale({"C"}); + this->setRange(-1e6, 1e6); + this->setDecimals(4); +} + +/* + * Reimplementation of QDoubleSpinBox::textFromValue to remove trailing zeros. + */ +QString DoubleSpinBox::textFromValue(double value) const +{ + QString result = QDoubleSpinBox::textFromValue(value); + if (result.contains(".")) + { + // Remove trailing zeros + while (result.endsWith("0")) + result.chop(1); + // Remove trailing decimal point if we just removed all the zeros. + if (result.endsWith(".")) + result.chop(1); + } + return result; +} + +/* + * Reimplementation of QDoubleSpinBox::validate to fix the decimal point if the locale-specific + * decimal point was used. + */ +QValidator::State DoubleSpinBox::validate(QString& input, int& pos) const +{ + input.replace(QLocale().decimalPoint(), this->locale().decimalPoint()); + return QDoubleSpinBox::validate(input, pos); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/widgets/doublespinbox.h Mon Aug 24 23:00:50 2020 +0300 @@ -0,0 +1,34 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2020 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <QDoubleSpinBox> + +/* + * A version of QDoubleSpinBox that consistently uses "." as the decimal separator + * and does not display trailing zeros. + */ +class DoubleSpinBox : public QDoubleSpinBox +{ +public: + DoubleSpinBox(QWidget* parent = nullptr); +protected: + QString textFromValue(double value) const override; + QValidator::State validate(QString& input, int& pos) const override; +}; +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/widgets/matrixeditor.cpp Mon Aug 24 23:00:50 2020 +0300 @@ -0,0 +1,89 @@ +#include "main.h" +#include "matrixeditor.h" +#include "ui_matrixeditor.h" +#include "../ui/multiplyfactordialog.h" + +constexpr char BUTTON_COLUMN_PROPERTY[] = "_ldforge_column"; + +MatrixEditor::MatrixEditor(const glm::mat4 value, QWidget* parent) : + QWidget(parent), + ui(new Ui::MatrixEditor) +{ + ui->setupUi(this); + for (int column = 0; column < countof(this->spinboxes); column += 1) + { + for (int row = 0; row < countof(this->spinboxes[0]); row += 1) + { + const QString name = "cell"_q + QString::number(column) + QString::number(row); + QDoubleSpinBox** spinbox = &this->spinboxes[column][row]; + *spinbox = this->findChild<QDoubleSpinBox*>(name); + connect(*spinbox, qOverload<double>(&QDoubleSpinBox::valueChanged), [&]() + { + emit this->valueChanged(this->value()); + }); + Q_ASSERT(*spinbox != nullptr); + } + QAbstractButton* button = this->findChild<QAbstractButton*>("multiply"_q + QString::number(column)); + button->setProperty(BUTTON_COLUMN_PROPERTY, column); + connect(button, &QAbstractButton::clicked, this, &MatrixEditor::multiplyButtonPressed); + } + this->setValue(value); +} + +MatrixEditor::MatrixEditor(QWidget *parent) : + MatrixEditor{glm::mat4{1}, parent} +{ +} + +MatrixEditor::~MatrixEditor() +{ + delete ui; +} + +glm::mat4 MatrixEditor::value() const +{ + glm::mat4 result{1}; + for (int column = 0; column < countof(this->spinboxes); column += 1) + { + for (int row = 0; row < countof(this->spinboxes[0]); row += 1) + { + result[column][row] = this->spinboxes[column][row]->value(); + } + } + return result; +} + +void MatrixEditor::setValue(const glm::mat4& value) +{ + for (int column = 0; column < countof(this->spinboxes); column += 1) + { + for (int row = 0; row < countof(this->spinboxes[0]); row += 1) + { + QDoubleSpinBox* spinbox = this->spinboxes[column][row]; + QSignalBlocker blocker{spinbox}; + spinbox->setValue(value[column][row]); + } + } +} + +void MatrixEditor::multiplyButtonPressed() +{ + QAbstractButton* button = qobject_cast<QAbstractButton*>(this->sender()); + if (button != nullptr) + { + bool ok; + const int column = button->property(BUTTON_COLUMN_PROPERTY).toInt(&ok); + if (ok and column >= 0 and column < this->matrixSize()) + { + glm::mat4 newValue = this->value(); + MultiplyFactorDialog dialog{newValue[column], this}; + const int result = dialog.exec(); + if (result == QDialog::Accepted) + { + newValue[column] = glm::vec4{dialog.value(), (column == 3) ? 1 : 0}; + this->setValue(newValue); + emit valueChanged(newValue); + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/widgets/matrixeditor.h Mon Aug 24 23:00:50 2020 +0300 @@ -0,0 +1,30 @@ +#pragma once +#include <QWidget> +#include "main.h" + +namespace Ui { +class MatrixEditor; +} + +class MatrixEditor : public QWidget +{ + Q_OBJECT +public: + explicit MatrixEditor(QWidget *parent = nullptr); + explicit MatrixEditor(const glm::mat4 value, QWidget* parent = nullptr); + ~MatrixEditor(); + glm::mat4 value() const; + void setValue(const glm::mat4& value); +Q_SIGNALS: + void valueChanged(const glm::mat4& value); +private: + constexpr int matrixSize() const; + Q_SLOT void multiplyButtonPressed(); + class QDoubleSpinBox* spinboxes[4][3]; + Ui::MatrixEditor *ui; +}; + +constexpr int MatrixEditor::matrixSize() const +{ + return 4; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/widgets/matrixeditor.ui Mon Aug 24 23:00:50 2020 +0300 @@ -0,0 +1,123 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MatrixEditor</class> + <widget class="QWidget" name="MatrixEditor"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>356</width> + <height>172</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="DoubleSpinBox" name="cell00"/> + </item> + <item row="0" column="1"> + <widget class="DoubleSpinBox" name="cell10"/> + </item> + <item row="0" column="2"> + <widget class="DoubleSpinBox" name="cell20"/> + </item> + <item row="0" column="3"> + <widget class="DoubleSpinBox" name="cell30"/> + </item> + <item row="1" column="0"> + <widget class="DoubleSpinBox" name="cell01"/> + </item> + <item row="1" column="1"> + <widget class="DoubleSpinBox" name="cell11"/> + </item> + <item row="1" column="2"> + <widget class="DoubleSpinBox" name="cell21"/> + </item> + <item row="1" column="3"> + <widget class="DoubleSpinBox" name="cell31"/> + </item> + <item row="2" column="0"> + <widget class="DoubleSpinBox" name="cell02"/> + </item> + <item row="2" column="1"> + <widget class="DoubleSpinBox" name="cell12"/> + </item> + <item row="2" column="2"> + <widget class="DoubleSpinBox" name="cell22"/> + </item> + <item row="2" column="3"> + <widget class="DoubleSpinBox" name="cell32"/> + </item> + <item row="3" column="0"> + <widget class="DoubleSpinBox" name="doubleSpinBox"> + <property name="enabled"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="DoubleSpinBox" name="doubleSpinBox_2"> + <property name="enabled"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="3" column="2"> + <widget class="DoubleSpinBox" name="doubleSpinBox_3"> + <property name="enabled"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="3" column="3"> + <widget class="DoubleSpinBox" name="doubleSpinBox_4"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="value"> + <double>1.000000000000000</double> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QPushButton" name="multiply0"> + <property name="text"> + <string>×</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QPushButton" name="multiply1"> + <property name="text"> + <string>×</string> + </property> + </widget> + </item> + <item row="4" column="2"> + <widget class="QPushButton" name="multiply2"> + <property name="text"> + <string>×</string> + </property> + </widget> + </item> + <item row="4" column="3"> + <widget class="QPushButton" name="multiply3"> + <property name="text"> + <string>×</string> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>DoubleSpinBox</class> + <extends>QDoubleSpinBox</extends> + <header>widgets/doublespinbox.h</header> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/widgets/vec3editor.cpp Mon Aug 24 23:00:50 2020 +0300 @@ -0,0 +1,67 @@ +#include <QDialog> +#include <QCheckBox> +#include <QSignalBlocker> +#include "vec3editor.h" +#include "ui_vec3editor.h" +#include "../ui/multiplyfactordialog.h" + +Vec3Editor::Vec3Editor(const glm::vec3& value, QWidget *parent, QFlags<Flag> flags) : + QWidget{parent}, + ui{new Ui::Vec3Editor} +{ + this->ui->setupUi(this); + this->setValue(value); + if (flags.testFlag(NoMultiplyButton)) + { + this->ui->multiply->setVisible(false); + } + else + { + connect(this->ui->multiply, &QPushButton::clicked, this, &Vec3Editor::multiplyPressed); + } + for (QDoubleSpinBox* spinbox : this->spinboxes()) + { + connect(spinbox, qOverload<double>(&QDoubleSpinBox::valueChanged), [&](double) + { + Q_EMIT this->valueChanged(this->value()); + }); + } +} + +Vec3Editor::~Vec3Editor() +{ +} + +glm::vec3 Vec3Editor::value() const +{ + auto get = [](DoubleSpinBox* spinbox){ return toFloat(spinbox->value()); }; + return {get(this->ui->x), get(this->ui->y), get(this->ui->z)}; +} + +void Vec3Editor::setValue(const glm::vec3& value) +{ + auto set = [](DoubleSpinBox* spinbox, float value) + { + QSignalBlocker blocker{spinbox}; + spinbox->setValue(toQreal(value)); + }; + set(this->ui->x, value.x); + set(this->ui->y, value.y); + set(this->ui->z, value.z); + Q_EMIT this->valueChanged(value); +} + +std::array<DoubleSpinBox*, 3> Vec3Editor::spinboxes() +{ + return {this->ui->x, this->ui->y, this->ui->z}; +} + +void Vec3Editor::multiplyPressed() +{ + MultiplyFactorDialog dialog{this->value(), this}; + const int dialogResult = dialog.exec(); + if (dialogResult == QDialog::Accepted) + { + this->setValue(dialog.value()); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/widgets/vec3editor.h Mon Aug 24 23:00:50 2020 +0300 @@ -0,0 +1,30 @@ +#pragma once +#include <QWidget> +#include "main.h" + +namespace Ui +{ + class Vec3Editor; +} + +class Vec3Editor : public QWidget +{ + Q_OBJECT +public: + enum Flag + { + NoMultiplyButton = 0x1 + }; + explicit Vec3Editor(const glm::vec3& value, QWidget* parent = nullptr, QFlags<Flag> flags = 0); + ~Vec3Editor(); + glm::vec3 value() const; + void setValue(const glm::vec3& value); +Q_SIGNALS: + void valueChanged(const glm::vec3& value); +private: + std::array<class DoubleSpinBox*, 3> spinboxes(); + Q_SLOT void multiplyPressed(); + std::unique_ptr<Ui::Vec3Editor> ui; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QFlags<Vec3Editor::Flag>)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/widgets/vec3editor.ui Mon Aug 24 23:00:50 2020 +0300 @@ -0,0 +1,83 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Vec3Editor</class> + <widget class="QWidget" name="Vec3Editor"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>613</width> + <height>46</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout" stretch="1,1,1,0"> + <item> + <widget class="DoubleSpinBox" name="x"> + <property name="prefix"> + <string>x = </string> + </property> + <property name="decimals"> + <number>4</number> + </property> + <property name="minimum"> + <double>-1000000.000000000000000</double> + </property> + <property name="maximum"> + <double>1000000.000000000000000</double> + </property> + </widget> + </item> + <item> + <widget class="DoubleSpinBox" name="y"> + <property name="prefix"> + <string>y = </string> + </property> + <property name="decimals"> + <number>4</number> + </property> + <property name="minimum"> + <double>-1000000.000000000000000</double> + </property> + <property name="maximum"> + <double>1000000.000000000000000</double> + </property> + </widget> + </item> + <item> + <widget class="DoubleSpinBox" name="z"> + <property name="prefix"> + <string>z = </string> + </property> + <property name="decimals"> + <number>4</number> + </property> + <property name="minimum"> + <double>-1000000.000000000000000</double> + </property> + <property name="maximum"> + <double>1000000.000000000000000</double> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="multiply"> + <property name="text"> + <string>×</string> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>DoubleSpinBox</class> + <extends>QDoubleSpinBox</extends> + <header>widgets/doublespinbox.h</header> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui>