More rework on model stuff, removals of LDSpawn calls. Most importantly, the LDraw code parsing function was moved to Model.

2017-01-29

author
Teemu Piippo <teemu@hecknology.net>
date
Sun, 29 Jan 2017 21:02:11 +0200 (2017-01-29)
changeset 1079
67c6e5d32e68
parent 1078
c72e3115a297
child 1080
6dac2d52bd9a

More rework on model stuff, removals of LDSpawn calls. Most importantly, the LDraw code parsing function was moved to Model.

src/basics.cpp file | annotate | diff | comparison | revisions
src/basics.h file | annotate | diff | comparison | revisions
src/documentloader.cpp file | annotate | diff | comparison | revisions
src/documentmanager.cpp file | annotate | diff | comparison | revisions
src/editHistory.cpp file | annotate | diff | comparison | revisions
src/editmodes/circleMode.cpp file | annotate | diff | comparison | revisions
src/editmodes/curvemode.cpp file | annotate | diff | comparison | revisions
src/editmodes/drawMode.cpp file | annotate | diff | comparison | revisions
src/editmodes/linePathMode.cpp file | annotate | diff | comparison | revisions
src/editmodes/rectangleMode.cpp file | annotate | diff | comparison | revisions
src/glRenderer.cpp file | annotate | diff | comparison | revisions
src/ldDocument.cpp file | annotate | diff | comparison | revisions
src/ldObject.cpp file | annotate | diff | comparison | revisions
src/ldObject.h file | annotate | diff | comparison | revisions
src/mathfunctions.cpp file | annotate | diff | comparison | revisions
src/model.cpp file | annotate | diff | comparison | revisions
src/model.h file | annotate | diff | comparison | revisions
src/toolsets/algorithmtoolset.cpp file | annotate | diff | comparison | revisions
src/toolsets/basictoolset.cpp file | annotate | diff | comparison | revisions
src/toolsets/extprogramtoolset.cpp file | annotate | diff | comparison | revisions
src/toolsets/filetoolset.cpp file | annotate | diff | comparison | revisions
--- a/src/basics.cpp	Sun Jan 29 15:49:36 2017 +0200
+++ b/src/basics.cpp	Sun Jan 29 21:02:11 2017 +0200
@@ -138,35 +138,6 @@
 
 // =============================================================================
 //
-void BoundingBox::calcObject (LDObject* obj)
-{
-	switch (obj->type())
-	{
-	case OBJ_Line:
-	case OBJ_Triangle:
-	case OBJ_Quad:
-	case OBJ_CondLine:
-		for (int i = 0; i < obj->numVertices(); ++i)
-			calcVertex (obj->vertex (i));
-		break;
-
-	case OBJ_SubfileReference:
-	    {
-		    Model model;
-			static_cast<LDSubfileReference*>(obj)->inlineContents(model, true, false);
-
-			for (LDObject* it : model.objects())
-				calcObject(it);
-	    }
-		break;
-
-	default:
-		break;
-	}
-}
-
-// =============================================================================
-//
 BoundingBox& BoundingBox::operator<< (const Vertex& v)
 {
 	calcVertex (v);
@@ -175,14 +146,6 @@
 
 // =============================================================================
 //
-BoundingBox& BoundingBox::operator<< (LDObject* obj)
-{
-	calcObject (obj);
-	return *this;
-}
-
-// =============================================================================
-//
 void BoundingBox::calcVertex (const Vertex& vertex)
 {
 	m_vertex0.setX (qMin (vertex.x(), m_vertex0.x()));
--- a/src/basics.h	Sun Jan 29 15:49:36 2017 +0200
+++ b/src/basics.h	Sun Jan 29 21:02:11 2017 +0200
@@ -107,7 +107,6 @@
 public:
 	BoundingBox();
 
-	void calcObject (LDObject* obj);
 	void calcVertex (const Vertex& vertex);
 	Vertex center() const;
 	bool isEmpty() const;
@@ -116,7 +115,6 @@
 	const Vertex& vertex0() const;
 	const Vertex& vertex1() const;
 
-	BoundingBox& operator<< (LDObject* obj);
 	BoundingBox& operator<< (const Vertex& v);
 
 private:
--- a/src/documentloader.cpp	Sun Jan 29 15:49:36 2017 +0200
+++ b/src/documentloader.cpp	Sun Jan 29 21:02:11 2017 +0200
@@ -114,7 +114,7 @@
 		while (line.endsWith ("\n") or line.endsWith ("\r"))
 			line.chop (1);
 
-		LDObject* obj = ParseLine (line);
+		LDObject* obj = _model->addFromString(line);
 
 		// Check for parse errors and warn about them
 		if (obj->type() == OBJ_Error)
@@ -122,8 +122,6 @@
 			print ("Couldn't parse line #%1: %2", progress() + 1, static_cast<LDError*> (obj)->reason());
 			++m_warningCount;
 		}
-
-		_model->addObject(obj);
 	}
 
 	m_progress = i;
--- a/src/documentmanager.cpp	Sun Jan 29 15:49:36 2017 +0200
+++ b/src/documentmanager.cpp	Sun Jan 29 21:02:11 2017 +0200
@@ -329,7 +329,7 @@
 
 	int numWarnings;
 	bool ok;
-	Model model;
+	Model model {this};
 	loadFileContents(fp, model, &numWarnings, &ok);
 	load->merge(model);
 	fp->close();
--- a/src/editHistory.cpp	Sun Jan 29 15:49:36 2017 +0200
+++ b/src/editHistory.cpp	Sun Jan 29 21:02:11 2017 +0200
@@ -171,7 +171,7 @@
 
 void AddHistoryEntry::redo() const
 {
-	parent()->document()->insertObject (m_index, ParseLine (m_code));
+	parent()->document()->insertFromString(m_index, m_code);
 }
 
 //
@@ -203,15 +203,13 @@
 void EditHistoryEntry::undo() const
 {
 	LDObject* object = parent()->document()->getObject (m_index);
-	LDObject* newObject = ParseLine (m_oldCode);
-	parent()->document()->replace(object, newObject);
+	parent()->document()->replaceWithFromString(object, m_oldCode);
 }
 
 void EditHistoryEntry::redo() const
 {
 	LDObject* object = parent()->document()->getObject (m_index);
-	LDObject* newObject = ParseLine (m_newCode);
-	parent()->document()->replace(object, newObject);
+	parent()->document()->replaceWithFromString(object, m_newCode);
 }
 
 //
--- a/src/editmodes/circleMode.cpp	Sun Jan 29 15:49:36 2017 +0200
+++ b/src/editmodes/circleMode.cpp	Sun Jan 29 21:02:11 2017 +0200
@@ -86,7 +86,7 @@
 
 void CircleMode::endDraw()
 {
-	Model model;
+	Model model {m_documents};
 	PrimitiveModel primitiveModel;
 	primitiveModel.segments = m_window->ringToolSegments();
 	primitiveModel.divisions = m_window->ringToolHiRes() ? HighResolution : LowResolution;
--- a/src/editmodes/curvemode.cpp	Sun Jan 29 15:49:36 2017 +0200
+++ b/src/editmodes/curvemode.cpp	Sun Jan 29 21:02:11 2017 +0200
@@ -86,7 +86,7 @@
 {
 	if (countof(m_drawedVerts) == 4)
 	{
-		Model model;
+		Model model {m_documents};
 		model.emplace<LDBezierCurve>(m_drawedVerts[0], m_drawedVerts[1], m_drawedVerts[2], m_drawedVerts[3]);
 		finishDraw(model);
 	}
--- a/src/editmodes/drawMode.cpp	Sun Jan 29 15:49:36 2017 +0200
+++ b/src/editmodes/drawMode.cpp	Sun Jan 29 21:02:11 2017 +0200
@@ -65,16 +65,14 @@
 {
 	// Clean the selection and create the object
 	QList<Vertex>& verts = m_drawedVerts;
-	Model model;
+	Model model {m_documents};
 
 	switch (countof(verts))
 	{
-		case 2:
-		{
+	    case 2:
 			// 2 verts - make a line
-		    LDLine* obj = model.emplace<LDLine>(verts[0], verts[1]);
-			break;
-		}
+		    model.emplace<LDLine>(verts[0], verts[1]);
+		    break;
 
 		case 3:
 		case 4:
--- a/src/editmodes/linePathMode.cpp	Sun Jan 29 15:49:36 2017 +0200
+++ b/src/editmodes/linePathMode.cpp	Sun Jan 29 21:02:11 2017 +0200
@@ -80,10 +80,10 @@
 
 void LinePathMode::endDraw()
 {
-	Model model;
+	Model model {m_documents};
 
 	for (int i = 0; i < countof(m_drawedVerts) - 1; ++i)
-		LDLine* line = model.emplace<LDLine>(m_drawedVerts[i], m_drawedVerts[i + 1]);
+		model.emplace<LDLine>(m_drawedVerts[i], m_drawedVerts[i + 1]);
 
 	finishDraw(model);
 }
--- a/src/editmodes/rectangleMode.cpp	Sun Jan 29 15:49:36 2017 +0200
+++ b/src/editmodes/rectangleMode.cpp	Sun Jan 29 21:02:11 2017 +0200
@@ -41,7 +41,7 @@
 {
 	if (countof(m_drawedVerts) == 2)
 	{
-		Model model;
+		Model model {m_documents};
 		LDQuad* quad = model.emplace<LDQuad>();
 		updateRectVerts();
 
--- a/src/glRenderer.cpp	Sun Jan 29 15:49:36 2017 +0200
+++ b/src/glRenderer.cpp	Sun Jan 29 21:02:11 2017 +0200
@@ -1438,68 +1438,71 @@
 			continue;
 
 		LDGLOverlay& meta = currentDocumentData().overlays[camera];
-		LDOverlay* ovlobj = findOverlayObject (camera);
+		LDOverlay* overlayObject = findOverlayObject (camera);
 
-		if (meta.image == nullptr and ovlobj)
+		if (meta.image == nullptr and overlayObject)
 		{
 			// If this is the last overlay image, we need to remove the empty space after it as well.
-			LDObject* nextobj = ovlobj->next();
+			LDObject* nextobj = overlayObject->next();
 
 			if (nextobj and nextobj->type() == OBJ_Empty)
 				document()->remove(nextobj);
 
 			// If the overlay object was there and the overlay itself is
 			// not, remove the object.
-			document()->remove(ovlobj);
+			document()->remove(overlayObject);
+			overlayObject = nullptr;
 		}
-		else if (meta.image and ovlobj == nullptr)
+		else if (meta.image and overlayObject == nullptr)
 		{
 			// Inverse case: image is there but the overlay object is
 			// not, thus create the object.
-			ovlobj = LDSpawn<LDOverlay>();
-
+			//
 			// Find a suitable position to place this object. We want to place
 			// this into the header, which is everything up to the first scemantic
 			// object. If we find another overlay object, place this object after
 			// the last one found. Otherwise, place it before the first schemantic
 			// object and put an empty object after it (though don't do this if
 			// there was no schemantic elements at all)
-			int i, lastOverlay = -1;
+			int i;
+			int lastOverlayPosition = -1;
 			bool found = false;
 
 			for (i = 0; i < document()->getObjectCount(); ++i)
 			{
-				LDObject* obj = document()->getObject (i);
+				LDObject* object = document()->getObject (i);
 
-				if (obj->isScemantic())
+				if (object->isScemantic())
 				{
 					found = true;
 					break;
 				}
 
-				if (obj->type() == OBJ_Overlay)
-					lastOverlay = i;
+				if (object->type() == OBJ_Overlay)
+					lastOverlayPosition = i;
 			}
 
-			if (lastOverlay != -1)
-				document()->insertObject (lastOverlay + 1, ovlobj);
+			if (lastOverlayPosition != -1)
+			{
+				overlayObject = document()->emplaceAt<LDOverlay>(lastOverlayPosition + 1);
+			}
 			else
 			{
-				document()->insertObject (i, ovlobj);
+				overlayObject = document()->emplaceAt<LDOverlay>(i);
 
 				if (found)
 					document()->emplaceAt<LDEmpty>(i + 1);
 			}
 		}
 
-		if (meta.image and ovlobj)
+		if (meta.image and overlayObject)
 		{
-			ovlobj->setCamera (camera);
-			ovlobj->setFileName (meta.fileName);
-			ovlobj->setX (meta.offsetX);
-			ovlobj->setY (meta.offsetY);
-			ovlobj->setWidth (meta.width);
-			ovlobj->setHeight (meta.height);
+			overlayObject->setCamera (camera);
+			overlayObject->setFileName (meta.fileName);
+			overlayObject->setX (meta.offsetX);
+			overlayObject->setY (meta.offsetY);
+			overlayObject->setWidth (meta.width);
+			overlayObject->setHeight (meta.height);
 		}
 	}
 
--- a/src/ldDocument.cpp	Sun Jan 29 15:49:36 2017 +0200
+++ b/src/ldDocument.cpp	Sun Jan 29 21:02:11 2017 +0200
@@ -36,6 +36,7 @@
 
 LDDocument::LDDocument (DocumentManager* parent) :
 	QObject (parent),
+    Model {parent},
 	HierarchyElement (parent),
 	m_history (new EditHistory (this)),
 	m_flags(IsCache | VerticesOutdated | NeedsVertexMerge | NeedsRecache),
@@ -291,248 +292,6 @@
 
 // =============================================================================
 //
-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* ParseLine (QString line)
-{
-	try
-	{
-		QStringList tokens = line.split (" ", QString::SkipEmptyParts);
-
-		if (countof(tokens) <= 0)
-		{
-			// Line was empty, or only consisted of whitespace
-			return LDSpawn<LDEmpty>();
-		}
-
-		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<BfcStatement>())
-					{
-						if (commentTextSimplified == format("BFC %1", LDBfc::statementToString (statement)))
-							return LDSpawn<LDBfc>(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 LDSpawn<LDBfc> (BfcStatement::InvertNext);
-					else if (commentTextSimplified == "BFC CERTIFY CLIP")
-						return LDSpawn<LDBfc> (BfcStatement::Clip);
-					else if (commentTextSimplified == "BFC CERTIFY NOCLIP")
-						return LDSpawn<LDBfc> (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 = LDSpawn<LDOverlay>();
-						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 = LDSpawn<LDBezierCurve>();
-						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:
-				LDComment* obj = LDSpawn<LDComment>();
-				obj->setText (commentText);
-				return obj;
-			}
-
-			case 1:
-			{
-				// Subfile
-				CheckTokenCount (tokens, 15);
-				CheckTokenNumbers (tokens, 1, 13);
-				LDDocument* document = g_win->documents()->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 = LDSpawn<LDError> (line, format ("Could not open %1", tokens[14]));
-					obj->setFileReferenced (tokens[14]);
-					return obj;
-				}
-
-				LDSubfileReference* obj = LDSpawn<LDSubfileReference>();
-				obj->setColor (StringToNumber (tokens[1]));
-				obj->setPosition (ParseVertex (tokens, 2));  // 2 - 4
-
-				Matrix transform;
-
-				for (int i = 0; i < 9; ++i)
-					transform.value(i) = tokens[i + 5].toDouble(); // 5 - 13
-
-				obj->setTransformationMatrix (transform);
-				obj->setFileInfo (document);
-				return obj;
-			}
-
-			case 2:
-			{
-				CheckTokenCount (tokens, 8);
-				CheckTokenNumbers (tokens, 1, 7);
-
-				// Line
-				LDLine* obj (LDSpawn<LDLine>());
-				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 (LDSpawn<LDTriangle>());
-				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 = LDSpawn<LDQuad>();
-				else
-					obj = LDSpawn<LDCondLine>();
-
-				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& e)
-	{
-		// Strange line we couldn't parse
-		return LDSpawn<LDError> (line, e);
-	}
-}
-
-// =============================================================================
-//
 void LDDocument::reloadAllSubfiles()
 {
 	print ("Reloading subfiles of %1", getDisplayName());
@@ -554,7 +313,7 @@
 		// Reparse gibberish files. It could be that they are invalid because
 		// of loading errors. Circumstances may be different now.
 		if (obj->type() == OBJ_Error)
-			obj->replace (ParseLine (static_cast<LDError*> (obj)->contents()));
+			replaceWithFromString(obj, static_cast<LDError*> (obj)->contents());
 	}
 
 	m_flags |= NeedsRecache;
@@ -660,7 +419,7 @@
 	if (checkFlag(NeedsRecache))
 	{
 		m_vertices.clear();
-		Model model;
+		Model model {m_documents};
 		inlineContents(model, true, true);
 
 		for (LDObject* obj : model.objects())
@@ -688,7 +447,7 @@
 	if (checkFlag(VerticesOutdated))
 	{
 		m_objectVertices.clear();
-		Model model;
+		Model model {m_documents};
 		inlineContents(model, true, false);
 
 		for (LDObject* obj : model)
@@ -740,7 +499,7 @@
 		if (deep and object->type() == OBJ_SubfileReference)
 			static_cast<LDSubfileReference*>(object)->inlineContents(model, deep, renderinline);
 		else
-			model.addObject(object->createCopy());
+			model.addFromString(object->asText());
 	}
 }
 
--- a/src/ldObject.cpp	Sun Jan 29 15:49:36 2017 +0200
+++ b/src/ldObject.cpp	Sun Jan 29 21:02:11 2017 +0200
@@ -347,7 +347,7 @@
 // -----------------------------------------------------------------------------
 void LDSubfileReference::inlineContents(Model& model, bool deep, bool render)
 {
-	Model inlined;
+	Model inlined {this->model()->documentManager()};
 	fileInfo()->inlineContents(inlined, deep, render);
 
 	// Transform the objects
@@ -545,7 +545,7 @@
 
 	// Check whether subfile is flat
 	int axisSet = (1 << X) | (1 << Y) | (1 << Z);
-	Model model;
+	Model model {this->model()->documentManager()};
 	fileInfo()->inlineContents(model, true, false);
 
 	for (LDObject* obj : model.objects())
@@ -960,14 +960,6 @@
 	return result;
 }
 
-// =============================================================================
-//
-LDObject* LDObject::createCopy() const
-{
-	LDObject* copy = ParseLine (asText());
-	return copy;
-}
-
 LDSubfileReference::LDSubfileReference(LDDocument* reference, const Matrix& transformationMatrix,
                                        const Vertex& position, Model* model) :
     LDMatrixObject {transformationMatrix, position, model},
--- a/src/ldObject.h	Sun Jan 29 15:49:36 2017 +0200
+++ b/src/ldObject.h	Sun Jan 29 21:02:11 2017 +0200
@@ -98,8 +98,7 @@
     LDObject (Model* model = nullptr);
 
 	virtual QString asText() const = 0; // This object as LDraw code
-	LDColor color() const;
-	LDObject* createCopy() const;
+    LDColor color() const;
 	virtual LDColor defaultColor() const = 0; // What color does the object default to?
 	Model* model() const;
 	LDPolygon* getPolygon();
--- a/src/mathfunctions.cpp	Sun Jan 29 15:49:36 2017 +0200
+++ b/src/mathfunctions.cpp	Sun Jan 29 21:02:11 2017 +0200
@@ -95,9 +95,14 @@
 			for (LDObject* obj : objs)
 			{
 				if (obj->hasMatrix())
+				{
 					box << static_cast<LDMatrixObject*> (obj)->position();
+				}
 				else
-					box << obj;
+				{
+					for (int i = 0; i < obj->numVertices(); ++i)
+						box << obj->vertex(i);
+				}
 			}
 
 			return box.center();
--- 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<LDEmpty>(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<BfcStatement>())
+					{
+						if (commentTextSimplified == format("BFC %1", LDBfc::statementToString (statement)))
+							return emplaceAt<LDBfc>(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<LDBfc>(position, BfcStatement::InvertNext);
+					else if (commentTextSimplified == "BFC CERTIFY CLIP")
+						return emplaceAt<LDBfc>(position, BfcStatement::Clip);
+					else if (commentTextSimplified == "BFC CERTIFY NOCLIP")
+						return emplaceAt<LDBfc>(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<LDOverlay>(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<LDBezierCurve>(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<LDComment>(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<LDError>(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<LDSubfileReference>(position, document, transform, referncePosition);
+				obj->setColor (StringToNumber (tokens[1]));
+				return obj;
+		    }
+
+		    case 2:
+		    {
+			    CheckTokenCount (tokens, 8);
+				CheckTokenNumbers (tokens, 1, 7);
+
+				// Line
+				LDLine* obj = emplaceAt<LDLine>(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<LDTriangle>(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<LDQuad>(position);
+				else
+					obj = emplaceAt<LDCondLine>(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<LDError>(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;
+}
--- a/src/model.h	Sun Jan 29 15:49:36 2017 +0200
+++ b/src/model.h	Sun Jan 29 21:02:11 2017 +0200
@@ -5,7 +5,7 @@
 class Model
 {
 public:
-	Model();
+	Model(DocumentManager* manager);
 	Model(const Model& other) = delete;
 	~Model();
 
@@ -27,6 +27,10 @@
 	QVector<LDObject*>::iterator begin();
 	QVector<LDObject*>::iterator end();
 	bool isEmpty() const;
+	DocumentManager* documentManager() const;
+	LDObject* insertFromString(int position, QString line);
+	LDObject* addFromString(QString line);
+	LDObject* replaceWithFromString(LDObject* object, QString line);
 
 	template<typename T, typename... Args>
 	T* emplace(Args&& ...args)
@@ -85,6 +89,7 @@
 	virtual LDObject* withdrawAt(int position);
 
 	QVector<LDObject*> _objects;
+	DocumentManager* _manager;
 	mutable int _triangleCount = 0;
 	mutable bool _needsTriangleRecount;
 };
--- a/src/toolsets/algorithmtoolset.cpp	Sun Jan 29 15:49:36 2017 +0200
+++ b/src/toolsets/algorithmtoolset.cpp	Sun Jan 29 21:02:11 2017 +0200
@@ -110,8 +110,7 @@
 		return;
 
 	// Reinterpret it from the text of the input field
-	LDObject* newobj = ParseLine (ui.code->text());
-	obj->replace (newobj);
+	currentDocument()->replaceWithFromString(obj, ui.code->text());
 }
 
 void AlgorithmToolset::makeBorders()
@@ -125,7 +124,7 @@
 		if (type != OBJ_Quad and type != OBJ_Triangle)
 			continue;
 
-		Model lines;
+		Model lines {m_documents};
 
 		if (type == OBJ_Quad)
 		{
@@ -395,7 +394,7 @@
 		if (not isOneOf (obj->type(), OBJ_Line, OBJ_CondLine))
 			continue;
 
-		Model segments;
+		Model segments {m_documents};
 
 		for (int i = 0; i < numSegments; ++i)
 		{
@@ -536,7 +535,7 @@
 	subfile->setFullPath(fullsubname);
 	subfile->setName(LDDocument::shortenName(fullsubname));
 
-	Model header;
+	Model header {m_documents};
 	header.emplace<LDComment>(subtitle);
 	header.emplace<LDComment>("Name: "); // This gets filled in when the subfile is saved
 	header.emplace<LDComment>(format("Author: %1 [%2]", m_config->defaultName(), m_config->defaultUser()));
@@ -552,7 +551,7 @@
 
 	// Copy the body over to the new document
 	for (LDObject* object : selectedObjects())
-		subfile->addObject(object->createCopy());
+		subfile->addFromString(object->asText());
 
 	// Try save it
 	if (m_window->save(subfile, true))
--- a/src/toolsets/basictoolset.cpp	Sun Jan 29 15:49:36 2017 +0200
+++ b/src/toolsets/basictoolset.cpp	Sun Jan 29 21:02:11 2017 +0200
@@ -72,17 +72,16 @@
 	const QString clipboardText = qApp->clipboard()->text();
 	int idx = m_window->suggestInsertPoint();
 	currentDocument()->clearSelection();
-	int num = 0;
+	int count = 0;
 
-	for (QString line : clipboardText.split ("\n"))
+	for (QString line : clipboardText.split("\n"))
 	{
-		LDObject* pasted = ParseLine (line);
-		currentDocument()->insertObject (idx++, pasted);
+		LDObject* pasted = currentDocument()->insertFromString(idx++, line);
 		currentDocument()->addToSelection(pasted);
-		++num;
+		++count;
 	}
 
-	print (tr ("%1 objects pasted"), num);
+	print(tr("%1 objects pasted"), count);
 	m_window->refresh();
 	m_window->scrollToSelection();
 }
@@ -103,7 +102,7 @@
 
 		if (idx != -1)
 		{
-			Model inlined;
+			Model inlined {m_documents};
 			reference->inlineContents(inlined, deep, false);
 
 			// Merge in the inlined objects
@@ -120,7 +119,7 @@
 
 	for (LDBezierCurve* curve : filterByType<LDBezierCurve> (selectedObjects()))
 	{
-		Model curveModel;
+		Model curveModel {m_documents};
 		curve->rasterize(curveModel, grid()->bezierCurveSegments());
 		currentDocument()->replace(curve, curveModel);
 	}
@@ -164,7 +163,7 @@
 
 void BasicToolset::insertRaw()
 {
-	int idx = m_window->suggestInsertPoint();
+	int position = m_window->suggestInsertPoint();
 
 	QDialog* const dlg = new QDialog;
 	QVBoxLayout* const layout = new QVBoxLayout;
@@ -185,11 +184,9 @@
 
 	for (QString line : QString (inputbox->toPlainText()).split ("\n"))
 	{
-		LDObject* obj = ParseLine (line);
-
-		currentDocument()->insertObject (idx, obj);
-		currentDocument()->addToSelection(obj);
-		idx++;
+		LDObject* object = currentDocument()->insertFromString(position, line);
+		currentDocument()->addToSelection(object);
+		position += 1;
 	}
 
 	m_window->refresh();
--- a/src/toolsets/extprogramtoolset.cpp	Sun Jan 29 15:49:36 2017 +0200
+++ b/src/toolsets/extprogramtoolset.cpp	Sun Jan 29 21:02:11 2017 +0200
@@ -170,14 +170,14 @@
 		if (obj->type() == OBJ_SubfileReference)
 		{
 			LDSubfileReference* ref = static_cast<LDSubfileReference*> (obj);
-			Model model;
+			Model model {m_documents};
 			ref->inlineContents(model, true, false);
 			writeObjects(model.objects().toList(), f);
 		}
 		else if (obj->type() == OBJ_BezierCurve)
 		{
 			LDBezierCurve* curve = static_cast<LDBezierCurve*> (obj);
-			Model model;
+			Model model {m_documents};
 			curve->rasterize(model, grid()->bezierCurveSegments());
 			writeObjects(model.objects().toList(), f);
 		}
@@ -323,7 +323,7 @@
 
 	// TODO: I don't like how I need to go to the document manager to load objects from a file...
 	// We're not loading this as a document so it shouldn't be necessary.
-	Model model;
+	Model model {m_documents};
 	m_documents->loadFileContents(&f, model, nullptr, nullptr);
 
 	// If we replace the objects, delete the selection now.
--- a/src/toolsets/filetoolset.cpp	Sun Jan 29 15:49:36 2017 +0200
+++ b/src/toolsets/filetoolset.cpp	Sun Jan 29 21:02:11 2017 +0200
@@ -117,8 +117,7 @@
 
 		if (file.open(QIODevice::ReadOnly))
 		{
-			// TODO: shouldn't need to go to the document manager to parse a file
-			Model model;
+			Model model {m_documents};
 			m_documents->loadFileContents(&file, model, nullptr, nullptr);
 
 			currentDocument()->clearSelection();

mercurial