diff -r c72e3115a297 -r 67c6e5d32e68 src/model.cpp --- a/src/model.cpp Sun Jan 29 15:49:36 2017 +0200 +++ b/src/model.cpp Sun Jan 29 21:02:11 2017 +0200 @@ -1,7 +1,9 @@ #include "model.h" #include "ldObject.h" +#include "documentmanager.h" -Model::Model() {} +Model::Model(DocumentManager* manager) : + _manager {manager} {} Model::~Model() { @@ -197,3 +199,262 @@ { return _objects.isEmpty(); } + +DocumentManager* Model::documentManager() const +{ + return _manager; +} + +// ============================================================================= +// +static void CheckTokenCount (const QStringList& tokens, int num) +{ + if (countof(tokens) != num) + throw QString (format ("Bad amount of tokens, expected %1, got %2", num, countof(tokens))); +} + +// ============================================================================= +// +static void CheckTokenNumbers (const QStringList& tokens, int min, int max) +{ + bool ok; + QRegExp scientificRegex ("\\-?[0-9]+\\.[0-9]+e\\-[0-9]+"); + + for (int i = min; i <= max; ++i) + { + // Check for floating point + tokens[i].toDouble (&ok); + if (ok) + return; + + // Check hex + if (tokens[i].startsWith ("0x")) + { + tokens[i].mid (2).toInt (&ok, 16); + + if (ok) + return; + } + + // Check scientific notation, e.g. 7.99361e-15 + if (scientificRegex.exactMatch (tokens[i])) + return; + + throw QString (format ("Token #%1 was `%2`, expected a number (matched length: %3)", + (i + 1), tokens[i], scientificRegex.matchedLength())); + } +} + +// ============================================================================= +// +static Vertex ParseVertex (QStringList& s, const int n) +{ + Vertex v; + v.apply ([&] (Axis ax, double& a) { a = s[n + ax].toDouble(); }); + return v; +} + +static int32 StringToNumber (QString a, bool* ok = nullptr) +{ + int base = 10; + + if (a.startsWith ("0x")) + { + a.remove (0, 2); + base = 16; + } + + return a.toLong (ok, base); +} + +// ============================================================================= +// This is the LDraw code parser function. It takes in a string containing LDraw +// code and returns the object parsed from it. parseLine never returns null, +// the object will be LDError if it could not be parsed properly. +// ============================================================================= +LDObject* Model::insertFromString(int position, QString line) +{ + try + { + QStringList tokens = line.split(" ", QString::SkipEmptyParts); + + if (countof(tokens) <= 0) + { + // Line was empty, or only consisted of whitespace + return emplaceAt(position); + } + + if (countof(tokens[0]) != 1 or not tokens[0][0].isDigit()) + throw QString ("Illogical line code"); + + int num = tokens[0][0].digitValue(); + + switch (num) + { + case 0: + { + // Comment + QString commentText = line.mid (line.indexOf ("0") + 2); + QString commentTextSimplified = commentText.simplified(); + + // Handle BFC statements + if (countof(tokens) > 2 and tokens[1] == "BFC") + { + for (BfcStatement statement : iterateEnum()) + { + if (commentTextSimplified == format("BFC %1", LDBfc::statementToString (statement))) + return emplaceAt(position, statement); + } + + // MLCAD is notorious for stuffing these statements in parts it + // creates. The above block only handles valid statements, so we + // need to handle MLCAD-style invertnext, clip and noclip separately. + if (commentTextSimplified == "BFC CERTIFY INVERTNEXT") + return emplaceAt(position, BfcStatement::InvertNext); + else if (commentTextSimplified == "BFC CERTIFY CLIP") + return emplaceAt(position, BfcStatement::Clip); + else if (commentTextSimplified == "BFC CERTIFY NOCLIP") + return emplaceAt(position, BfcStatement::NoClip); + } + + if (countof(tokens) > 2 and tokens[1] == "!LDFORGE") + { + // Handle LDForge-specific types, they're embedded into comments too + if (tokens[2] == "OVERLAY") + { + CheckTokenCount (tokens, 9); + CheckTokenNumbers (tokens, 5, 8); + + LDOverlay* obj = emplaceAt(position); + obj->setFileName (tokens[3]); + obj->setCamera (tokens[4].toLong()); + obj->setX (tokens[5].toLong()); + obj->setY (tokens[6].toLong()); + obj->setWidth (tokens[7].toLong()); + obj->setHeight (tokens[8].toLong()); + return obj; + } + else if (tokens[2] == "BEZIER_CURVE") + { + CheckTokenCount (tokens, 16); + CheckTokenNumbers (tokens, 3, 15); + LDBezierCurve* obj = emplaceAt(position); + obj->setColor (StringToNumber (tokens[3])); + + for (int i = 0; i < 4; ++i) + obj->setVertex (i, ParseVertex (tokens, 4 + (i * 3))); + + return obj; + } + } + + // Just a regular comment: + return emplaceAt(position, commentText); + } + + case 1: + { + // Subfile + CheckTokenCount (tokens, 15); + CheckTokenNumbers (tokens, 1, 13); + LDDocument* document = _manager->getDocumentByName(tokens[14]); + + // If we cannot open the file, mark it an error. Note we cannot use LDParseError + // here because the error object needs the document reference. + if (not document) + { + LDError* obj = emplaceAt(position, line, format ("Could not open %1", tokens[14])); + obj->setFileReferenced (tokens[14]); + return obj; + } + + Vertex referncePosition = ParseVertex (tokens, 2); // 2 - 4 + Matrix transform; + + for (int i = 0; i < 9; ++i) + transform.value(i) = tokens[i + 5].toDouble(); // 5 - 13 + + LDSubfileReference* obj = emplaceAt(position, document, transform, referncePosition); + obj->setColor (StringToNumber (tokens[1])); + return obj; + } + + case 2: + { + CheckTokenCount (tokens, 8); + CheckTokenNumbers (tokens, 1, 7); + + // Line + LDLine* obj = emplaceAt(position); + obj->setColor (StringToNumber (tokens[1])); + + for (int i = 0; i < 2; ++i) + obj->setVertex (i, ParseVertex (tokens, 2 + (i * 3))); // 2 - 7 + + return obj; + } + + case 3: + { + CheckTokenCount (tokens, 11); + CheckTokenNumbers (tokens, 1, 10); + + // Triangle + LDTriangle* obj = emplaceAt(position); + obj->setColor (StringToNumber (tokens[1])); + + for (int i = 0; i < 3; ++i) + obj->setVertex (i, ParseVertex (tokens, 2 + (i * 3))); // 2 - 10 + + return obj; + } + + case 4: + case 5: + { + CheckTokenCount (tokens, 14); + CheckTokenNumbers (tokens, 1, 13); + + // Quadrilateral / Conditional line + LDObject* obj; + + if (num == 4) + obj = emplaceAt(position); + else + obj = emplaceAt(position); + + obj->setColor (StringToNumber (tokens[1])); + + for (int i = 0; i < 4; ++i) + obj->setVertex (i, ParseVertex (tokens, 2 + (i * 3))); // 2 - 13 + + return obj; + } + + default: + throw QString {"Unknown line code number"}; + } + } + catch (QString& errorMessage) + { + // Strange line we couldn't parse + return emplaceAt(position, line, errorMessage); + } +} + +LDObject* Model::addFromString(QString line) +{ + return insertFromString(size(), line); +} + +LDObject* Model::replaceWithFromString(LDObject* object, QString line) +{ + if (object and object->model() == this) + { + int position = object->lineNumber(); + removeAt(position); + return insertFromString(position, line); + } + else + return nullptr; +}