Tue, 15 Mar 2022 19:48:07 +0200
Added line path tool
/* * 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 "model.h" #include "parser.h" #include "linetypes/conditionaledge.h" #include "linetypes/edge.h" #include "linetypes/errorline.h" #include "linetypes/metacommand.h" #include "linetypes/object.h" #include "linetypes/quadrilateral.h" #include "linetypes/subfilereference.h" #include "linetypes/triangle.h" struct BodyParseError { QString message; }; /* * Constructs an LDraw parser */ Parser::Parser(QIODevice& device, QObject* parent) : QObject {parent}, device {device} {} /* * Reads a single line from the device. */ QString Parser::readLine() { return QString::fromUtf8(this->device.readLine()).trimmed(); } /** * @brief Parses the model body into the given model. * @param editor Handle to model edit context */ void Parser::parseBody(Model& model) { bool invertNext = false; while (not this->device.atEnd()) { // Some LDraw parts such as 53588.dat can contain "BFC INVERTNEXT" with multiple inner whitespaces. // So we need to pass the string through QString::simplified to catch these cases. const QString line = this->readLine().simplified(); if (line == "0 BFC INVERTNEXT" or line == "0 BFC CERTIFY INVERTNEXT") { invertNext = true; continue; } model.append(parseFromString(line)); if (invertNext) { model[model.size() - 1]->invert(nullptr); } invertNext = false; } } static ldraw::Color colorFromString(const QString& colorString) { bool colorSucceeded; const ldraw::Color color = {colorString.toInt(&colorSucceeded)}; if (colorSucceeded) { return color; } else { throw BodyParseError{"colour was not an integer value"}; } } static glm::vec3 vertexFromStrings( const QStringList& tokens, const int startingPosition) { bool ok_x; const float x = tokens[startingPosition].toFloat(&ok_x); bool ok_y; const float y = tokens[startingPosition + 1].toFloat(&ok_y); bool ok_z; const float z = tokens[startingPosition + 2].toFloat(&ok_z); if (not ok_x or not ok_y or not ok_z) { throw BodyParseError{"vertex contained illegal co-ordinates"}; } return {x, y, z}; } static glm::mat4 matrixFromStrings( const QStringList& tokens, const int startingPosition, const int positionStartingIndex) { glm::mat4 result = glm::mat4{1}; for (int i = 0; i < 9; i += 1) { const int row = i / 3; const int column = i % 3; const int index = i + startingPosition; if (index >= tokens.size()) { throw BodyParseError{"too few tokens available"}; } bool ok; // note that glm::mat4 is column-major result[column][row] = tokens[index].toFloat(&ok); if (not ok) { throw BodyParseError{"non-numeric values for matrix"}; } } for (int i = 0; i < 3; i += 1) { bool ok; const auto value = tokens[i + positionStartingIndex].toFloat(&ok); result[3][i] = value; if (not ok) { throw BodyParseError{"non-numeric values for matrix"}; } } return result; } static std::unique_ptr<ldraw::Object> parseType0Line( const QString& line, const QStringList& tokens) { Q_UNUSED(tokens) return std::make_unique<ldraw::MetaCommand>(line.mid(1).trimmed()); } static std::unique_ptr<ldraw::SubfileReference> parseType1Line( const QString& line, const QStringList& tokens) { Q_UNUSED(line) constexpr int colorPosition = 1; constexpr int positionPosition = 2; // 2..4 constexpr int transformPosition = 5; // 5..13 constexpr int namePosition = 14; if (tokens.size() != 15) { throw BodyParseError{"wrong amount of tokens in a type-1 line"}; } const ldraw::Color color = colorFromString(tokens[colorPosition]); const glm::mat4 transform = matrixFromStrings(tokens, transformPosition, positionPosition); const QString& name = tokens[namePosition]; return std::make_unique<ldraw::SubfileReference>(transform, name, color); } template<typename T, int NumVertices> static std::unique_ptr<T> parsePolygon( const QString& line, const QStringList& tokens) { Q_UNUSED(line) constexpr int colorPosition = 1; auto vertexPosition = [](int n) { return 2 + 3*n; }; if (tokens.size() != 2 + 3 * NumVertices) { throw BodyParseError{"wrong amount of tokens"}; } const ldraw::Color color = colorFromString(tokens[colorPosition]); std::array<glm::vec3, NumVertices> vertices; for (int i = 0; i < NumVertices; i += 1) { vertices[unsigned_cast(i)] = vertexFromStrings(tokens, vertexPosition(i)); } return std::make_unique<T>(vertices, color); } std::unique_ptr<ldraw::Object> Parser::parseFromString(QString line) { line = line.trimmed(); try { const QStringList tokens = line.split(QRegExp{R"(\s+)"}); if (tokens.empty() or tokens == QStringList{{""}}) { return std::make_unique<ldraw::Empty>(); } bool ok_code; const int code = tokens[0].toInt(&ok_code); if (not ok_code) { throw BodyParseError{"line type was not an integer"}; } switch (code) { case 0: return parseType0Line(line, tokens); case 1: return parseType1Line(line, tokens); case 2: return parsePolygon<ldraw::Edge, 2>(line, tokens); case 3: return parsePolygon<ldraw::Triangle, 3>(line, tokens); case 4: return parsePolygon<ldraw::Quadrilateral, 4>(line, tokens); case 5: return parsePolygon<ldraw::ConditionalEdge, 4>(line, tokens); default: throw BodyParseError{utility::format("bad line type '%1'", code)}; } } catch(const BodyParseError& error) { return std::make_unique<ldraw::ErrorLine>(line, error.message); } }