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

Sun, 29 Jan 2017 21:02:11 +0200

author
Teemu Piippo <teemu@hecknology.net>
date
Sun, 29 Jan 2017 21:02:11 +0200
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