Giant refactor

Mon, 06 Jun 2022 22:01:22 +0300

author
Teemu Piippo <teemu@hecknology.net>
date
Mon, 06 Jun 2022 22:01:22 +0300
changeset 200
ca23936b455b
parent 199
6988973515d2
child 201
5d201ee4a9c3

Giant refactor

CMakeLists.txt file | annotate | diff | comparison | revisions
src/document.cpp file | annotate | diff | comparison | revisions
src/document.h file | annotate | diff | comparison | revisions
src/documentmanager.cpp file | annotate | diff | comparison | revisions
src/edithistory.cpp file | annotate | diff | comparison | revisions
src/edithistory.h file | annotate | diff | comparison | revisions
src/geometry.cpp file | annotate | diff | comparison | revisions
src/geometry.h file | annotate | diff | comparison | revisions
src/gl/common.h file | annotate | diff | comparison | revisions
src/gl/compiler.cpp file | annotate | diff | comparison | revisions
src/gl/compiler.h file | annotate | diff | comparison | revisions
src/gl/partrenderer.cpp file | annotate | diff | comparison | revisions
src/gl/partrenderer.h file | annotate | diff | comparison | revisions
src/gl/vertexprogram.cpp file | annotate | diff | comparison | revisions
src/gl/vertexprogram.h file | annotate | diff | comparison | revisions
src/invert.cpp file | annotate | diff | comparison | revisions
src/ldrawalgorithm.cpp file | annotate | diff | comparison | revisions
src/ldrawalgorithm.h file | annotate | diff | comparison | revisions
src/linetypes/circularprimitive.cpp file | annotate | diff | comparison | revisions
src/linetypes/circularprimitive.h file | annotate | diff | comparison | revisions
src/linetypes/compoundobject.cpp file | annotate | diff | comparison | revisions
src/linetypes/compoundobject.h file | annotate | diff | comparison | revisions
src/linetypes/conditionaledge.cpp file | annotate | diff | comparison | revisions
src/linetypes/conditionaledge.h file | annotate | diff | comparison | revisions
src/linetypes/edge.cpp file | annotate | diff | comparison | revisions
src/linetypes/edge.h file | annotate | diff | comparison | revisions
src/linetypes/errorline.cpp file | annotate | diff | comparison | revisions
src/linetypes/errorline.h file | annotate | diff | comparison | revisions
src/linetypes/metacommand.cpp file | annotate | diff | comparison | revisions
src/linetypes/metacommand.h file | annotate | diff | comparison | revisions
src/linetypes/object.cpp file | annotate | diff | comparison | revisions
src/linetypes/object.h file | annotate | diff | comparison | revisions
src/linetypes/polygonobject.h file | annotate | diff | comparison | revisions
src/linetypes/propertygenerics.h file | annotate | diff | comparison | revisions
src/linetypes/quadrilateral.cpp file | annotate | diff | comparison | revisions
src/linetypes/quadrilateral.h file | annotate | diff | comparison | revisions
src/linetypes/subfilereference.cpp file | annotate | diff | comparison | revisions
src/linetypes/subfilereference.h file | annotate | diff | comparison | revisions
src/linetypes/triangle.cpp file | annotate | diff | comparison | revisions
src/linetypes/triangle.h file | annotate | diff | comparison | revisions
src/main.cpp file | annotate | diff | comparison | revisions
src/main.h file | annotate | diff | comparison | revisions
src/mainwindow.cpp file | annotate | diff | comparison | revisions
src/mainwindow.h file | annotate | diff | comparison | revisions
src/model.cpp file | annotate | diff | comparison | revisions
src/model.h file | annotate | diff | comparison | revisions
src/modeleditor.cpp file | annotate | diff | comparison | revisions
src/modeleditor.h file | annotate | diff | comparison | revisions
src/parser.cpp file | annotate | diff | comparison | revisions
src/parser.h file | annotate | diff | comparison | revisions
src/polygoncache.cpp file | annotate | diff | comparison | revisions
src/polygoncache.h file | annotate | diff | comparison | revisions
src/ui/canvas.cpp file | annotate | diff | comparison | revisions
src/ui/canvas.h file | annotate | diff | comparison | revisions
src/ui/objecteditor.cpp file | annotate | diff | comparison | revisions
src/ui/objecteditor.h file | annotate | diff | comparison | revisions
src/utility.h file | annotate | diff | comparison | revisions
src/vertexmap.cpp file | annotate | diff | comparison | revisions
src/vertexmap.h file | annotate | diff | comparison | revisions
src/widgets/colorindexinput.cpp file | annotate | diff | comparison | revisions
src/widgets/colorindexinput.h file | annotate | diff | comparison | revisions
--- a/CMakeLists.txt	Wed May 25 20:36:34 2022 +0300
+++ b/CMakeLists.txt	Mon Jun 06 22:01:22 2022 +0300
@@ -25,7 +25,6 @@
 	src/colors.cpp
 	src/document.cpp
 	src/documentmanager.cpp
-	src/edithistory.cpp
 	src/geometry.cpp
 	src/header.cpp
 	src/ldrawalgorithm.cpp
@@ -34,7 +33,6 @@
 	src/main.cpp
 	src/mainwindow.cpp
 	src/model.cpp
-	src/modeleditor.cpp
 	src/parser.cpp
 	src/polygoncache.cpp
 	src/uiutilities.cpp
@@ -46,16 +44,6 @@
 	src/gl/gridprogram.cpp
 	src/gl/partrenderer.cpp
 	src/gl/vertexprogram.cpp
-	src/linetypes/circularprimitive.cpp
-	src/linetypes/compoundobject.cpp
-	src/linetypes/conditionaledge.cpp
-	src/linetypes/edge.cpp
-	src/linetypes/errorline.cpp
-	src/linetypes/metacommand.cpp
-	src/linetypes/object.cpp
-	src/linetypes/quadrilateral.cpp
-	src/linetypes/subfilereference.cpp
-	src/linetypes/triangle.cpp
 	src/settingseditor/keyboardshortcutseditor.cpp
 	src/settingseditor/librarieseditor.cpp
 	src/settingseditor/settingseditor.cpp
@@ -75,7 +63,6 @@
 	src/colors.h
 	src/document.h
 	src/documentmanager.h
-	src/edithistory.h
 	src/functional.h
 	src/geometry.h
 	src/header.h
@@ -85,7 +72,6 @@
 	src/main.h
 	src/mainwindow.h
 	src/model.h
-	src/modeleditor.h
 	src/parser.h
 	src/polygoncache.h
 	src/ring.h
@@ -100,18 +86,6 @@
 	src/gl/gridprogram.h
 	src/gl/partrenderer.h
 	src/gl/vertexprogram.h
-	src/linetypes/circularprimitive.h
-	src/linetypes/compoundobject.h
-	src/linetypes/conditionaledge.h
-	src/linetypes/edge.h
-	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
 	src/settingseditor/keyboardshortcutseditor.h
 	src/settingseditor/librarieseditor.h
 	src/settingseditor/settingseditor.h
--- a/src/document.cpp	Wed May 25 20:36:34 2022 +0300
+++ b/src/document.cpp	Mon Jun 06 22:01:22 2022 +0300
@@ -21,13 +21,9 @@
 #include "document.h"
 #include "ui_document.h"
 #include "model.h"
-#include "modeleditor.h"
 #include "ui/objecteditor.h"
-#include "linetypes/edge.h"
-#include "linetypes/triangle.h"
-#include "linetypes/quadrilateral.h"
 
-Document::Document(
+EditorTabWidget::EditorTabWidget(
 	Model* model,
 	DocumentManager* documents,
 	const ldraw::ColorTable& colorTable,
@@ -39,8 +35,7 @@
 	documents{documents},
 	vertexMap{model},
 	ui{*new Ui_Document},
-	toolsBar{new QToolBar{this}},
-	objectEditor{new ObjectEditor{this}}
+	toolsBar{new QToolBar{this}}
 {
 	this->ui.setupUi(this);
 	const int listWidth = static_cast<int>(this->width() / 3);
@@ -52,17 +47,17 @@
 	this->ui.viewportFrame->layout()->addWidget(this->canvas);
 	this->toolsBar->setOrientation(Qt::Vertical);
 	this->setMouseTracking(true);
-	connect(this->ui.viewportListSplitter, &QSplitter::splitterMoved, this, &Document::splitterChanged);
-	connect(this->canvas, &Canvas::mouseClick, this, &Document::canvasMouseClick);
-	connect(this->canvas, &Canvas::mouseMove, this, &Document::canvasMouseMove);
-	connect(this->canvas, &Canvas::newStatusText, this, &Document::newStatusText);
+	connect(this->ui.viewportListSplitter, &QSplitter::splitterMoved, this, &EditorTabWidget::splitterChanged);
+	connect(this->canvas, &Canvas::mouseClick, this, &EditorTabWidget::canvasMouseClick);
+	connect(this->canvas, &Canvas::mouseMove, this, &EditorTabWidget::canvasMouseMove);
+	connect(this->canvas, &Canvas::newStatusText, this, &EditorTabWidget::newStatusText);
 	connect(this->ui.listView->selectionModel(), &QItemSelectionModel::selectionChanged,
 		[&](const QItemSelection& selected, const QItemSelection& deselected)
 	{
-		auto resolveIndex = [this](const QModelIndex& index){ return (*this->model)[index.row()]->id; };
+		auto resolveIndex = [this](const QModelIndex& index){ return this->model->idAt(index.row()); };
 		auto resolve = [resolveIndex](const QItemSelection& selection)
 		{
-			return fn::map<QSet<ldraw::id_t>>(selection.indexes(), resolveIndex);
+			return fn::map<QSet<ModelId>>(selection.indexes(), resolveIndex);
 		};
 		this->canvas->handleSelectionChange(resolve(selected), resolve(deselected));
 	});
@@ -75,38 +70,29 @@
 	this->initializeTools();
 }
 
-Document::~Document()
+EditorTabWidget::~EditorTabWidget()
 {
 	delete &this->ui;
 }
 
-QByteArray Document::saveSplitterState() const
+QByteArray EditorTabWidget::saveSplitterState() const
 {
 	return this->ui.viewportListSplitter->saveState();
 }
 
-void Document::restoreSplitterState(const QByteArray& state)
+void EditorTabWidget::restoreSplitterState(const QByteArray& state)
 {
 	this->ui.viewportListSplitter->restoreState(state);
 }
 
-std::unique_ptr<ModelEditor> Document::editModel()
-{
-	std::unique_ptr<ModelEditor> editorPointer = std::make_unique<ModelEditor>(*this->model);
-	connect(editorPointer.get(), &ModelEditor::objectModified, [&](int position){
-		this->model->emitDataChangedSignal(position);
-	});
-	return editorPointer;
-}
-
-void Document::applyToVertices(VertexMap::ApplyFunction fn) const
+void EditorTabWidget::applyToVertices(VertexMap::ApplyFunction fn) const
 {
 	this->vertexMap.apply(fn);
 }
 
 const char INDEX_PROPERTY[] = "_editing_mode_index";
 
-void Document::initializeTools()
+void EditorTabWidget::initializeTools()
 {
 	const struct
 	{
@@ -118,7 +104,7 @@
 			.name = tr("Select"),
 			.tooltip = tr("Select elements from the model."),
 			.icon = {":/icons/navigate-outline.png"},
-			.widget = this->objectEditor,
+			//.widget = this->objectEditor,
 		},
 		{
 			.name = tr("Draw"),
@@ -142,12 +128,12 @@
 		}
 		this->ui.toolWidgetStack->addWidget(widget);
 		this->toolActions.push_back(action);
-		connect(action, &QAction::triggered, this, &Document::editingModeTriggered);
+		connect(action, &QAction::triggered, this, &EditorTabWidget::editingModeTriggered);
 	}
 	this->ui.listView->selectAll();
 }
 
-void Document::editingModeTriggered()
+void EditorTabWidget::editingModeTriggered()
 {
 	QAction* triggeredAction = qobject_cast<QAction*>(this->sender());
 	if (triggeredAction != nullptr)
@@ -186,16 +172,16 @@
 	return any(points, std::bind(geom::isclose, std::placeholders::_1, pos));
 }
 
-void Document::canvasMouseClick(QMouseEvent *event)
+void EditorTabWidget::canvasMouseClick(QMouseEvent *event)
 {
 	switch(this->drawState.mode)
 	{
 	case SelectMode:
 		if (event->button() == Qt::LeftButton)
 		{
-			const ldraw::id_t highlighted = this->canvas->getHighlightedObject();
-			QSet<ldraw::id_t> selected;
-			if (highlighted != ldraw::NULL_ID) {
+			const ModelId highlighted = this->canvas->getHighlightedObject();
+			QSet<ModelId> selected;
+			if (highlighted != ModelId{0}) {
 				selected.insert(highlighted);
 			}
 			this->select(selected);
@@ -229,7 +215,7 @@
 	}
 }
 
-void Document::canvasMouseMove(QMouseEvent *event)
+void EditorTabWidget::canvasMouseMove(QMouseEvent *event)
 {
 	switch(this->drawState.mode)
 	{
@@ -247,52 +233,59 @@
 	}
 }
 
-void Document::select(const QSet<ldraw::id_t> &selected)
+void EditorTabWidget::select(const QSet<ModelId> &selected)
 {
 	QItemSelectionModel* selectionModel = this->ui.listView->selectionModel();
 	QItemSelection itemSelection;
-	for (const ldraw::id_t id : selected)
+	for (const ModelId id : selected)
 	{
-		QModelIndex index = this->model->find(id);
-		if (index != QModelIndex{})
+		const std::optional<int> row = this->model->find(id);
+		if (row.has_value())
 		{
-			itemSelection.select(index, index);
+			const QModelIndex qindex = this->model->index(*row);
+			itemSelection.select(qindex, qindex);
 		}
 	}
 	selectionModel->select(itemSelection, QItemSelectionModel::ClearAndSelect);
 }
 
-const Model &Document::getModel() const
-{
-	return *this->model;
-}
-
-const QSet<ldraw::id_t> Document::selectedObjects() const
+const QSet<ModelId> EditorTabWidget::selectedObjects() const
 {
 	return this->canvas->selectedObjects();
 }
 
-void Document::closeShape()
+void EditorTabWidget::closeShape()
 {
 	if (this->drawState.polygon.size() >= 2 and this->drawState.polygon.size() <= 4)
 	{
-		std::unique_ptr<ModelEditor> modelEditor = this->editModel();
 		switch (this->drawState.polygon.size())
 		{
 		case 2:
-			modelEditor->append<ldraw::Edge>(
-				vectorToArray<2>(this->drawState.polygon),
-				ldraw::EDGE_COLOR);
+			this->model->append(Colored<LineSegment>{
+				LineSegment{
+					.p1 = this->drawState.polygon[0],
+					.p2 = this->drawState.polygon[1],
+				},
+				ldraw::EDGE_COLOR});
 			break;
 		case 3:
-			modelEditor->append<ldraw::Triangle>(
-				vectorToArray<3>(this->drawState.polygon),
-				ldraw::MAIN_COLOR);
+			this->model->append(Colored<Triangle>{
+				Triangle{
+					.p1 = this->drawState.polygon[0],
+					.p2 = this->drawState.polygon[1],
+					.p3 = this->drawState.polygon[2],
+				},
+				ldraw::MAIN_COLOR});
 			break;
 		case 4:
-			modelEditor->append<ldraw::Quadrilateral>(
-				vectorToArray<4>(this->drawState.polygon),
-				ldraw::MAIN_COLOR);
+			this->model->append(Colored<Quadrilateral>{
+				Quadrilateral{
+					.p1 = this->drawState.polygon[0],
+					.p2 = this->drawState.polygon[1],
+					.p3 = this->drawState.polygon[2],
+					.p4 = this->drawState.polygon[3],
+				},
+				ldraw::MAIN_COLOR});
 			break;
 		}
 	}
--- a/src/document.h	Wed May 25 20:36:34 2022 +0300
+++ b/src/document.h	Mon Jun 06 22:01:22 2022 +0300
@@ -23,46 +23,38 @@
 #include "ui/canvas.h"
 #include "model.h"
 #include "vertexmap.h"
-#include "edithistory.h"
 
-class Document : public QWidget
+class EditorTabWidget : public QWidget
 {
 	Q_OBJECT
 public:
-	explicit Document(
+	explicit EditorTabWidget(
 		Model* model,
 		DocumentManager* documents,
 		const ldraw::ColorTable& colorTable,
 		QWidget *parent = nullptr);
-	~Document() override;
+	~EditorTabWidget() override;
 	QByteArray saveSplitterState() const;
 	void restoreSplitterState(const QByteArray& state);
-	std::unique_ptr<ModelEditor> editModel();
 	void applyToVertices(VertexMap::ApplyFunction fn) const;
-	const Model& getModel() const;
-	const QSet<ldraw::id_t> selectedObjects() const;
+	const QSet<ModelId> selectedObjects() const;
 	const ldraw::ColorTable& colorTable;
 	Canvas* const canvas;
+	Model* const model;
 	Q_SLOT void editingModeTriggered();
 	Q_SLOT void canvasMouseClick(QMouseEvent* event);
 	Q_SLOT void canvasMouseMove(QMouseEvent* event);
-	void select(const QSet<ldraw::id_t> &selected);
-	DrawState drawState;
+	void select(const QSet<ModelId> &selected);
 Q_SIGNALS:
 	void newStatusText(const QString& newStatusText);
 	void splitterChanged();
 private:
 	void initializeTools();
 	void closeShape();
-	Model* model;
+	DrawState drawState;
 	DocumentManager* const documents;
 	VertexMap vertexMap;
 	class Ui_Document& ui;
 	QToolBar* toolsBar;
 	std::vector<QAction*> toolActions;
-	class ObjectEditor* objectEditor;
-	/**
-	 * @brief History information of edits to this model
-	 */
-	// EditHistory editHistory;
 };
--- a/src/documentmanager.cpp	Wed May 25 20:36:34 2022 +0300
+++ b/src/documentmanager.cpp	Mon Jun 06 22:01:22 2022 +0300
@@ -21,8 +21,6 @@
 #include <QFileInfo>
 #include <QSaveFile>
 #include "documentmanager.h"
-#include "modeleditor.h"
-#include "linetypes/subfilereference.h"
 #include "parser.h"
 
 /**
@@ -43,7 +41,7 @@
 	const ModelId modelId{++this->modelIdCounter};
 	const QString name = makeNewModelName();
 	this->openModels[modelId] = ModelInfo{
-		.model = std::make_unique<Model>(),
+		.model = std::make_unique<Model>(this),
 		.id = modelId,
 		.opentype = OpenType::ManuallyOpened,
 	};
@@ -400,6 +398,16 @@
 	return referencedFilePath;
 }
 
+template<typename T>
+void iterate(const Model& model, std::function<void(const T&)> fn)
+{
+	for (int i = 0; i < model.size(); ++i) {
+		if (std::holds_alternative<T>(model[i])) {
+			fn(std::get<T>(model[i]));
+		}
+	}
+}
+
 void DocumentManager::loadDependenciesForModel(
 	const ModelId modelId,
 	const QString &path,
@@ -418,9 +426,8 @@
 	}
 	ModelInfo& modelInfo = this->openModels[modelId];
 	modelInfo.dependencies.clear();
-	for (int i = 0; i < modelInfo.model->size(); i += 1)
-	{
-		const QString referenceName = (*modelInfo.model)[i]->getProperty(ldraw::Property::ReferenceName).toString();
+	iterate<Colored<SubfileReference>>(*modelInfo.model, [&](const SubfileReference& ref) {
+		const QString referenceName = ref.name;
 		if (not referenceName.isEmpty()
 			and modelInfo.dependencies.count(referenceName) == 0
 			and not failedToOpen.contains(referenceName))
@@ -458,5 +465,5 @@
 				bag.missing.append(referenceName);
 			}
 		}
-	}
+	});
 }
--- a/src/edithistory.cpp	Wed May 25 20:36:34 2022 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,55 +0,0 @@
-/*
- *  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 "edithistory.h"
-#include "modeleditor.h"
-
-EditHistory::EditHistory()
-{
-	
-}
-
-void InsertHistoryEntry::undo(ModelEditor &editContext)
-{
-	editContext.remove(this->position);
-}
-
-void InsertHistoryEntry::redo(ModelEditor &editContext)
-{
-	
-}
-
-void DeleteHistoryEntry::undo(ModelEditor &editContext)
-{
-	static_cast<InsertHistoryEntry*>(this)->redo(editContext);
-}
-
-void DeleteHistoryEntry::redo(ModelEditor &editContext)
-{
-	static_cast<InsertHistoryEntry*>(this)->undo(editContext);
-}
-
-void EditHistoryEntry::undo(ModelEditor &editContext)
-{
-	
-}
-
-void EditHistoryEntry::redo(ModelEditor &editContext)
-{
-	
-}
--- a/src/edithistory.h	Wed May 25 20:36:34 2022 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,102 +0,0 @@
-/*
- *  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 "main.h"
-#include "model.h"
-
-class ModelEditor;
-
-class AbstractHistoryEntry
-{
-public:
-	virtual void undo(ModelEditor& editContext) = 0;
-	virtual void redo(ModelEditor& editContext) = 0;
-};
-
-class InsertHistoryEntry : public AbstractHistoryEntry
-{
-public:
-	InsertHistoryEntry(int position, const QByteArray& state) :
-		position{position},
-		state{state} {}
-	void undo(ModelEditor& editContext) override;
-	void redo(ModelEditor& editContext) override;
-protected:
-	int position;
-	QByteArray state;
-};
-
-class DeleteHistoryEntry : public InsertHistoryEntry
-{
-public:
-	void undo(ModelEditor& editContext) override;
-	void redo(ModelEditor& editContext) override;
-};
-
-class EditHistoryEntry : public AbstractHistoryEntry
-{
-public:
-	EditHistoryEntry(int position, const QByteArray& stateBefore, const QByteArray& stateAfter) :
-		position{position},
-		stateBefore{stateBefore},
-		stateAfter{stateAfter} {}
-	void undo(ModelEditor& editContext) override;
-	void redo(ModelEditor& editContext) override;
-private:
-	int position;
-	QByteArray stateBefore;
-	QByteArray stateAfter;
-};
-
-class EditHistory
-{
-public:
-	using Changeset = std::vector<std::unique_ptr<AbstractHistoryEntry>>;
-	EditHistory();
-	/**
-	 * @brief Adds a new entry into the edit history. Creates a new changeset if there is not one already open.
-	 * If behind in history, deletes all history entries in front.
-	 */
-	template<typename T, typename... Rs>
-	void add(Rs&&... args)
-	{
-		if (not this->changesetOpen)
-		{
-			while (this->position < this->changesets.size())
-			{
-				this->changesets.erase(this->changesets.end() - 1);
-			}
-			if (this->changesets.empty())
-			{
-				this->changesets.emplace_back();
-			}
-			this->changesetOpen = true;
-		}
-		this->changesets.back().emplace_back(args...);
-	}
-	void commit()
-	{
-		this->changesetOpen = false;
-		this->position += 1;
-	}
-private:
-	std::vector<Changeset> changesets;
-	std::size_t position = 0;
-	bool changesetOpen = false;
-};
\ No newline at end of file
--- a/src/geometry.cpp	Wed May 25 20:36:34 2022 +0300
+++ b/src/geometry.cpp	Mon Jun 06 22:01:22 2022 +0300
@@ -33,7 +33,7 @@
  */
 geom::Plane geom::planeFromTriangle(const geom::Triangle& triangle)
 {
-	return geom::Plane{normalVector(triangle), triangle.points[0]};
+	return geom::Plane{normalVector(triangle), triangle.p1};
 }
 
 /**
@@ -45,8 +45,8 @@
 {
 	return glm::normalize(
 		glm::cross(
-			triangle.points[1] - triangle.points[0],
-			triangle.points[2] - triangle.points[0]));
+			triangle.p2 - triangle.p1,
+			triangle.p3 - triangle.p1));
 }
 
 /**
@@ -107,7 +107,7 @@
 {
 	std::optional<glm::vec2> result = lineLineIntersection(
 		rayToLine(ray),
-		lineFromPoints(line.points[0], line.points[1]));
+		lineFromPoints(line.p1, line.p2));
 	if (result.has_value())
 	{
 		const float d1 = glm::dot(*result - ray.anchor, ray.direction);
@@ -117,7 +117,7 @@
 		}
 		else
 		{
-			const float d2 = glm::dot(*result - line.points[0], *result - line.points[1]);
+			const float d2 = glm::dot(*result - line.p1, *result - line.p2);
 			if (d2 > 0)
 			{
 				result.reset();
--- a/src/geometry.h	Wed May 25 20:36:34 2022 +0300
+++ b/src/geometry.h	Mon Jun 06 22:01:22 2022 +0300
@@ -24,24 +24,44 @@
 		glm::vec<N, T, Q> anchor;
 	};
 
-	template<int N>
-	struct Polygon
-	{
-		std::array<glm::vec3, N> points;
-	};
-
-	template<int N>
-	struct Polygon2D
-	{
-		glm::vec2 points[N];
-	};
-
 	inline const glm::vec3 origin = {0, 0, 0};
 	inline const Plane XY = {{0, 0, 1}, origin};
 	inline const Plane XZ = {{0, 1, 0}, origin};
 	inline const Plane YZ = {{1, 0, 0}, origin};
-	using Triangle = Polygon<3>;
-	using LineSegment2D = Polygon2D<2>;
+
+	struct LineSegment
+	{
+		glm::vec3 p1, p2;
+	};
+	struct Triangle
+	{
+		glm::vec3 p1, p2, p3;
+	};
+	struct Quadrilateral
+	{
+		glm::vec3 p1, p2, p3, p4;
+	};
+	struct ConditionalEdge
+	{
+		glm::vec3 p1, p2;
+		glm::vec3 c1, c2;
+	};
+	struct LineSegment2D
+	{
+		glm::vec2 p1, p2;
+	};
+
+	// get polygon type from amount of points
+	template<int N>
+	struct PolygonType {};
+	template<>
+	struct PolygonType<2> { using type = LineSegment; };
+	template<>
+	struct PolygonType<3> { using type = Triangle; };
+	template<>
+	struct PolygonType<4> { using type = Quadrilateral; };
+	template<int N>
+	using Polygon = typename PolygonType<N>::type;
 
 	/**
 	 * @brief Computes a line from two points
@@ -154,3 +174,4 @@
 	glm::vec3 pointOnCurve(const BezierCurve& curve, float t);
 	glm::vec3 derivativeOnCurve(const BezierCurve& curve, float t);
 }
+using namespace geom;
--- a/src/gl/common.h	Wed May 25 20:36:34 2022 +0300
+++ b/src/gl/common.h	Mon Jun 06 22:01:22 2022 +0300
@@ -27,6 +27,7 @@
 #include <glm/gtc/type_ptr.hpp>
 #include "basics.h"
 #include "colors.h"
+#include "model.h"
 
 namespace gl
 {
@@ -77,7 +78,7 @@
 	} type;
 	glm::vec3 vertices[4];
 	ldraw::Color color;
-	ldraw::id_t id;
+	ModelId id;
 
 	/**
 	 * @return amount of vertices used for geometry
@@ -124,41 +125,44 @@
 
 	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)
+	inline Polygon edgeLine(const Colored<LineSegment>& seg, ModelId id)
 	{
-		return {Polygon::EdgeLine, {v_1, v_2}, color, id};
-	}
-
-	inline Polygon triangle(
-		const glm::vec3& v_1,
-		const glm::vec3& v_2,
-		const glm::vec3& v_3,
-		ldraw::Color color,
-		ldraw::id_t id)
-	{
-		return {Polygon::Triangle, {v_1, v_2, v_3}, color, id};
+		return Polygon{
+			.type = Polygon::EdgeLine,
+			.vertices = {seg.p1, seg.p2},
+			.color = seg.color,
+			.id = id,
+		};
 	}
 
-	inline Polygon quadrilateral(
-		const glm::vec3& v_1,
-		const glm::vec3& v_2,
-		const glm::vec3& v_3,
-		const glm::vec3& v_4,
-		ldraw::Color color,
-		ldraw::id_t id)
+	inline Polygon triangle(const Colored<Triangle>& tri, ModelId id)
 	{
-		return {Polygon::Quadrilateral, {v_1, v_2, v_3, v_4}, color, id};
+		return Polygon{
+			.type = Polygon::Triangle,
+			.vertices = {tri.p1, tri.p2, tri.p3},
+			.color = tri.color,
+			.id = id,
+		};
 	}
 
-	inline Polygon conditionalEdge(
-		const glm::vec3& v_1,
-		const glm::vec3& v_2,
-		const glm::vec3& control_1,
-		const glm::vec3& control_2,
-		ldraw::Color color,
-		ldraw::id_t id)
+	inline Polygon quadrilateral(const Colored<Quadrilateral>& quad, ModelId id)
 	{
-		return {Polygon::ConditionalEdge, {v_1, v_2, control_1, control_2}, color, id};
+		return Polygon{
+			.type = Polygon::Quadrilateral,
+			.vertices = {quad.p1, quad.p2, quad.p3, quad.p4},
+			.color = quad.color,
+			.id = id,
+		};
+	}
+
+	inline Polygon conditionalEdge(const Colored<ConditionalEdge>& cedge, ModelId id)
+	{
+		return Polygon{
+			.type = Polygon::ConditionalEdge,
+			.vertices = {cedge.p1, cedge.p2, cedge.c1, cedge.c2},
+			.color = cedge.color,
+			.id = id,
+		};
 	}
 
 	// Vbo names
--- a/src/gl/compiler.cpp	Wed May 25 20:36:34 2022 +0300
+++ b/src/gl/compiler.cpp	Mon Jun 06 22:01:22 2022 +0300
@@ -311,7 +311,7 @@
 	}
 }
 
-ldraw::id_t gl::idFromColor(const std::array<GLubyte, 3>& data)
+ModelId gl::idFromColor(const std::array<GLubyte, 3>& data)
 {
 	return {data[0] * std::int32_t{0x10000} + data[1] * std::int32_t{0x100} + data[2]};
 }
@@ -330,7 +330,7 @@
 	shaderObject.vertexArray.release();
 }
 
-void gl::setModelShaderSelectedObjects(gl::ModelShaders* shaders, const QSet<ldraw::id_t>& ids)
+void gl::setModelShaderSelectedObjects(gl::ModelShaders* shaders, const QSet<ModelId> &ids)
 {
 	for (ModelShaders::ShaderObject& object : shaders->shaderObjects)
 	{
--- a/src/gl/compiler.h	Wed May 25 20:36:34 2022 +0300
+++ b/src/gl/compiler.h	Mon Jun 06 22:01:22 2022 +0300
@@ -27,6 +27,7 @@
 #include <QOpenGLBuffer>
 #include <QOpenGLShaderProgram>
 #include <QOpenGLExtraFunctions>
+#include "model.h"
 
 class Model;
 class DocumentManager;
@@ -64,9 +65,9 @@
 	void initializeModelShaders(ModelShaders* modelShaders);
 	void bindModelShaderVertexArray(gl::ModelShaders* shaders, gl::ArrayClass arrayClass);
 	void releaseModelShaderVertexArray(gl::ModelShaders* shaders, gl::ArrayClass arrayClass);
-	void setModelShaderSelectedObjects(gl::ModelShaders* shaders, const QSet<ldraw::id_t>& ids);
+	void setModelShaderSelectedObjects(gl::ModelShaders* shaders, const QSet<ModelId>& ids);
 	std::size_t vertexCount(const ModelShaders *shaders, gl::ArrayClass arrayClass);
-	ldraw::id_t idFromColor(const std::array<GLubyte, 3>& data);
+	ModelId idFromColor(const std::array<GLubyte, 3>& data);
 
 	template<typename T>
 	void setShaderUniform(gl::ModelShaders* shaders, const char* uniformName, T&& value)
--- a/src/gl/partrenderer.cpp	Wed May 25 20:36:34 2022 +0300
+++ b/src/gl/partrenderer.cpp	Mon Jun 06 22:01:22 2022 +0300
@@ -25,6 +25,7 @@
 #include "geometry.h"
 #include "partrenderer.h"
 #include "model.h"
+#include "gl/compiler.h"
 
 static constexpr double MIN_ZOOM = -3.0;
 static constexpr double MAX_ZOOM = 3.0;
@@ -352,7 +353,7 @@
 		viewportVector);
 }
 
-ldraw::id_t PartRenderer::pick(QPoint where)
+ModelId PartRenderer::pick(QPoint where)
 {
 	// y is flipped, take that into account
 	where.setY(this->height() - where.y());
@@ -402,7 +403,7 @@
 /**
  * @return the currently highlighted object
  */
-ldraw::id_t PartRenderer::getHighlightedObject() const
+ModelId PartRenderer::getHighlightedObject() const
 {
 	return this->highlighted;
 }
--- a/src/gl/partrenderer.h	Wed May 25 20:36:34 2022 +0300
+++ b/src/gl/partrenderer.h	Mon Jun 06 22:01:22 2022 +0300
@@ -1,15 +1,10 @@
 #pragma once
 #include <QOpenGLWidget>
-#include <QOpenGLFunctions>
-#include <QQuaternion>
-#include <QOpenGLVertexArrayObject>
-#include <QOpenGLBuffer>
-#include <QOpenGLShader>
-#include <QOpenGLShaderProgram>
-#include <glm/glm.hpp>
 #include "main.h"
 #include "gl/common.h"
 #include "gl/compiler.h"
+#include "documentmanager.h"
+#include "types/boundingbox.h"
 
 class PartRenderer : public QOpenGLWidget
 {
@@ -22,9 +17,9 @@
 		QWidget* parent = nullptr);
 	~PartRenderer() override;
 	void setRenderPreferences(const gl::RenderPreferences& newPreferences);
-	ldraw::id_t getHighlightedObject() const;
+	ModelId getHighlightedObject() const;
 protected:
-	ldraw::id_t pick(QPoint where);
+	ModelId pick(QPoint where);
 	void initializeGL() override;
 	void resizeGL(int width, int height) override;
 	void paintGL() override;
@@ -35,7 +30,7 @@
 	const ldraw::ColorTable& colorTable;
 	BoundingBox boundingBox;
 	gl::ModelShaders shaders;
-	ldraw::id_t highlighted = ldraw::NULL_ID;
+	ModelId highlighted = {0};
 	std::optional<glm::vec3> screenToModelCoordinates(const QPoint& point, const geom::Plane& plane) const;
 	QPointF modelToScreenCoordinates(const glm::vec3& point) const;
 	geom::Line<3> cameraLine(const QPoint& point) const;
--- a/src/gl/vertexprogram.cpp	Wed May 25 20:36:34 2022 +0300
+++ b/src/gl/vertexprogram.cpp	Mon Jun 06 22:01:22 2022 +0300
@@ -120,7 +120,7 @@
 	this->fragmentStyle = newFragmentStyle;
 }
 
-void VertexProgram::build(const Document *document)
+void VertexProgram::build(const EditorTabWidget *document)
 {
 	constexpr glm::vec3 color = {0.0, 1.0, 1.0};
 	this->data.clear();
--- a/src/gl/vertexprogram.h	Wed May 25 20:36:34 2022 +0300
+++ b/src/gl/vertexprogram.h	Mon Jun 06 22:01:22 2022 +0300
@@ -1,7 +1,7 @@
 #ifndef VERTEXPROGRAM_H
 #define VERTEXPROGRAM_H
 #include "basicshaderprogram.h"
-class Document;
+class EditorTabWidget;
 
 class VertexProgram : public AbstractBasicShaderProgram
 {
@@ -17,7 +17,7 @@
 		glm::vec3 color;
 	};
 	VertexProgram(QObject* parent = nullptr);
-	void build(const Document* document);
+	void build(const EditorTabWidget* document);
 protected:
 	const char* vertexShaderSource() const override;
 	const char* fragmentShaderSource() const override;
--- a/src/invert.cpp	Wed May 25 20:36:34 2022 +0300
+++ b/src/invert.cpp	Mon Jun 06 22:01:22 2022 +0300
@@ -21,7 +21,6 @@
 #include "gl/common.h"
 #include "invert.h"
 #include "documentmanager.h"
-#include "modeleditor.h"
 
 /*
  * Returns a matrix that causes a flip on the given dimension.
--- a/src/ldrawalgorithm.cpp	Wed May 25 20:36:34 2022 +0300
+++ b/src/ldrawalgorithm.cpp	Mon Jun 06 22:01:22 2022 +0300
@@ -1,53 +1,26 @@
 #include "ldrawalgorithm.h"
-#include "linetypes/quadrilateral.h"
-#include "linetypes/triangle.h"
 
-void ldraw::invert(ModelEditor& editor, ldraw::id_t id, GetPolygonsContext* context)
+std::pair<Triangle, Triangle> splitTriangles(
+	const Quadrilateral& q,
+	ldraw::Diagonal diagonal)
 {
-	editor.modifyObject(id, [context](ldraw::Object* object){
-		object->invert(context);
-	});
-}
-
-static std::array<geom::Triangle, 2> splitTriangles(ldraw::Diagonal diagonal, const std::array<glm::vec3, 4>& points)
-{
-	std::array<geom::Triangle, 2> result;
+	std::pair<Triangle, Triangle> result;
 	switch (diagonal)
 	{
 	case ldraw::Diagonal::Diagonal_13:
-		result = {geom::Triangle{points[0], points[1], points[2]}, {points[0], points[2], points[3]}};
+		result = {Triangle{q.p1, q.p2, q.p3}, {q.p1, q.p3, q.p4}};
 		break;
 	case ldraw::Diagonal::Diagonal_24:
-		result = {geom::Triangle{points[0], points[1], points[3]}, {points[1], points[2], points[3]}};
+		result = {Triangle{q.p1, q.p2, q.p3}, {q.p2, q.p3, q.p4}};
 		break;
 	}
 	return result;
 }
 
-auto ldraw::splitQuadrilateral(
-	ModelEditor& editor,
-	ldraw::quadrilateralid_t quadrilateral_id,
-	ldraw::Diagonal splitType
-) -> std::optional<std::pair<ldraw::triangleid_t, ldraw::triangleid_t>>
-{
-	std::optional<std::pair<ldraw::triangleid_t, ldraw::triangleid_t>> result;
-	const auto resolved = editor.model().get2(quadrilateral_id);
-	if (resolved.object != nullptr)
-	{
-		const ldraw::Color color = resolved.object->colorIndex;
-		const std::array<geom::Triangle, 2> split = splitTriangles(splitType, resolved.object->points);
-		const int position = resolved.index.row();
-		editor.remove(position);
-		result = std::make_pair(
-			editor.insert<ldraw::Triangle>(position, split[0].points, color),
-			editor.insert<ldraw::Triangle>(position, split[1].points, color));
-	}
-	return result;
-}
-
 /**
  * @brief Modifies the !LDRAW_ORG line so that it becomes unofficial
  */
+/*
 void ldraw::makeUnofficial(ModelEditor& editor)
 {
 	if (editor.model().size() >= 4)
@@ -71,4 +44,24 @@
 			}
 		}
 	}
-}
\ No newline at end of file
+}
+*/
+
+ModelElement inverted(const ModelElement& element)
+{
+	return std::visit(overloaded{
+		[](Colored<SubfileReference> ref) -> ModelElement {
+			ref.inverted = not ref.inverted;
+			return ref;
+		},
+		[](Colored<Triangle> triangle) -> ModelElement {
+			std::swap(triangle.p1, triangle.p2);
+			return triangle;
+		},
+		[](Colored<Quadrilateral> quad) -> ModelElement {
+			std::swap(quad.p2, quad.p4);
+			return quad;
+		},
+		[](const ModelElement& x) { return x; }
+	}, element);
+}
--- a/src/ldrawalgorithm.h	Wed May 25 20:36:34 2022 +0300
+++ b/src/ldrawalgorithm.h	Mon Jun 06 22:01:22 2022 +0300
@@ -1,5 +1,6 @@
 #pragma once
-#include "modeleditor.h"
+#include "main.h"
+#include "model.h"
 
 namespace ldraw
 {
@@ -10,15 +11,13 @@
 		Diagonal_24
 	};
 
-	// Splits the specified quadrilateral into triangles.
-	// If it is not a quadrilateral then no action is performed
-	auto splitQuadrilateral(ModelEditor& editor,
-		quadrilateralid_t quadrilateral_id,
-		Diagonal splitType = Diagonal::Diagonal_13
-	) -> std::optional<std::pair<triangleid_t, triangleid_t>>;
+	std::pair<Triangle, Triangle> splitTriangles(
+		const Quadrilateral& q,
+		ldraw::Diagonal diagonal);
 
-	void invert(ModelEditor& editor, ldraw::id_t id, GetPolygonsContext *context);
+	/*
 	void makeUnofficial(ModelEditor &editor);
+	*/
 
 	template<typename Fn>
 	void circle(int segments, int divisions, Fn&& fn)
@@ -34,3 +33,5 @@
 		}
 	}
 }
+
+ModelElement inverted(const ModelElement &element);
--- a/src/linetypes/circularprimitive.cpp	Wed May 25 20:36:34 2022 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,108 +0,0 @@
-#include "circularprimitive.h"
-
-ldraw::CircularPrimitive::CircularPrimitive(CircularPrimitiveType type, int segments, int divisions) :
-	type{type},
-	segments{segments},
-	divisions{divisions}
-{
-}
-
-QVariant ldraw::CircularPrimitive::getProperty(Property property) const
-{
-	switch (property)
-	{
-	case Property::Segments:
-		return this->segments;
-	case Property::Divisions:
-		return this->divisions;
-	case Property::CircularPrimitiveType:
-		return this->type;
-	default:
-		return BaseClass::getProperty(property);
-	}
-}
-
-QString ldraw::CircularPrimitive::textRepresentation() const
-{
-	return circularPrimitiveTypeName(this->type) + " " + QString::number(this->fraction());
-}
-
-QString ldraw::CircularPrimitive::circularPrimitiveTypeName(CircularPrimitiveType type)
-{
-	switch (type)
-	{
-	case Circle:
-		return QObject::tr("Circle");
-	case Disc:
-		return QObject::tr("Disc");
-	}
-}
-
-ldraw::Object::Type ldraw::CircularPrimitive::typeIdentifier() const
-{
-	return ldraw::Object::Type::CircularPrimitive;
-}
-
-QDataStream &ldraw::CircularPrimitive::serialize(QDataStream &stream) const
-{
-	return BaseClass::serialize(stream) << this->type << this->segments << this->divisions;
-}
-
-QDataStream &ldraw::CircularPrimitive::deserialize(QDataStream &stream)
-{
-	return BaseClass::deserialize(stream) >> this->type >> this->segments >> this->divisions;
-}
-
-QString ldraw::CircularPrimitive::toLDrawCode() const
-{
-	return utility::format(
-		"0 !LDFORGE CIRCULAR_PRIMITIVE %1 %2 %3 %4",
-		static_cast<int>(this->type),
-		this->segments,
-		this->divisions,
-		this->transformToBareString());
-}
-
-QString ldraw::CircularPrimitive::iconName() const
-{
-	return ":/icons/linetype-circularprimitive.png";
-}
-
-QString ldraw::CircularPrimitive::typeName() const
-{
-	return QObject::tr("circular primitive");
-}
-
-void ldraw::CircularPrimitive::getPolygons(std::vector<gl::Polygon> &polygons, GetPolygonsContext *) const
-{
-	for (int i = 0; i < this->segments; i += 1)
-	{
-		const float ang_1 = (2 * pi<> * i) / this->divisions;
-		const float ang_2 = (2 * pi<> * (i + 1)) / this->divisions;
-		const glm::vec3 p_1 = {std::sin(ang_1), 0, std::cos(ang_1)};
-		const glm::vec3 p_2 = {std::sin(ang_2), 0, std::cos(ang_2)};
-		switch (this->type)
-		{
-		case Circle:
-			polygons.push_back(gl::edgeLine(
-				p_1,
-				p_2,
-				this->colorIndex,
-				this->id));
-			break;
-		case Disc:
-			polygons.push_back(gl::triangle(
-				{0, 0, 0},
-				p_1,
-				p_2,
-				this->colorIndex,
-				this->id));
-			break;
-		}
-	}
-}
-
-float ldraw::CircularPrimitive::fraction() const
-{
-	return static_cast<float>(this->segments) / static_cast<float>(this->divisions);
-}
--- a/src/linetypes/circularprimitive.h	Wed May 25 20:36:34 2022 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,31 +0,0 @@
-#pragma once
-#include "compoundobject.h"
-#include "propertygenerics.h"
-
-namespace ldraw
-{
-	class CircularPrimitive;
-}
-
-class ldraw::CircularPrimitive : public CompoundObject
-{
-public:
-	using BaseClass = CompoundObject;
-	CircularPrimitive() = default;
-	CircularPrimitive(CircularPrimitiveType type, int segments, int divisions);
-	QVariant getProperty(Property property) const override;
-	QString textRepresentation() const override;
-	static QString circularPrimitiveTypeName(CircularPrimitiveType type);
-	Type typeIdentifier() const override;
-	QDataStream& serialize(QDataStream& stream) const override;
-	QDataStream& deserialize(QDataStream& stream) override;
-	QString toLDrawCode() const override;
-	QString iconName() const override;
-	QString typeName() const override;
-	void getPolygons(std::vector<gl::Polygon>& polygons, GetPolygonsContext*) const override;
-	float fraction() const;
-	CircularPrimitiveType type = Circle;
-	int segments = 16;
-	int divisions = 16;
-};
-
--- a/src/linetypes/compoundobject.cpp	Wed May 25 20:36:34 2022 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,133 +0,0 @@
-#include "compoundobject.h"
-#include "documentmanager.h"
-#include "invert.h"
-#include "polygoncache.h"
-
-ldraw::CompoundObject::CompoundObject
-(
-	const glm::mat4& transformation,
-	const Color color
-) :
-	ColoredObject{color},
-	transformation{transformation}
-{
-}
-
-QVariant ldraw::CompoundObject::getProperty(Property property) const
-{
-	switch (property)
-	{
-	case Property::Transformation:
-		return QVariant::fromValue(this->transformation);
-	case Property::IsInverted:
-		return this->isInverted;
-	default:
-		return ColoredObject::getProperty(property);
-	}
-}
-
-void ldraw::CompoundObject::setProperty(SetPropertyResult* result, const PropertyKeyValue& pair)
-{
-	LDRAW_OBJECT_HANDLE_SET_PROPERTY(Transformation, {this->transformation = value;});
-	LDRAW_OBJECT_HANDLE_SET_PROPERTY(IsInverted, {this->isInverted = value;});
-	ldraw::ColoredObject::setProperty(result, pair);
-}
-
-glm::vec3 ldraw::CompoundObject::position() const
-{
-	return this->transformation[3];
-}
-
-void ldraw::CompoundObject::invert(GetPolygonsContext *context)
-{
-	const std::optional<Axis> flatDimension = context ? this->flatDimension(context) : std::optional<Axis>{};
-	if (flatDimension.has_value())
-	{
-		glm::mat4 matrix = glm::identity<glm::mat4>();
-		matrix[*flatDimension][*flatDimension] = -1.0f;
-		this->transformation *= matrix;
-	}
-	else
-	{
-		this->isInverted = not this->isInverted;
-	}
-}
-
-
-QDataStream& ldraw::CompoundObject::serialize(QDataStream &stream) const
-{
-	return ColoredObject::serialize(stream) << this->transformation << this->isInverted;
-}
-
-QDataStream& ldraw::CompoundObject::deserialize(QDataStream &stream)
-{
-	return ColoredObject::deserialize(stream) >> this->transformation >> this->isInverted;
-}
-
-/**
- * @brief Finds out in which dimension the object is flat in. In that dimension all vertices have a value of 0.
- * If the object is not flat, this does not return a value.
- * @param model Model to find out flatness of
- * @param documents Where to look for subfiles
- * @returns dimension the model is flat in, if such dimension exists, no value otherwise.
- */
-std::optional<Axis> ldraw::CompoundObject::flatDimension(GetPolygonsContext *context) const
-{
-	// The dimensions that this model is potentially flat in.
-	QVector<Axis> dimensions = {X, Y, Z};
-	std::vector<gl::Polygon> polygons;
-	this->getPolygons(polygons, context);
-	for (const gl::Polygon& polygon : polygons)
-	{
-		for (unsigned int i = 0; i < polygon.numPolygonVertices(); i += 1)
-		{
-			const glm::vec3& v_i = polygon.vertices[i];
-			if (not qFuzzyCompare(v_i.x, 0.0f))
-			{
-				dimensions.removeOne(X);
-			}
-			if (not qFuzzyCompare(v_i.y, 0.0f))
-			{
-				dimensions.removeOne(Y);
-			}
-			if (not qFuzzyCompare(v_i.z, 0.0f))
-			{
-				dimensions.removeOne(Z);
-			}
-		}
-		// If there are no more dimensions left, we can exit the loop.
-		if (dimensions.isEmpty())
-		{
-			break;
-		}
-	}
-	if (dimensions.size() == 1)
-	{
-		// The model is flat in one dimension, return that.
-		// If the model is flat in two or three dimensions, it's not really a valid model.
-		return dimensions[0];
-	}
-	else
-	{
-		// The model is not flat.
-		return {};
-	}
-}
-
-QString ldraw::CompoundObject::transformToBareString() const
-{
-	return utility::format(
-		"%1 %2 %3 %4 %5 %6 %7 %8 %9 %10 %11 %12",
-		this->transformation[3][0],
-		this->transformation[3][1],
-		this->transformation[3][2],
-		this->transformation[0][0],
-		this->transformation[1][0],
-		this->transformation[2][0],
-		this->transformation[0][1],
-		this->transformation[1][1],
-		this->transformation[2][1],
-		this->transformation[0][2],
-		this->transformation[1][2],
-		this->transformation[2][2]);
-}
--- a/src/linetypes/compoundobject.h	Wed May 25 20:36:34 2022 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +0,0 @@
-#pragma once
-#include "object.h"
-
-class Model;
-
-namespace ldraw
-{
-	class CompoundObject;
-}
-
-/**
- * @brief An abstract object that has a transformation mmatrix and can be inverted.
- * It can be inlined into multiple objects.
- */
-class ldraw::CompoundObject : public ColoredObject
-{
-public:
-	CompoundObject() = default;
-	CompoundObject(
-		const glm::mat4& transformation,
-		const Color color = ldraw::MAIN_COLOR);
-	QVariant getProperty(Property property) const override;
-	glm::vec3 position() const;
-	void invert(GetPolygonsContext*context) override;
-	Model* resolve(const ModelId callingModelId, DocumentManager* documents) const;
-	QDataStream& serialize(QDataStream& stream) const override;
-	QDataStream& deserialize(QDataStream& stream) override;
-	std::optional<Axis> flatDimension(GetPolygonsContext *context) const;
-	glm::mat4 transformation;
-	QString transformToBareString() const;
-	bool isInverted = false;
-protected:
-	void setProperty(SetPropertyResult* result, const PropertyKeyValue& pair) override;
-};
--- a/src/linetypes/conditionaledge.cpp	Wed May 25 20:36:34 2022 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,36 +0,0 @@
-#include "conditionaledge.h"
-
-QString ldraw::ConditionalEdge::textRepresentation() const
-{
-	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]));
-}
-
-ldraw::Object::Type ldraw::ConditionalEdge::typeIdentifier() const
-{
-	return Type::ConditionalEdge;
-}
-
-QString ldraw::ConditionalEdge::toLDrawCode() const
-{
-	return utility::format(
-		"5 %1 %2 %3 %4 %5",
-		this->colorIndex.index,
-		utility::vertexToString(this->points[0]),
-		utility::vertexToString(this->points[1]),
-		utility::vertexToString(this->points[2]),
-			utility::vertexToString(this->points[3]));
-}
-
-QString ldraw::ConditionalEdge::iconName() const
-{
-	return ":/icons/linetype-conditionaledge.png";
-}
-
-QString ldraw::ConditionalEdge::typeName() const
-{
-	return QObject::tr("conditional edge");
-}
--- a/src/linetypes/conditionaledge.h	Wed May 25 20:36:34 2022 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,18 +0,0 @@
-#pragma once
-#include "polygonobject.h"
-
-namespace ldraw
-{
-	class ConditionalEdge;
-}
-
-class ldraw::ConditionalEdge : public PolygonObject<4>
-{
-public:
-	using PolygonObject<4>::PolygonObject;
-	QString textRepresentation() const override;
-	Type typeIdentifier() const override;
-	QString toLDrawCode() const override;
-	QString iconName() const override;
-	QString typeName() const override;
-};
--- a/src/linetypes/edge.cpp	Wed May 25 20:36:34 2022 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-#include "edge.h"
-
-QString ldraw::Edge::textRepresentation() const
-{
-	return utility::format(
-		"%1 %2",
-		utility::vertexToStringParens(this->points[0]),
-		utility::vertexToStringParens(this->points[1]));
-}
-
-void ldraw::Edge::getPolygons(
-	std::vector<gl::Polygon>& polygons,
-	GetPolygonsContext* context) const
-{
-	Q_UNUSED(context)
-	polygons.push_back(gl::edgeLine(this->points[0], this->points[1], this->colorIndex, this->id));
-}
-
-ldraw::Object::Type ldraw::Edge::typeIdentifier() const
-{
-	return Type::EdgeLine;
-}
-
-QString ldraw::Edge::toLDrawCode() const
-{
-	return utility::format(
-		"2 %1 %2 %3",
-		this->colorIndex.index,
-		utility::vertexToString(this->points[0]),
-			utility::vertexToString(this->points[1]));
-}
-
-QString ldraw::Edge::iconName() const
-{
-	return ":/icons/linetype-edgeline.png";
-}
-
-QString ldraw::Edge::typeName() const
-{
-	return QObject::tr("edge");
-}
--- a/src/linetypes/edge.h	Wed May 25 20:36:34 2022 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,19 +0,0 @@
-#pragma once
-#include "polygonobject.h"
-
-namespace ldraw
-{
-	class Edge;
-}
-
-class ldraw::Edge : public PolygonObject<2>
-{
-public:
-	using PolygonObject::PolygonObject;
-	QString textRepresentation() const override;
-	void getPolygons(std::vector<gl::Polygon>& polygons, GetPolygonsContext* context) const override;
-	Type typeIdentifier() const override;
-	QString toLDrawCode() const override;
-	QString iconName() const override;
-	QString typeName() const override;
-};
--- a/src/linetypes/errorline.cpp	Wed May 25 20:36:34 2022 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,73 +0,0 @@
-#include <QBrush>
-#include "errorline.h"
-
-ldraw::ErrorLine::ErrorLine(QStringView text, QStringView message) :
-	text{text.toString()},
-	message{message.toString()}
-{
-}
-
-QVariant ldraw::ErrorLine::getProperty(Property property) const
-{
-	switch (property)
-	{
-	case Property::Text:
-		return this->text;
-	case Property::ErrorMessage:
-		return this->message;
-	default:
-		return Object::getProperty(property);
-	}
-}
-
-void ldraw::ErrorLine::setProperty(SetPropertyResult* result, const PropertyKeyValue& pair)
-{
-	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
-{
-	return this->text;
-}
-
-QBrush ldraw::ErrorLine::textRepresentationForeground() const
-{
-	return QBrush{Qt::yellow};
-}
-
-QBrush ldraw::ErrorLine::textRepresentationBackground() const
-{
-	return QBrush{Qt::red};
-}
-
-ldraw::Object::Type ldraw::ErrorLine::typeIdentifier() const
-{
-	return Type::ErrorLine;
-}
-
-QDataStream &ldraw::ErrorLine::serialize(QDataStream &stream) const
-{
-	return ldraw::Object::serialize(stream) << this->text;
-}
-
-QDataStream &ldraw::ErrorLine::deserialize(QDataStream &stream)
-{
-	return ldraw::Object::deserialize(stream) >> this->text;
-}
-
-QString ldraw::ErrorLine::toLDrawCode() const
-{
-	return this->text;
-}
-
-QString ldraw::ErrorLine::iconName() const
-{
-	return ":/icons/linetype-errorline.png";
-}
-
-QString ldraw::ErrorLine::typeName() const
-{
-	return QObject::tr("error line");
-}
--- a/src/linetypes/errorline.h	Wed May 25 20:36:34 2022 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,28 +0,0 @@
-#pragma once
-#include "object.h"
-
-namespace ldraw
-{
-	class ErrorLine;
-}
-
-class ldraw::ErrorLine : public Object
-{
-public:
-	using BaseClass = Object;
-	ErrorLine(QStringView text = u"", QStringView message = u"");
-	QVariant getProperty(Property property) const override;
-	QString textRepresentation() const override;
-	QBrush textRepresentationForeground() const override;
-	QBrush textRepresentationBackground() const override;
-	Type typeIdentifier() const override;
-	QDataStream& serialize(QDataStream& stream) const override;
-	QDataStream& deserialize(QDataStream& stream) override;
-	QString toLDrawCode() const override;
-	QString iconName() const override;
-	QString typeName() const override;
-	QString text;
-	QString message;
-protected:
-	void setProperty(SetPropertyResult* result, const PropertyKeyValue& pair) override;
-};
--- a/src/linetypes/metacommand.cpp	Wed May 25 20:36:34 2022 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,68 +0,0 @@
-#include "metacommand.h"
-
-ldraw::MetaCommand::MetaCommand(QStringView text) :
-	Object{},
-	storedText{text.toString()} {}
-
-QVariant ldraw::MetaCommand::getProperty(Property property) const
-{
-	switch (property)
-	{
-	case Property::Text:
-		return storedText;
-	default:
-		return Object::getProperty(property);
-	}
-}
-
-void ldraw::MetaCommand::setProperty(ldraw::Object::SetPropertyResult* result, const PropertyKeyValue& pair)
-{
-	LDRAW_OBJECT_HANDLE_SET_PROPERTY(Text, {this->storedText = value;});
-	BaseClass::setProperty(result, pair);
-}
-
-QString ldraw::MetaCommand::textRepresentation() const
-{
-	return this->storedText;
-}
-
-ldraw::Object::Type ldraw::MetaCommand::typeIdentifier() const
-{
-	return Type::MetaCommand;
-}
-
-/**
- * @brief @overload ldraw::Object::serialize
- * @param stream
- * @return stream
- */
-QDataStream& ldraw::MetaCommand::serialize(QDataStream& stream) const
-{
-	return ldraw::Object::serialize(stream) << this->storedText;
-}
-
-/**
- * @brief @overload ldraw::Object::deserialize
- * @param stream
- * @return stream
- */
-QDataStream& ldraw::MetaCommand::deserialize(QDataStream& stream)
-{
-	return ldraw::Object::deserialize(stream) >> this->storedText;
-}
-
-QString ldraw::MetaCommand::toLDrawCode() const
-{
-	return "0 " + this->storedText;
-}
-
-QString ldraw::MetaCommand::iconName() const
-{
-	return ":/icons/chatbubble-ellipses-outline.png";
-}
-
-QString ldraw::MetaCommand::typeName() const
-{
-	return QObject::tr("comment");
-}
-
--- a/src/linetypes/metacommand.h	Wed May 25 20:36:34 2022 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,25 +0,0 @@
-#pragma once
-#include "object.h"
-
-namespace ldraw
-{
-	class MetaCommand;
-}
-class ldraw::MetaCommand : public Object
-{
-public:
-	using BaseClass = Object;
-	MetaCommand() = default;
-	MetaCommand(QStringView text);
-	QVariant getProperty(Property property) const override;
-	QString textRepresentation() const override;
-	QString storedText = "";
-	Type typeIdentifier() const override;
-	QDataStream& serialize(QDataStream& stream) const override;
-	QDataStream& deserialize(QDataStream& stream) override;
-	QString toLDrawCode() const override;
-	QString iconName() const override;
-	QString typeName() const override;
-protected:
-	void setProperty(SetPropertyResult* result, const PropertyKeyValue& pair) override;
-};
--- a/src/linetypes/object.cpp	Wed May 25 20:36:34 2022 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,168 +0,0 @@
-#include <QBrush>
-#include <QFont>
-#include "object.h"
-#include "widgets/vec3editor.h"
-#include "modeleditor.h"
-
-static std::int32_t getIdForNewObject()
-{
-	static std::int32_t id = 0;
-	id += 1;
-	return id;
-}
-
-ldraw::Object::Object() :
-	id {getIdForNewObject()}
-{
-}
-
-ldraw::Object::~Object()
-{
-}
-
-bool ldraw::Object::hasColor() const
-{
-	return false;
-}
-
-QVariant ldraw::Object::getProperty(Property id) const
-{
-	Q_UNUSED(id);
-	return {};
-}
-
-void ldraw::Object::setProperty(SetPropertyResult* result, const PropertyKeyValue& pair)
-{
-	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
-{
-	return {};
-}
-
-QBrush ldraw::Object::textRepresentationBackground() const
-{
-	return {};
-}
-
-QFont ldraw::Object::textRepresentationFont() const
-{
-	return {};
-}
-
-void ldraw::Object::getPolygons(std::vector<gl::Polygon>& polygons, GetPolygonsContext* context) const
-{
-	Q_UNUSED(polygons)
-	Q_UNUSED(context)
-}
-
-const glm::vec3& ldraw::Object::getPoint(int index) const
-{
-	Q_UNUSED(index);
-	throw BadPointIndex{};
-}
-
-/**
- * @brief Serializes the object into a stream of bytes for internal storage.
- * @param stream Data stream to serialize into
- */
-QDataStream& ldraw::Object::serialize(QDataStream &stream) const
-{
-	return stream << static_cast<int>(this->typeIdentifier());
-}
-
-/**
- * @brief Deserializes the object from a stream of bytes coming from internal storage.
- * @param stream Data stream to serialize from.
- * @note @c ldraw::Object::serialize will insert a type enumerator. Before calling this function,
- * this enumerator is assumed to have been handled as the object class needs to be initialized based
- * on the value of this enumerator.
- */
-QDataStream& ldraw::Object::deserialize(QDataStream &stream)
-{
-	return stream;
-}
-
-QString ldraw::Object::iconName() const
-{
-	return "";
-}
-
-ldraw::ColoredObject::ColoredObject(const Color color_index) :
-	colorIndex{color_index}
-{
-}
-
-bool ldraw::ColoredObject::hasColor() const
-{
-	return true;
-}
-
-QVariant ldraw::ColoredObject::getProperty(Property id) const
-{
-	switch (id)
-	{
-	case Property::Color:
-		return QVariant::fromValue<Color>(colorIndex);
-	default:
-		return Object::getProperty(id);
-	}
-}
-
-void ldraw::ColoredObject::setProperty(SetPropertyResult* result, const PropertyKeyValue& pair)
-{
-	LDRAW_OBJECT_HANDLE_SET_PROPERTY(Color, {colorIndex = value;});
-	Object::setProperty(result, pair);
-}
-
-/**
- * @brief @overload @c ldraw::Object::serialize
- * @param stream
- * @return stream
- */
-QDataStream& ldraw::ColoredObject::serialize(QDataStream& stream) const
-{
-	return Object::serialize(stream) << this->colorIndex;
-}
-
-/**
- * @brief @overload @c ldraw::Object::deserialize
- * @param stream
- * @return stream
- */
-QDataStream& ldraw::ColoredObject::deserialize(QDataStream& stream)
-{
-	return Object::deserialize(stream) >> this->colorIndex;
-}
-
-QString ldraw::Empty::textRepresentation() const
-{
-	return "";
-}
-
-ldraw::Object::Type ldraw::Empty::typeIdentifier() const
-{
-	return Type::Empty;
-}
-
-QString ldraw::Empty::toLDrawCode() const
-{
-	return "";
-}
-
-QString ldraw::Empty::typeName() const
-{
-	return QObject::tr("empty");
-}
--- a/src/linetypes/object.h	Wed May 25 20:36:34 2022 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,152 +0,0 @@
-#pragma once
-#include <QPointF>
-#include <QString>
-#include <QStringView>
-#include "main.h"
-#include "colors.h"
-#include "gl/common.h"
-#include "linetypes/propertygenerics.h"
-
-class Model;
-
-namespace ldraw
-{
-	struct GetPolygonsContext;
-	class Object;
-	class ColoredObject;
-	class Empty;
-	class UnhandledProperty;
-}
-
-class DocumentManager;
-
-struct ldraw::GetPolygonsContext
-{
-	::ModelId modelId;
-	::DocumentManager* documents;
-};
-
-class ldraw::Object
-{
-public:
-	enum class SetPropertyResult
-	{
-		Success = 0,
-		PropertyNotHandled
-	};
-	/**
-	 * @brief Enumerates different object types
-	 */
-	enum class Type
-	{
-		Empty,
-		Comment,
-		MetaCommand,
-		ErrorLine,
-		SubfileReference,
-		EdgeLine,
-		ConditionalEdge,
-		Triangle,
-		Quadrilateral,
-		CircularPrimitive,
-	};
-	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 bool hasColor() const;
-	virtual QVariant getProperty(Property id) const;
-	template<ldraw::Property property>
-	PropertyType<property> getProperty() const;
-	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(GetPolygonsContext*) {}
-	virtual int numPoints() const { return 0; }
-	virtual const glm::vec3& getPoint(int index) const;
-	virtual QDataStream& serialize(QDataStream& stream) const;
-	virtual QDataStream& deserialize(QDataStream& stream);
-	virtual Type typeIdentifier() const = 0;
-	virtual QString toLDrawCode() const = 0;
-	virtual QString iconName() const;
-	virtual QString typeName() const = 0;
-
-protected:
-	template<Property property, typename Function>
-	void handle(SetPropertyResult* result, const PropertyKeyValue& pair, Function function);
-	virtual void setProperty(SetPropertyResult* result, const PropertyKeyValue& pair);
-};
-
-/**
- * @brief Tests whether the object is exactly of the specified type
- * @tparam R Type of LDraw line type object to test for
- * @param object Object to test
- * @returns whether the type of the object specified by @c id is the same type as R. Returns false if it is a subclass.
- */
-template<typename R>
-bool isA(const ldraw::Object* object)
-{
-	const std::type_info& a = typeid(*object);
-	const std::type_info& b = typeid(R);
-	return a == b;
-}
-
-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;
-	}
-}
-
-template<ldraw::Property property>
-ldraw::PropertyType<property> ldraw::Object::getProperty() const
-{
-	return this->getProperty(property).value<ldraw::PropertyType<property>>();
-}
-
-class ldraw::ColoredObject : public Object
-{
-public:
-	ColoredObject(const Color colorIndex = ldraw::MAIN_COLOR);
-	bool hasColor() const override final;
-	QVariant getProperty(Property id) const override;
-	QDataStream &serialize(QDataStream& stream) const override;
-	QDataStream& deserialize(QDataStream& stream) override;
-	Color colorIndex = ldraw::MAIN_COLOR;
-protected:
-	void setProperty(SetPropertyResult* result, const PropertyKeyValue& pair) override;
-};
-
-/**
- * @brief Represents an empty line.
- */
-class ldraw::Empty : public Object
-{
-	QString textRepresentation() const override;
-	Type typeIdentifier() const override;
-	QString toLDrawCode() const override;
-	QString typeName() const override;
-};
--- a/src/linetypes/polygonobject.h	Wed May 25 20:36:34 2022 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,88 +0,0 @@
-#pragma once
-#include "object.h"
-#include "widgets/vec3editor.h"
-#include "model.h"
-#include "modeleditor.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) override
-	{
-		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);
-	}
-	QDataStream &serialize(QDataStream& stream) const override
-	{
-		ColoredObject::serialize(stream);
-		for (const glm::vec3& point : this->points)
-		{
-			stream << point;
-		}
-		return stream;
-	}
-	QDataStream& deserialize(QDataStream& stream) override
-	{
-		ColoredObject::deserialize(stream);
-		for (glm::vec3& point : this->points)
-		{
-			stream >> point;
-		}
-		return stream;
-	}
-	std::array<glm::vec3, N> points;
-};
--- a/src/linetypes/propertygenerics.h	Wed May 25 20:36:34 2022 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,168 +0,0 @@
-#pragma once
-#include "main.h"
-#include "colors.h"
-
-class Vec3Editor;
-class MatrixEditor;
-
-namespace ldraw
-{
-	enum CircularPrimitiveType
-	{
-		Circle,
-		Disc
-	};
-	enum class Property;
-	struct PropertyKeyValue;
-	template<Property property>
-	struct PropertyTraits
-	{
-		static constexpr bool defined = false;
-	};
-}
-
-Q_DECLARE_METATYPE(ldraw::CircularPrimitiveType)
-
-/**
- * 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
-	CircularPrimitiveType, // Type of circular primitive (circle, disc, ...)
-	Segments, // Amount of circular segments this primitive covers (numerator)
-	Divisions, // Amount of segments in the circle this primitive fits in (8, 16, 48, ...)
-};
-
-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)
-LDFORGE_DEFINE_PROPERTY_TYPE(CircularPrimitiveType, ldraw::CircularPrimitiveType)
-LDFORGE_DEFINE_PROPERTY_TYPE(Segments, int)
-LDFORGE_DEFINE_PROPERTY_TYPE(Divisions, int)
-
-#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;
-
-	constexpr int MAX_POINTS = 4;
-
-	struct PropertyKeyValue
-	{
-		Property key;
-		QVariant value;
-	};
-
-	constexpr Property pointProperty(int n)
-	{
-		Q_ASSERT(n >= 0 and n < MAX_POINTS);
-		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	Wed May 25 20:36:34 2022 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +0,0 @@
-#include "quadrilateral.h"
-
-QString ldraw::Quadrilateral::textRepresentation() const
-{
-	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]));
-}
-
-void ldraw::Quadrilateral::getPolygons(
-	std::vector<gl::Polygon>& polygons,
-	GetPolygonsContext* context) const
-{
-	Q_UNUSED(context)
-	polygons.push_back(gl::quadrilateral(
-		this->points[0],
-		this->points[1],
-		this->points[2],
-		this->points[3],
-		this->colorIndex,
-		this->id));
-}
-
-void ldraw::Quadrilateral::invert(GetPolygonsContext *)
-{
-	//    0 1 2 3
-	// -> 2 1 0 3
-	std::swap(this->points[0], this->points[2]);
-}
-
-ldraw::Object::Type ldraw::Quadrilateral::typeIdentifier() const
-{
-	return Type::Quadrilateral;
-}
-
-QString ldraw::Quadrilateral::toLDrawCode() const
-{
-	return utility::format(
-		"4 %1 %2 %3 %4 %5",
-		this->colorIndex.index,
-		utility::vertexToString(this->points[0]),
-		utility::vertexToString(this->points[1]),
-		utility::vertexToString(this->points[2]),
-			utility::vertexToString(this->points[3]));
-}
-
-QString ldraw::Quadrilateral::iconName() const
-{
-	return ":/icons/linetype-quadrilateral.png";
-}
-
-QString ldraw::Quadrilateral::typeName() const
-{
-	return QObject::tr("quadrilateral");
-}
--- a/src/linetypes/quadrilateral.h	Wed May 25 20:36:34 2022 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,20 +0,0 @@
-#pragma once
-#include "polygonobject.h"
-
-namespace ldraw
-{
-	class Quadrilateral;
-}
-
-class ldraw::Quadrilateral : public PolygonObject<4>
-{
-public:
-	using PolygonObject<4>::PolygonObject;
-	QString textRepresentation() const override;
-	void getPolygons(std::vector<gl::Polygon>& polygons, GetPolygonsContext* context) const override;
-	void invert(GetPolygonsContext *) override;
-	Type typeIdentifier() const override;
-	QString toLDrawCode() const override;
-	QString iconName() const override;
-	QString typeName() const override;
-};
--- a/src/linetypes/subfilereference.cpp	Wed May 25 20:36:34 2022 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,128 +0,0 @@
-#include "subfilereference.h"
-#include "documentmanager.h"
-#include "invert.h"
-#include "polygoncache.h"
-
-ldraw::SubfileReference::SubfileReference
-(
-	const glm::mat4& transformation,
-	const QString& referenceName,
-	const Color color
-) :
-	CompoundObject{transformation, color},
-	referenceName{referenceName}
-{
-}
-
-QVariant ldraw::SubfileReference::getProperty(Property property) const
-{
-	switch (property)
-	{
-	case Property::ReferenceName:
-		return this->referenceName;
-	default:
-		return CompoundObject::getProperty(property);
-	}
-}
-
-void ldraw::SubfileReference::setProperty(SetPropertyResult* result, const PropertyKeyValue& pair)
-{
-	LDRAW_OBJECT_HANDLE_SET_PROPERTY(ReferenceName, {this->referenceName = value;});
-	ldraw::CompoundObject::setProperty(result, pair);
-}
-
-QString ldraw::SubfileReference::textRepresentation() const
-{
-	return this->referenceName + " " + utility::vertexToStringParens(this->position());
-}
-
-void ldraw::SubfileReference::getPolygons
-(
-	std::vector<gl::Polygon>& polygons,
-	GetPolygonsContext* context
-) const
-{
-	Model* dependency = this->resolve(context->modelId, context->documents);
-	PolygonCache* cache = nullptr;
-	if (dependency != nullptr)
-	{
-		const auto dependencyModelId = context->documents->findIdForModel(dependency);
-		if (dependencyModelId.has_value())
-		{
-			cache = context->documents->getPolygonCacheForModel(dependencyModelId.value());
-		}
-	}
-	if (cache != nullptr)
-	{
-		const bool needInverting = glm::determinant(this->transformation) < 0;
-		const std::vector<gl::Polygon> modelPolygons = getCachedPolygons(
-			cache,
-			dependency,
-			context->documents);
-		polygons.reserve(polygons.size() + modelPolygons.size());
-		for (gl::Polygon polygon : modelPolygons)
-		{
-			for (unsigned int i = 0; i < polygon.numPolygonVertices(); i += 1)
-			{
-				glm::vec4 vertex {polygon.vertices[i], 1};
-				vertex = this->transformation * vertex;
-				polygon.vertices[i] = vertex;
-			}
-			if (needInverting != this->isInverted)
-			{
-				gl::invert(polygon);
-			}
-			if (polygon.color == ldraw::MAIN_COLOR)
-			{
-				polygon.color = this->colorIndex;
-			}
-			polygon.id = this->id;
-			polygons.push_back(polygon);
-		}
-	}
-}
-
-Model* ldraw::SubfileReference::resolve(const ModelId callingModelId, DocumentManager* documents) const
-{
-	return documents->findDependencyByName(callingModelId, this->referenceName);
-}
-
-ldraw::Object::Type ldraw::SubfileReference::typeIdentifier() const
-{
-	return Type::SubfileReference;
-}
-
-QDataStream& ldraw::SubfileReference::serialize(QDataStream &stream) const
-{
-	return CompoundObject::serialize(stream) << this->referenceName;
-}
-
-QDataStream& ldraw::SubfileReference::deserialize(QDataStream &stream)
-{
-	return CompoundObject::deserialize(stream) >> this->referenceName;
-}
-
-QString ldraw::SubfileReference::toLDrawCode() const
-{
-	QString result;
-	if (this->isInverted)
-	{
-		result += "0 BFC INVERTNEXT\r\n";
-	}
-	result += utility::format(
-		"1 %1 %2 %3",
-		this->colorIndex.index,
-		this->transformToBareString(),
-		this->referenceName);
-	return result;
-}
-
-QString ldraw::SubfileReference::iconName() const
-{
-	return ":/icons/linetype-subfile.png";
-}
-
-QString ldraw::SubfileReference::typeName() const
-{
-	return QObject::tr("subfile reference");
-}
--- a/src/linetypes/subfilereference.h	Wed May 25 20:36:34 2022 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,33 +0,0 @@
-#pragma once
-#include "compoundobject.h"
-#include "invert.h"
-
-class Model;
-
-namespace ldraw
-{
-	class SubfileReference;
-}
-
-class ldraw::SubfileReference : public CompoundObject
-{
-public:
-	SubfileReference() = default;
-	SubfileReference(
-		const glm::mat4& transformation,
-		const QString &referenceName,
-		const Color color = ldraw::MAIN_COLOR);
-	QVariant getProperty(Property property) const override;
-	QString textRepresentation() const override;
-	void getPolygons(std::vector<gl::Polygon>& polygons, GetPolygonsContext* context) const override;
-	Model* resolve(const ModelId callingModelId, DocumentManager* documents) const;
-	Type typeIdentifier() const override;
-	QDataStream& serialize(QDataStream& stream) const override;
-	QDataStream& deserialize(QDataStream& stream) override;
-	QString toLDrawCode() const override;
-	QString iconName() const override;
-	QString typeName() const override;
-	QString referenceName;
-protected:
-	void setProperty(SetPropertyResult* result, const PropertyKeyValue& pair) override;
-};
--- a/src/linetypes/triangle.cpp	Wed May 25 20:36:34 2022 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,54 +0,0 @@
-#include "triangle.h"
-
-QString ldraw::Triangle::textRepresentation() const
-{
-	return utility::format("%1 %2 %3",
-		utility::vertexToStringParens(points[0]),
-		utility::vertexToStringParens(points[1]),
-		utility::vertexToStringParens(points[2]));
-}
-
-void ldraw::Triangle::getPolygons(
-	std::vector<gl::Polygon>& polygons,
-	GetPolygonsContext* context) const
-{
-	Q_UNUSED(context)
-	polygons.push_back(gl::triangle(
-		this->points[0],
-		this->points[1],
-		this->points[2],
-		this->colorIndex,
-		this->id));
-}
-
-void ldraw::Triangle::invert(GetPolygonsContext *)
-{
-	//    0 1 2
-	// -> 1 0 2
-	std::swap(this->points[0], this->points[1]);
-}
-
-ldraw::Object::Type ldraw::Triangle::typeIdentifier() const
-{
-	return Type::Triangle;
-}
-
-QString ldraw::Triangle::toLDrawCode() const
-{
-	return utility::format(
-		"3 %1 %2 %3 %4",
-		this->colorIndex.index,
-		utility::vertexToString(this->points[0]),
-		utility::vertexToString(this->points[1]),
-			utility::vertexToString(this->points[2]));
-}
-
-QString ldraw::Triangle::iconName() const
-{
-	return ":/icons/linetype-triangle.png";
-}
-
-QString ldraw::Triangle::typeName() const
-{
-	return QObject::tr("triangle");
-}
--- a/src/linetypes/triangle.h	Wed May 25 20:36:34 2022 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,21 +0,0 @@
-#pragma once
-#include "polygonobject.h"
-
-namespace ldraw
-{
-	class Triangle;
-}
-
-class ldraw::Triangle : public PolygonObject<3>
-{
-public:
-	using PolygonObject<3>::PolygonObject;
-	QString textRepresentation() const override;
-	void getPolygons(std::vector<gl::Polygon>& polygons, GetPolygonsContext* context) const override;
-	void invert(GetPolygonsContext*) override;
-	Type typeIdentifier() const override;
-	QString toLDrawCode() const override;
-	QString iconName() const override;
-	QString typeName() const override;
-};
-
--- a/src/main.cpp	Wed May 25 20:36:34 2022 +0300
+++ b/src/main.cpp	Mon Jun 06 22:01:22 2022 +0300
@@ -19,30 +19,21 @@
 #include <QApplication>
 #include "mainwindow.h"
 #include "version.h"
-#include <iostream>
-#include <QMessageBox>
-#include <QLabel>
-#include <QImageReader>
-#include <QPushButton>
 
-int main(int argc, char *argv[])
+static void doQtRegistrations()
 {
 	QCoreApplication::setApplicationName(::appName);
 	QCoreApplication::setOrganizationName("hecknology.net");
 	QCoreApplication::setOrganizationDomain("hecknology.net");
-	::qRegisterMetaTypeStreamOperators<Library>("Library");
-	::qRegisterMetaTypeStreamOperators<Libraries>("Libraries");
+	qRegisterMetaTypeStreamOperators<Library>("Library");
+	qRegisterMetaTypeStreamOperators<Libraries>("Libraries");
+}
+
+int main(int argc, char *argv[])
+{
+	doQtRegistrations();
 	QApplication app{argc, argv};
 	MainWindow mainwindow;
 	mainwindow.show();
 	return app.exec();
 }
-
-QDataStream& operator<<(QDataStream& stream, const glm::vec3& vec)
-{
-	return stream << vec.x << vec.y << vec.z;
-}
-QDataStream& operator>>(QDataStream& stream, glm::vec3& vec)
-{
-	return stream >> vec.x >> vec.y >> vec.z;
-}
--- a/src/main.h	Wed May 25 20:36:34 2022 +0300
+++ b/src/main.h	Mon Jun 06 22:01:22 2022 +0300
@@ -34,78 +34,6 @@
 	constexpr char mainwindow[] = "mainwindow";
 }
 
-namespace ldraw
-{
-	class Object;
-
-	// Uniquely identifies a model body object
-	template<typename T>
-	struct Id
-	{
-		std::int32_t value;
-		template<typename A, typename B>
-		static constexpr bool is_base_or_base_of = std::disjunction_v<std::is_base_of<A, B>, std::is_base_of<B, A>>;
-		template<typename R, typename = std::enable_if_t<is_base_or_base_of<T, R>>>
-		constexpr bool operator<(ldraw::Id<R> other) const
-		{
-			return this->value < other.value;
-		}
-		friend constexpr unsigned int qHash(ldraw::Id<T> id)
-		{
-			return qHash(id.value);
-		}
-		// Allow comparing ids as long as they are related
-		template<typename R, typename = std::enable_if_t<is_base_or_base_of<T, R>>>
-		friend bool operator==(ldraw::Id<T> one, ldraw::Id<R> other)
-		{
-			return one.value == other.value;
-		}
-		// Allow upcasting
-		template<typename R, typename = std::enable_if_t<std::is_base_of_v<R, T>>>
-		constexpr operator Id<R>() const
-		{
-			return Id<R>{this->value};
-		}
-	};
-
-	using id_t = Id<Object>;
-	using triangleid_t = Id<class Triangle>;
-	using quadrilateralid_t = Id<class Quadrilateral>;
-	using edgeid_t = Id<class EdgeLine>;
-	using conditionaledgeid_t = Id<class ConditionalEdge>;
-	using subfileid_t = Id<class SubfileReference>;
-	using commentid_t = Id<class Comment>;
-	using metacommandid_t = Id<class MetaCommand>;
-
-	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>
-	inline bool operator==(Id<T> one, decltype(NULL_ID))
-	{
-		return one.value == 0;
-	}
-
-	template<typename T>
-	inline bool operator!=(Id<T> one, decltype(NULL_ID))
-	{
-		return one.value != 0;
-	}
-
-	template<typename T>
-	inline bool operator<(Id<T> one, decltype(NULL_ID))
-	{
-		return one.value < 0;
-	}
-}
-
 constexpr std::size_t operator""_z(const unsigned long long int x)
 {
 	return static_cast<std::size_t>(x);
@@ -262,8 +190,6 @@
 	return qHash(value.value);
 }
 
-using ModelId = TypeValue<int, struct TypeValueModelId>;
-
 /**
  * Iterates a @c glm::mat
  */
@@ -279,8 +205,15 @@
 	}
 }
 
-QDataStream& operator<<(QDataStream&, const glm::vec3&);
-QDataStream& operator>>(QDataStream&, glm::vec3&);
+inline QDataStream& operator<<(QDataStream& stream, const glm::vec3& vec)
+{
+	return stream << vec.x << vec.y << vec.z;
+}
+
+inline QDataStream& operator>>(QDataStream& stream, glm::vec3& vec)
+{
+	return stream >> vec.x >> vec.y >> vec.z;
+}
 
 template<int X, int Y, typename T, glm::qualifier Q>
 QDataStream& operator<<(QDataStream& stream, const glm::mat<X, Y, T, Q>& mat)
@@ -312,3 +245,27 @@
 	}
 	return result;
 }
+
+template<typename T>
+std::optional<T> pointerToOptional(const T* p)
+{
+	std::optional<T> result;
+	if (p != nullptr) {
+		result = *p;
+	}
+	return result;
+}
+
+template<typename T, typename R>
+void removeFromMap(std::map<T, R>& map, T&& key)
+{
+	const auto it = map.find(key);
+	if (it != map.end()) {
+		map.erase(it);
+	}
+}
+
+// some magic code from https://en.cppreference.com/w/cpp/utility/variant/visit
+// for use with std::visit
+template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
+template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
--- a/src/mainwindow.cpp	Wed May 25 20:36:34 2022 +0300
+++ b/src/mainwindow.cpp	Mon Jun 06 22:01:22 2022 +0300
@@ -28,7 +28,6 @@
 #include "document.h"
 #include "uiutilities.h"
 #include "widgets/colorselectdialog.h"
-#include "modeleditor.h"
 
 template<typename BaseType, typename MemberType, typename DataType>
 struct MemberData
@@ -193,9 +192,13 @@
 
 void MainWindow::openModelForEditing(const ModelId modelId)
 {
-	Document* document = new Document{this->documents.getModelById(modelId), &this->documents, this->colorTable};
+	EditorTabWidget* document = new EditorTabWidget{
+		this->documents.getModelById(modelId),
+		&this->documents,
+		this->colorTable,
+	};
 	document->canvas->setRenderPreferences(this->renderPreferences);
-	connect(document, &Document::newStatusText, [&](const QString& newStatusText)
+	connect(document, &EditorTabWidget::newStatusText, [&](const QString& newStatusText)
 	{
 		this->statusBar()->showMessage(newStatusText);
 	});
@@ -220,25 +223,25 @@
 	}
 }
 
-Document* MainWindow::currentDocument()
+EditorTabWidget* MainWindow::currentDocument()
 {
-	return qobject_cast<Document*>(this->ui->tabs->currentWidget());
+	return qobject_cast<EditorTabWidget*>(this->ui->tabs->currentWidget());
 }
 
-const Document* MainWindow::currentDocument() const
+const EditorTabWidget* MainWindow::currentDocument() const
 {
-	return qobject_cast<const Document*>(this->ui->tabs->currentWidget());
+	return qobject_cast<const EditorTabWidget*>(this->ui->tabs->currentWidget());
 }
 
 void MainWindow::handleDocumentSplitterChange()
 {
-	Document* currentDocument = this->currentDocument();
+	EditorTabWidget* currentDocument = this->currentDocument();
 	if (currentDocument != nullptr)
 	{
 		this->documentSplitterState = currentDocument->saveSplitterState();
 		for (int i = 0; i < this->ui->tabs->count(); i += 1)
 		{
-			Document* document = qobject_cast<Document*>(this->ui->tabs->widget(i));
+			EditorTabWidget* document = qobject_cast<EditorTabWidget*>(this->ui->tabs->widget(i));
 			if (document != nullptr and document != currentDocument)
 			{
 				document->restoreSplitterState(this->documentSplitterState);
@@ -363,7 +366,8 @@
  */
 void MainWindow::actionDelete()
 {
-	Document* document = this->currentDocument();
+	/*
+	EditorTabWidget* document = this->currentDocument();
 	if (document != nullptr)
 	{
 		std::unique_ptr<ModelEditor> modelEditor = document->editModel();
@@ -377,6 +381,7 @@
 			}
 		}
 	}
+	*/
 }
 
 /**
@@ -384,7 +389,8 @@
  */
 void MainWindow::actionInvert()
 {
-	Document* document = this->currentDocument();
+	/*
+	EditorTabWidget* document = this->currentDocument();
 	if (document != nullptr)
 	{
 		// TODO: simplify
@@ -405,6 +411,7 @@
 			}
 		}
 	}
+	*/
 }
 
 /**
@@ -415,7 +422,7 @@
 {
 	if (tabIndex >= 0 and tabIndex < this->ui->tabs->count())
 	{
-		Document* document = qobject_cast<Document*>(this->ui->tabs->widget(tabIndex));
+		EditorTabWidget* document = qobject_cast<EditorTabWidget*>(this->ui->tabs->widget(tabIndex));
 		if (document != nullptr)
 		{
 			this->closeDocument(document);
@@ -427,9 +434,9 @@
  * @brief Closes the specified document
  * @param document
  */
-void MainWindow::closeDocument(Document *document)
+void MainWindow::closeDocument(EditorTabWidget *document)
 {
-	std::optional<ModelId> modelId = this->documents.findIdForModel(&document->getModel());
+	std::optional<ModelId> modelId = this->documents.findIdForModel(document->model);
 	if (modelId.has_value())
 	{
 		this->documents.closeDocument(modelId.value());
@@ -439,10 +446,10 @@
 
 std::optional<ModelId> MainWindow::findCurrentModelId() const
 {
-	const Document* document = this->currentDocument();
+	const EditorTabWidget* document = this->currentDocument();
 	if (document != nullptr)
 	{
-		return this->documents.findIdForModel(&document->getModel());
+		return this->documents.findIdForModel(document->model);
 	}
 	else
 	{
@@ -492,7 +499,7 @@
 {
 	for (int i = 0; i < this->ui->tabs->count(); i += 1)
 	{
-		Document* document = qobject_cast<Document*>(this->ui->tabs->widget(i));
+		EditorTabWidget* document = qobject_cast<EditorTabWidget*>(this->ui->tabs->widget(i));
 		if (document != nullptr)
 		{
 			document->canvas->setRenderPreferences(this->renderPreferences);
--- a/src/mainwindow.h	Wed May 25 20:36:34 2022 +0300
+++ b/src/mainwindow.h	Mon Jun 06 22:01:22 2022 +0300
@@ -27,7 +27,7 @@
 #include "uiutilities.h"
 #include "ui/canvas.h"
 
-class Document;
+class EditorTabWidget;
 
 class MainWindow : public QMainWindow
 {
@@ -78,8 +78,8 @@
 	void openModelForEditing(const ModelId modelId);
 	static QString pathToTranslation(const QString& localeCode);
 	void loadColors();
-	Document *currentDocument();
-	const Document *currentDocument() const;
-	void closeDocument(Document* document);
+	EditorTabWidget *currentDocument();
+	const EditorTabWidget *currentDocument() const;
+	void closeDocument(EditorTabWidget* document);
 	std::optional<ModelId> findCurrentModelId() const;
 };
--- a/src/model.cpp	Wed May 25 20:36:34 2022 +0300
+++ b/src/model.cpp	Mon Jun 06 22:01:22 2022 +0300
@@ -16,254 +16,169 @@
  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include <QBrush>
-#include <QFile>
-#include <QFileInfo>
-#include <QFont>
-#include <QSaveFile>
 #include "model.h"
-#include "modeleditor.h"
-#include "documentmanager.h"
 
-/**
- * @brief Constructs a model
- * @param parent QObject parent to pass forward
- */
+QString modelElementToString(const ModelElement &element)
+{
+	return std::visit(overloaded{
+		[](const Colored<SubfileReference>& ref) {
+			return QStringLiteral("1 %1 %2 %3")
+				.arg(ref.color.index)
+				.arg(transformToString(ref.transformation))
+				.arg(ref.name);
+		},
+		[](const Colored<LineSegment>& seg) {
+			return QStringLiteral("2 %1 %2 %3")
+				.arg(seg.color.index)
+				.arg(vertexToString(seg.p1))
+				.arg(vertexToString(seg.p2));
+		},
+		[](const Colored<Triangle>& triangle) {
+			return QStringLiteral("3 %1 %2 %3 %4")
+				.arg(triangle.color.index)
+				.arg(vertexToString(triangle.p1))
+				.arg(vertexToString(triangle.p2))
+				.arg(vertexToString(triangle.p3));
+		},
+		[](const Colored<Quadrilateral>& quad) {
+			return QStringLiteral("4 %1 %2 %3 %4 %5")
+				.arg(quad.color.index)
+				.arg(vertexToString(quad.p1))
+				.arg(vertexToString(quad.p2))
+				.arg(vertexToString(quad.p3))
+				.arg(vertexToString(quad.p4));
+		},
+		[](const Colored<ConditionalEdge>& cedge) {
+			return QStringLiteral("5 %1 %2 %3 %4 %5")
+				.arg(cedge.color.index)
+				.arg(vertexToString(cedge.p1))
+				.arg(vertexToString(cedge.p2))
+				.arg(vertexToString(cedge.c1))
+				.arg(vertexToString(cedge.c2));
+		},
+		[](const Comment& comment) {
+			return "0 " + comment.text;
+		},
+		[](const Empty&) {
+			return QStringLiteral("");
+		},
+		[](const ParseError& parseError) {
+			return parseError.code;
+		},
+	}, element);
+}
+
 Model::Model(QObject *parent) :
 	QAbstractListModel{parent}
 {
 }
 
-/**
- * @returns the amount of elements in the model
- */
-int Model::size() const
+Model::~Model()
+{
+}
+
+ModelId Model::append(const ModelElement &value)
 {
-	return static_cast<int>(this->body.size());
+	const int position = static_cast<int>(this->body.size());
+	const ModelId id = this->runningId;
+	this->runningId.value += 1;
+	Q_EMIT this->beginInsertRows({}, position, position);
+	this->body.push_back({value, id});
+	this->positions[id] = position;
+	Q_EMIT this->endInsertRows();
+	return id;
+}
+
+const ModelElement &Model::at(int position) const
+{
+	return this->body[position].data;
 }
 
-/**
- * @brief Looks up the object ID at the specified index. If out of bounds, returns NULL_ID.
- * @param index Index of object to look up
- * @return object ID
- */
-ldraw::id_t Model::at(int index) const
+ModelId Model::idAt(int position) const
+{
+	return this->body[position].id;
+}
+
+void Model::assignAt(int position, const ModelElement &element)
 {
-	if (index >= 0 and index < this->size())
-	{
-		return this->body[index]->id;
-	}
-	else
-	{
-		return ldraw::NULL_ID;
+	this->body[position].data = element;
+	const QModelIndex index = this->index(position);
+	Q_EMIT this->dataChanged(index, index);
+}
+
+std::optional<int> Model::find(ModelId id) const
+{
+	return pointerToOptional(findInMap(this->positions, id));
+}
+
+void Model::remove(int index)
+{
+	if (index >= 0 and index < this->size()) {
+		Q_EMIT this->beginRemoveRows({}, index, index);
+		this->body.erase(this->body.begin() + index);
+		Q_EMIT this->endRemoveRows();
 	}
 }
 
-/**
- * @brief @overload QAbstractListModel::rowCount
- * @return size
- */
-int Model::rowCount(const QModelIndex&) const
+int Model::rowCount(const QModelIndex &) const
 {
 	return this->size();
 }
 
-/**
- * @brief @overload QAbstractListModel::data
- * @param index
- * @param role
- * @return QVariant
- */
-QVariant Model::data(const QModelIndex& index, int role) const
+QVariant Model::data(const QModelIndex &index, int role) const
 {
-	const ldraw::Object* object = (*this)[index.row()];
+	const int i = index.row();
 	switch(role)
 	{
+	/*
 	case Qt::DecorationRole:
 		return QPixmap{object->iconName()}.scaledToHeight(24);
+	*/
 	case Qt::DisplayRole:
-		return object->textRepresentation();
+		return modelElementToString(this->body[i].data);
+	/*
 	case Qt::ForegroundRole:
 		return object->textRepresentationForeground();
 	case Qt::BackgroundRole:
 		return object->textRepresentationBackground();
 	case Qt::FontRole:
 		return object->textRepresentationFont();
+	*/
 	default:
 		return {};
 	}
 }
 
-/**
- * @brief Finds the position of the specified object in the model
- * @param id Object id to look for
- * @return model index
- */
-QModelIndex Model::find(ldraw::id_t id) const
+const ModelElement &Model::operator[](int index) const
+{
+	return this->body[index].data;
+}
+
+int Model::size() const
 {
-	if (this->needObjectsByIdRebuild)
-	{
-		this->objectsById.clear();
-		for (std::size_t i = 0; i < this->body.size(); ++i)
-		{
-			this->objectsById[this->body[i]->id] = i;
-		}
-		this->needObjectsByIdRebuild = false;
-	}
-	const auto it = this->objectsById.find(id);
-	if (it != this->objectsById.end())
-	{
-		return this->index(it->second);
-	}
-	else
-	{
-		return {};
+	return this->body.size();
+}
+
+void save(const Model &model, QIODevice *device)
+{
+	QTextStream out{device};
+	for (int i = 0; i < model.size(); ++i) {
+		out << modelElementToString(model[i]) << "\r\n";
 	}
 }
 
 /**
- * @brief Gets an object id by position in the model
- * @param index Position of the object in the model
- * @return id
- */
-ldraw::id_t Model::idAt(const QModelIndex& index) const
-{
-	return (*this)[index.row()]->id;
-}
-
-#if 0
-/**
  * @brief Sets the path to the model
  * @param path New path to use
  */
-void Model::setPath(const QString &path)
+void updateHeaderNameField(Model& model, const QString &name)
 {
-	this->storedPath = path;
-	this->header.name = QFileInfo{path}.fileName();
 	// Update the "Name: 1234.dat" comment
-	if (this->body.size() >= 2)
-	{
-		const ldraw::id_t id = this->body[1]->id;
-		if (this->isA<ldraw::MetaCommand>(id))
-		{
-			const QString& textBody = this->body[1]->getProperty<ldraw::Property::Text>();
-			if (textBody.startsWith("Name: "))
-			{
-				auto editor = this->edit();
-				editor.setObjectProperty<ldraw::Property::Text>(id, "Name: " + this->header.name);
+	if (model.size() >= 2) {
+		if (const Comment* nameObject = std::get_if<Comment>(&model[1])) {
+			if (nameObject->text.startsWith("Name: ")) {
+				model[1] = Comment{"Name: " + name};
 			}
 		}
 	}
 }
-#endif
-
-/**
- * @brief Adds the given object into the model.
- * @param object r-value reference to the object
- */
-ldraw::id_t Model::append(ModelObjectPointer&& object)
-{
-	const int position = static_cast<int>(this->body.size());
-	Q_EMIT this->beginInsertRows({}, position, position);
-	this->body.push_back(std::move(object));
-	Q_EMIT this->endInsertRows();
-	const ldraw::id_t id = this->body.back()->id;
-	this->objectsById[id] = this->body.size() - 1;
-	return id;
-}
-
-/**
- * @brief Removes the object at the specified position
- * @param position
- */
-void Model::remove(int position)
-{
-	if (position >= 0 and position < signed_cast(this->body.size()))
-	{
-		Q_EMIT this->beginRemoveRows({}, position, position);
-		this->body.erase(std::begin(this->body) + position);
-		this->needObjectsByIdRebuild = true;
-		Q_EMIT this->endRemoveRows();
-	}
-}
-
-void Model::emitDataChangedSignal(int position)
-{
-	Q_EMIT this->dataChanged(this->index(position), this->index(position));
-}
-
-/**
- * @brief Gets the object pointer at the specified position
- * @param index Position of the object
- * @returns object pointer
- */
-ldraw::Object* Model::operator[](int index)
-{
-	if (index >= 0 and index < this->size())
-	{
-		return this->body[index].get();
-	}
-	else
-	{
-		throw std::out_of_range{"index out of range"};
-	}
-}
-
-/**
- * @brief Gets the object pointer at the specified position
- * @param index Position of the object
- * @returns object pointer
- */
-const ldraw::Object* Model::operator[](int index) const
-{
-	if (index >= 0 and index < this->size())
-	{
-		return this->body[index].get();
-	}
-	else
-	{
-		throw std::out_of_range{"index out of range"};
-	}
-}
-
-/**
- * @brief Gets an object pointer by id. Used by the editing context to actually modify objects.
- * @param id
- * @return object pointer
- */
-ldraw::Object* Model::findObjectById(const ldraw::id_t id)
-{
-	const QModelIndex index = this->find(id);
-	if (index.isValid())
-	{
-		return (*this)[index.row()];
-	}
-	else
-	{
-		return nullptr;
-	}
-}
-
-const ldraw::Object* Model::findObjectById(const ldraw::id_t id) const
-{
-	const QModelIndex index = this->find(id);
-	if (index.isValid())
-	{
-		return (*this)[index.row()];
-	}
-	else
-	{
-		return nullptr;
-	}
-}
-
-/**
- * @brief Attempts the save the model
- */
-void save(const Model &model, QIODevice *device)
-{
-	QTextStream out{device};
-	applyToModel<ldraw::Object>(model, [&](const ldraw::Object* object) {
-		out << object->toLDrawCode() << "\r\n";
-	});
-}
--- a/src/model.h	Wed May 25 20:36:34 2022 +0300
+++ b/src/model.h	Mon Jun 06 22:01:22 2022 +0300
@@ -20,120 +20,96 @@
 #include <QAbstractListModel>
 #include <memory>
 #include "main.h"
-#include "header.h"
-#include "linetypes/object.h"
-#include "linetypes/metacommand.h"
-#include "gl/common.h"
+#include "colors.h"
+
+struct SubfileReference
+{
+	QString name;
+	glm::mat4 transformation;
+	bool inverted = false;
+};
+
+template<typename T>
+struct Colored : T
+{
+	ldraw::Color color;
+};
+
+struct Comment
+{
+	QString text;
+};
 
-enum class HeaderProperty
+struct ParseError
 {
-	Name
+	QString code;
 };
 
+struct Empty {};
+
+using ModelElement = std::variant<
+	Colored<SubfileReference>,
+	Colored<LineSegment>,
+	Colored<Triangle>,
+	Colored<Quadrilateral>,
+	Colored<ConditionalEdge>,
+	Comment,
+	Empty,
+	ParseError>;
+
+QString modelElementToString(const ModelElement& element);
+struct ModelId
+{
+	std::int32_t value;
+	constexpr auto operator<=>(const ModelId& other) const = default;
+};
+
+constexpr int qHash(ModelId id)
+{
+	return qHash(id.value);
+}
+
 class Model : public QAbstractListModel
 {
 	Q_OBJECT
+	struct Entry {
+		ModelElement data;
+		ModelId id;
+	};
+	std::vector<Entry> body;
+	std::map<ModelId, int> positions;
+	ModelId runningId = {1};
 public:
-	Model(QObject* parent = nullptr);
-	Model(const Model&) = delete;
-
-	int size() const;
-	ldraw::id_t at(int index) const;
+	Model(QObject* parent);
+	virtual ~Model();
+	ModelId append(const ModelElement& value);
+	const ModelElement& at(int position) const;
+	ModelId idAt(int position) const;
+	void assignAt(int position, const ModelElement& element);
+	std::optional<int> find(ModelId id) const;
+	void remove(int index);
 	int rowCount(const QModelIndex&) const override;
 	QVariant data(const QModelIndex& index, int role) const override;
-	ldraw::Object* findObjectById(const ldraw::id_t id);
-	const ldraw::Object* findObjectById(const ldraw::id_t id) const;
-	QModelIndex find(ldraw::id_t id) const;
-	ldraw::id_t idAt(const QModelIndex& index) const;
-	template<typename R>
-	const R* get(ldraw::Id<R> id) const;
-	template<typename R>
-	struct Get2Result
-	{
-		QModelIndex index;
-		const R* object;
-	};
-	template<typename R>
-	Get2Result<R> get2(ldraw::Id<R> id) const;
-	ldraw::Object* operator[](int index);
-	const ldraw::Object* operator[](int index) const;
-	using ModelObjectPointer = std::unique_ptr<ldraw::Object>;
-	template<typename T, typename... Args>
-	ldraw::Id<T> append(Args&&... args);
-	ldraw::id_t append(ModelObjectPointer&& object);
-	template<typename T, typename... Args>
-	ldraw::Id<T> insert(std::size_t position, Args&&... args);
-	void remove(int position);
-	void emitDataChangedSignal(int position);
-private:
-	bool modified = false;
-	std::vector<ModelObjectPointer> body;
-	mutable std::map<ldraw::id_t, std::size_t> objectsById;
-	mutable bool needObjectsByIdRebuild = false;
+	const ModelElement& operator[](int index) const;
+	int size() const;
+	auto operator[](int index) {
+		struct {
+			Model& model;
+			int index;
+			operator const ModelElement&() {
+				return model.at(index);
+			}
+			auto& operator=(const ModelElement& newData) {
+				model.assignAt(index, newData);
+				return *this;
+			}
+			const auto* operator&() {
+				return &(this->operator const ModelElement&());
+			}
+		} result{*this, index};
+		return result;
+	}
 };
 
 void save(const Model& model, QIODevice *device);
-
-/**
- * @brief Calls the specified function to all matching objects in the model
- * @tparam R Type of LDraw line type object to filter by
- * @param fn Function to call.
- */
-template<typename R, typename Fn>
-void applyToModel(const Model& model, Fn&& f)
-{
-	for (int i = 0; i < model.size(); i += 1)
-	{
-		const ldraw::Object* object = model[i];
-		const R* subobject = dynamic_cast<const R*>(object);
-		if (subobject != nullptr)
-		{
-			f(subobject);
-		}
-	}
-}
-
-template<typename T, typename... Args>
-ldraw::Id<T> Model::append(Args&&... args)
-{
-	const int position = static_cast<int>(this->body.size());
-	Q_EMIT beginInsertRows({}, position, position);
-	this->body.push_back(std::make_unique<T>(args...));
-	ldraw::Object* pointer = this->body.back().get();
-	this->objectsById[pointer->id] = this->body.size() - 1;
-	Q_EMIT endInsertRows();
-	return ldraw::Id<T>{pointer->id.value};
-}
-
-template<typename T, typename... Args>
-ldraw::Id<T> Model::insert(const std::size_t position, Args&&... args)
-{
-	Q_EMIT beginInsertRows({}, position, position);
-	this->body.insert(std::begin(this->body) + position, std::make_unique<T>(args...));
-	ldraw::Object* pointer = this->body[position].get();
-	this->objectsById[pointer->id] = position;
-	Q_EMIT endInsertRows();
-	return ldraw::Id<T>{pointer->id.value};
-}
-
-template<typename R>
-const R* Model::get(ldraw::Id<R> id) const
-{
-	return this->get2(id).object;
-}
-
-template<typename R>
-Model::Get2Result<R> Model::get2(const ldraw::Id<R> id) const
-{
-	Get2Result<R> result;
-	result.index = this->find(id);
-	if (result.index.isValid())
-	{
-		result.object = static_cast<const R*>((*this)[result.index.row()]);
-	}
-	else
-	{
-		result.object = nullptr;
-	}
-	return result;
-}
+void updateHeaderNameField(Model& model, const QString &name);
--- a/src/modeleditor.cpp	Wed May 25 20:36:34 2022 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,85 +0,0 @@
-/*
- *  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 "modeleditor.h"
-#include "linetypes/triangle.h"
-#include "linetypes/quadrilateral.h"
-
-ModelEditor::ModelEditor(Model& model) :
-	storedModel{model}
-{
-}
-
-ModelEditor::~ModelEditor()
-{
-	for (ldraw::id_t id : this->modifiedObjects)
-	{
-		const QModelIndex index = this->model().find(id);
-		if (index.isValid())
-		{
-			Q_EMIT this->objectModified(index.row());
-		}
-	}
-}
-
-ldraw::id_t ModelEditor::append(std::unique_ptr<ldraw::Object>&& object)
-{
-	this->storedModel.append(std::move(object));
-	Q_EMIT this->objectAdded(this->model().size() - 1);
-	return object->id;
-}
-
-void ModelEditor::remove(int position)
-{
-	this->storedModel.remove(position);
-}
-
-auto ModelEditor::setObjectProperty(
-	const ldraw::id_t id,
-	const ldraw::Property property,
-	const QVariant& value)
-	-> ldraw::Object::SetPropertyResult
-{
-	ldraw::Object* const object = this->storedModel.findObjectById(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 ModelEditor::setObjectPoint(ldraw::id_t id, int pointId, const glm::vec3& value)
-{
-	ldraw::Object* object = this->storedModel.findObjectById(id);
-	if (object != nullptr)
-	{
-		object->setProperty(ldraw::PropertyKeyValue{ldraw::pointProperty(pointId), QVariant::fromValue(value)});
-		modifiedObjects.insert(id);
-	}
-}
-
-const Model &ModelEditor::model()
-{
-	return this->storedModel;
-}
-
--- a/src/modeleditor.h	Wed May 25 20:36:34 2022 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,98 +0,0 @@
-/*
- *  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 "model.h"
-
-/**
- * @brief Provides an interface for editing a model such that signals are emitted for each edit done.
- * User edits to models should always be done through this class.
- */
-class ModelEditor : public QObject
-{
-	Q_OBJECT
-public:
-	ModelEditor(Model& model);
-	~ModelEditor();
-	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);
-	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);
-	template<typename T, typename Fn>
-	bool modifyObject(ldraw::Id<T> id, Fn&& function);
-	template<typename T, typename Fn>
-	bool modifyObjectAt(int position, Fn&& function);
-	const Model& model();
-Q_SIGNALS:
-	void objectAdded(int position);
-	void objectModified(int position);
-	void objectRemoved(ldraw::id_t id);
-private:
-	QSet<ldraw::id_t> modifiedObjects;
-	Model& storedModel;
-};
-
-template<ldraw::Property Property>
-void ModelEditor::setObjectProperty(const ldraw::id_t id, const ldraw::PropertyType<Property>& value)
-{
-	this->modifyObject(id, [&](ldraw::Object* object){
-		object->setProperty<Property>(value);
-	});
-}
-
-template<typename T, typename... Args>
-ldraw::Id<T> ModelEditor::append(Args&&... args)
-{
-	return this->storedModel.append<T>(args...);
-}
-
-template<typename T, typename... Args>
-ldraw::Id<T> ModelEditor::insert(int position, Args&&... args)
-{
-	return this->storedModel.insert<T>(position, args...);
-}
-
-template<typename T, typename Fn>
-bool ModelEditor::modifyObject(ldraw::Id<T> id, Fn&& function)
-{
-	QModelIndex index = this->model().find(id);
-	return this->modifyObjectAt<T>(index.row(), function);
-}
-
-template<typename T, typename Fn>
-bool ModelEditor::modifyObjectAt(int position, Fn&& function)
-{
-	if (position >= 0 and position < this->model().size())
-	{
-		T* object = dynamic_cast<T*>(this->storedModel[position]);
-		if (object != nullptr)
-		{
-			Q_EMIT this->objectModified(position);
-			function(object);
-			return true;
-		}
-	}
-	return false;
-}
\ No newline at end of file
--- a/src/parser.cpp	Wed May 25 20:36:34 2022 +0300
+++ b/src/parser.cpp	Mon Jun 06 22:01:22 2022 +0300
@@ -18,14 +18,7 @@
 
 #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"
+#include "ldrawalgorithm.h"
 
 struct BodyParseError
 {
@@ -64,11 +57,12 @@
 			invertNext = true;
 			continue;
 		}
-		model.append(parseFromString(line));
+		ModelElement element = parseLDrawLine(line);
 		if (invertNext)
 		{
-			model[model.size() - 1]->invert(nullptr);
+			element = inverted(element);
 		}
+		model.append(element);
 		invertNext = false;
 	}
 }
@@ -140,19 +134,13 @@
 	return result;
 }
 
-static std::unique_ptr<ldraw::Object> parseType0Line(
-	const QString& line,
-	const QStringList& tokens)
+static Comment parseType0Line(const QString& line)
 {
-	Q_UNUSED(tokens)
-	return std::make_unique<ldraw::MetaCommand>(line.mid(1).trimmed());
+	return {line.mid(1).trimmed()};
 }
 
-static std::unique_ptr<ldraw::SubfileReference> parseType1Line(
-	const QString& line,
-	const QStringList& tokens)
+static Colored<SubfileReference> parseType1Line(const QStringList& tokens)
 {
-	Q_UNUSED(line)
 	constexpr int colorPosition = 1;
 	constexpr int positionPosition = 2; // 2..4
 	constexpr int transformPosition = 5; // 5..13
@@ -164,15 +152,18 @@
 	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);
+	return Colored<SubfileReference>{
+		{
+			.name = name,
+			.transformation = transform,
+		},
+		color,
+	};
 }
 
-template<typename T, int NumVertices>
-static std::unique_ptr<T> parsePolygon(
-	const QString& line,
-	const QStringList& tokens)
+template<int NumVertices>
+static auto parsePolygon(const QStringList& tokens)
 {
-	Q_UNUSED(line)
 	constexpr int colorPosition = 1;
 	auto vertexPosition = [](int n) { return 2 + 3*n; };
 	if (tokens.size() != 2 + 3 * NumVertices)
@@ -185,10 +176,10 @@
 	{
 		vertices[unsigned_cast(i)] = vertexFromStrings(tokens, vertexPosition(i));
 	}
-	return std::make_unique<T>(vertices, color);
+	return std::make_pair(vertices, color);
 }
 
-std::unique_ptr<ldraw::Object> Parser::parseFromString(QString line)
+ModelElement parseLDrawLine(QString line)
 {
 	line = line.trimmed();
 	try
@@ -196,7 +187,7 @@
 		const QStringList tokens = line.split(QRegExp{R"(\s+)"});
 		if (tokens.empty() or tokens == QStringList{{""}})
 		{
-			return std::make_unique<ldraw::Empty>();
+			return Empty{};
 		}
 		bool ok_code;
 		const int code = tokens[0].toInt(&ok_code);
@@ -207,23 +198,38 @@
 		switch (code)
 		{
 		case 0:
-			return parseType0Line(line, tokens);
+			return parseType0Line(line);
 		case 1:
-			return parseType1Line(line, tokens);
+			return parseType1Line(tokens);
 		case 2:
-			return parsePolygon<ldraw::Edge, 2>(line, tokens);
+		{
+			const auto pair = parsePolygon<2>(tokens);
+			return Colored<LineSegment>{{pair.first[0], pair.first[1]}, pair.second};
+		}
 		case 3:
-			return parsePolygon<ldraw::Triangle, 3>(line, tokens);
+		{
+			const auto pair = parsePolygon<3>(tokens);
+			return Colored<Triangle>{{pair.first[0], pair.first[1], pair.first[2]}, pair.second
+			};
+		}
 		case 4:
-			return parsePolygon<ldraw::Quadrilateral, 4>(line, tokens);
+		{
+			const auto pair = parsePolygon<4>(tokens);
+			const Quadrilateral quad{pair.first[0], pair.first[1], pair.first[2], pair.first[3]};
+			return Colored<Quadrilateral>{quad, pair.second};
+		}
 		case 5:
-			return parsePolygon<ldraw::ConditionalEdge, 4>(line, tokens);
+		{
+			const auto pair = parsePolygon<4>(tokens);
+			const ConditionalEdge cedge{pair.first[0], pair.first[1], pair.first[2], pair.first[3]};
+			return Colored<ConditionalEdge>{cedge, pair.second};
+		}
 		default:
 			throw BodyParseError{utility::format("bad line type '%1'", code)};
 		}
 	}
 	catch(const BodyParseError& error)
 	{
-		return std::make_unique<ldraw::ErrorLine>(line, error.message);
+		return ParseError{line};
 	}
 }
--- a/src/parser.h	Wed May 25 20:36:34 2022 +0300
+++ b/src/parser.h	Mon Jun 06 22:01:22 2022 +0300
@@ -18,20 +18,17 @@
 
 #pragma once
 #include "main.h"
-#include "linetypes/object.h"
 #include "model.h"
-#include "modeleditor.h"
-#include "header.h"
 
 class Parser : public QObject
 {
 	Q_OBJECT
 public:
-	enum { EndOfModel = -1 };
 	Parser(QIODevice& device, QObject* parent = nullptr);
 	void parseBody(Model &model);
-	static std::unique_ptr<ldraw::Object> parseFromString(QString line);
 private:
 	QString readLine();
 	QIODevice& device;
 };
+
+ModelElement parseLDrawLine(QString line);
--- a/src/polygoncache.cpp	Wed May 25 20:36:34 2022 +0300
+++ b/src/polygoncache.cpp	Mon Jun 06 22:01:22 2022 +0300
@@ -1,11 +1,80 @@
 #include "polygoncache.h"
 #include "documentmanager.h"
+#include "invert.h"
 
-static void getObjectPolygons(
-	Model* model,
-	const int index,
-	std::vector<gl::Polygon>& polygons_out,
-	ldraw::GetPolygonsContext* context);
+Model* resolve(const QString& name, const ModelId callingModelId, DocumentManager* documents)
+{
+	return documents->findDependencyByName(callingModelId, name);
+}
+
+/**
+ * @brief Gets the GL polygons of the object at the specified position in the model
+ * @param index Index of object in the model
+ * @param polygons_out Vector to add polygons into
+ * @param context Context to use to resolve subfile references
+ */
+static std::vector<gl::Polygon> getPolygonsAt(const Model* model, GetPolygonsContext* context)
+{
+	std::vector<gl::Polygon> result;
+	for (int i = 0; i < model->size(); i += 1)
+	{
+		const ModelElement& element = (*model)[i];
+		const ModelId id = model->idAt(i);
+		std::visit<void>(overloaded{
+			[&](const Colored<LineSegment>& edge) {
+				result.push_back(gl::edgeLine(edge, id));
+			},
+			[&](const Colored<Triangle>& triangle) {
+				result.push_back(gl::triangle(triangle, id));
+			},
+			[&](const Colored<Quadrilateral>& quad) {
+				result.push_back(gl::quadrilateral(quad, id));
+			},
+			[&](const Colored<ConditionalEdge>& cedge) {
+				result.push_back(gl::conditionalEdge(cedge, id));
+			},
+			[&](const Colored<SubfileReference>& ref) {
+				Model* dependency = context->documents->findDependencyByName(context->modelId, ref.name);
+				PolygonCache* cache = nullptr;
+				if (dependency != nullptr)
+				{
+					const auto dependencyModelId = context->documents->findIdForModel(dependency);
+					if (dependencyModelId.has_value()) {
+						cache = context->documents->getPolygonCacheForModel(dependencyModelId.value());
+					}
+				}
+				if (cache != nullptr) {
+					const bool needInverting = glm::determinant(ref.transformation) < 0;
+					const std::vector<gl::Polygon>& modelPolygons = getCachedPolygons(
+						cache,
+						dependency,
+						context->documents);
+					result.reserve(result.size() + modelPolygons.size());
+					for (gl::Polygon polygon : modelPolygons)
+					{
+						for (unsigned int i = 0; i < polygon.numPolygonVertices(); i += 1)
+						{
+							const glm::vec4 homogenousVertex {polygon.vertices[i], 1};
+							polygon.vertices[i] = ref.transformation * homogenousVertex;
+						}
+						if (needInverting != ref.inverted)
+						{
+							gl::invert(polygon);
+						}
+						if (polygon.color == ldraw::MAIN_COLOR)
+						{
+							polygon.color = ref.color;
+						}
+						polygon.id = id;
+						result.push_back(polygon);
+					}
+				}
+			},
+			[](const ModelElement&) {}
+		}, element);
+	}
+	return result;
+}
 
 /**
  * @brief Gets a list of GL polygons that are used to represent this model.
@@ -24,29 +93,10 @@
 		const std::optional<ModelId> modelId = documents->findIdForModel(model);
 		if (modelId.has_value())
 		{
-			ldraw::GetPolygonsContext context{modelId.value(), documents};
-			for (int i = 0; i < model->size(); i += 1)
-			{
-				getObjectPolygons(model, i, cache->cachedPolygons, &context);
-			}
+			GetPolygonsContext context{modelId.value(), documents};
+			cache->cachedPolygons = getPolygonsAt(model, &context);
 		}
 		cache->needRecache = false;
 	}
 	return cache->cachedPolygons;
 }
-
-/**
- * @brief Gets the GL polygons of the object at the specified position in the model
- * @param index Index of object in the model
- * @param polygons_out Vector to add polygons into
- * @param context Context to use to resolve subfile references
- */
-static void getObjectPolygons(
-	Model* model,
-	const int index,
-	std::vector<gl::Polygon>& polygons_out,
-	ldraw::GetPolygonsContext* context)
-{
-	const ldraw::Object* object = (*model)[unsigned_cast(index)];
-	object->getPolygons(polygons_out, context);
-}
--- a/src/polygoncache.h	Wed May 25 20:36:34 2022 +0300
+++ b/src/polygoncache.h	Mon Jun 06 22:01:22 2022 +0300
@@ -1,6 +1,13 @@
 #pragma once
 #include "main.h"
 #include "model.h"
+#include "gl/common.h"
+
+struct GetPolygonsContext
+{
+	ModelId modelId;
+	class DocumentManager* documents;
+};
 
 struct PolygonCache
 {
--- a/src/ui/canvas.cpp	Wed May 25 20:36:34 2022 +0300
+++ b/src/ui/canvas.cpp	Mon Jun 06 22:01:22 2022 +0300
@@ -1,12 +1,11 @@
 #include <QMouseEvent>
 #include <QPainter>
-#include "modeleditor.h"
 #include "document.h"
 #include "canvas.h"
 
 Canvas::Canvas(
 	Model* model,
-	Document *document,
+	EditorTabWidget *document,
 	DocumentManager* documents,
 	const ldraw::ColorTable& colorTable,
 	QWidget* parent) :
@@ -21,9 +20,9 @@
  * @param selectedIds IDs of objects to select
  * @param deselectedIds IDs of objects to deselect.
  */
-void Canvas::handleSelectionChange(const QSet<ldraw::id_t>& selectedIds, const QSet<ldraw::id_t>& deselectedIds)
+void Canvas::handleSelectionChange(const QSet<ModelId> &selectedIds, const QSet<ModelId> &deselectedIds)
 {
-	Q_ASSERT(not selectedIds.contains(ldraw::NULL_ID));
+	Q_ASSERT(not selectedIds.contains({0}));
 	this->selection.subtract(deselectedIds);
 	this->selection.unite(selectedIds);
 	gl::setModelShaderSelectedObjects(&this->shaders, this->selection);
@@ -34,7 +33,7 @@
  * @brief Updates vertex rendering
  * @param document Document to get vertices from
  */
-void Canvas::rebuildVertices(Document* document)
+void Canvas::rebuildVertices(EditorTabWidget* document)
 {
 	if (this->vertexProgram.has_value())
 	{
@@ -45,7 +44,7 @@
 
 void Canvas::mouseMoveEvent(QMouseEvent* event)
 {
-	const ldraw::id_t id = this->pick(event->pos());
+	const ModelId id = this->pick(event->pos());
 	this->highlighted = id;
 	this->totalMouseMove += (event->pos() - this->lastMousePosition).manhattanLength();
 	this->worldPosition = this->screenToModelCoordinates(event->pos(), this->gridPlane);
@@ -339,7 +338,7 @@
 /**
  * @returns the ids of the currently selected objects 
  */
-const QSet<ldraw::id_t> Canvas::selectedObjects() const
+const QSet<ModelId> Canvas::selectedObjects() const
 {
 	return this->selection;
 }
--- a/src/ui/canvas.h	Wed May 25 20:36:34 2022 +0300
+++ b/src/ui/canvas.h	Mon Jun 06 22:01:22 2022 +0300
@@ -28,7 +28,7 @@
 	using OverpaintCallback = std::function<void(Canvas*, QPainter*)>;
 	Canvas(
 		Model* model,
-		Document* document,
+		EditorTabWidget* document,
 		DocumentManager* documents,
 		const ldraw::ColorTable& colorTable,
 		QWidget* parent = nullptr);
@@ -38,15 +38,15 @@
 	void drawWorldPolygon(QPainter* painter, const std::vector<glm::vec3>& points);
 	Winding worldPolygonWinding(const std::vector<glm::vec3>& points) const;
 	const std::optional<glm::vec3>& getWorldPosition() const;
-	const QSet<ldraw::id_t> selectedObjects() const;
+	const QSet<ModelId> selectedObjects() const;
 	const glm::mat4& getGridMatrix() const;
 	void closeShape();
 	glm::vec3 cameraVector() const;
 	std::optional<glm::vec3> worldPosition;
 	DrawState* drawState = nullptr;
 public Q_SLOTS:
-	void handleSelectionChange(const QSet<ldraw::id_t>& selectedIds, const QSet<ldraw::id_t>& deselectedIds);
-	void rebuildVertices(Document *document);
+	void handleSelectionChange(const QSet<ModelId>& selectedIds, const QSet<ModelId>& deselectedIds);
+	void rebuildVertices(EditorTabWidget *document);
 	void setGridMatrix(const glm::mat4 &newMatrix);
 protected:
 	void mouseMoveEvent(QMouseEvent* event) override;
@@ -70,9 +70,9 @@
 	geom::Plane gridPlane;
 	int totalMouseMove = 0;
 	bool isDark = true;
-	QSet<ldraw::id_t> selection;
+	QSet<ModelId> selection;
 	OverpaintCallback overpaintCallback = nullptr;
-	Document* document;
+	EditorTabWidget* document;
 };
 
 void adjustGridToView(Canvas* canvas);
--- a/src/ui/objecteditor.cpp	Wed May 25 20:36:34 2022 +0300
+++ b/src/ui/objecteditor.cpp	Mon Jun 06 22:01:22 2022 +0300
@@ -3,168 +3,73 @@
 #include <QFormLayout>
 #include "objecteditor.h"
 #include "document.h"
-#include "modeleditor.h"
 #include "widgets/colorbutton.h"
 #include "widgets/colorindexinput.h"
 #include "widgets/vec3editor.h"
 #include "ui_objecteditor.h"
 
-ObjectEditor::ObjectEditor(Document* document, const ldraw::id_t id) :
-	QWidget{document},
-	ui{*new Ui_ObjectEditor}, 
-	document{document}
-{
-	this->ui.setupUi(this);
-	this->setObjectId(id);
-	QFormLayout* formLayout = new QFormLayout{ui.properties};
-	this->ui.properties->setLayout(formLayout);
-	for (const ldraw::Property property : ldraw::ALL_PROPERTIES)
-	{
-		QWidget* editorWidget = this->makeEditorWidgetForProperty(property);
-		if (editorWidget != nullptr)
-		{
-			editorWidget->setProperty("_property_id", static_cast<int>(property));
-			QLabel* propertyLabel = new QLabel{ldraw::traits(property).name.data()};
-			formLayout->addRow(propertyLabel, editorWidget);
-			this->propertyWidgets[property] = {propertyLabel, editorWidget};
-		}
-	}
-	this->setObjectId(ldraw::NULL_ID);
-}
-
-ObjectEditor::~ObjectEditor()
-{
-	delete &this->ui;
-}
-
-QString titleCase(const QString& string)
-{
-	return string.left(1).toUpper() + string.mid(1);
-}
+using PropertyValue = std::variant<
+	const glm::vec3*,
+	const glm::mat4*,
+	const QString*,
+	ldraw::Color>;
 
-void setValueToWidget(QWidget* widget, const QVariant& value)
+enum PropertyKey
 {
-	ColorIndexInput* colorIndexInput = qobject_cast<ColorIndexInput*>(widget);
-	if (colorIndexInput != nullptr)
-	{
-		colorIndexInput->setSelectedColor(value.value<ldraw::Color>());
-	}
-	else
-	{
-		Vec3Editor* vec3Editor = qobject_cast<Vec3Editor*>(widget);
-		if (vec3Editor != nullptr)
-		{
-			vec3Editor->setValue(value.value<glm::vec3>());
-		}
-		else
-		{
-			QCheckBox* checkBox = qobject_cast<QCheckBox*>(widget);
-			if (checkBox != nullptr)
-			{
-				checkBox->setChecked(value.toBool());
-			}
-			else
-			{
-				QLineEdit* lineEdit = qobject_cast<QLineEdit*>(widget);
-				if (lineEdit != nullptr)
-				{
-					lineEdit->setText(value.toString());
-				}
-			}
-		}
-	}
-}
+	Point1,
+	Point2,
+	Point3,
+	Point4,
+	Control1,
+	Control2,
+	Color,
+	Transformation,
+	Name,
+	Text,
+	Code,
+};
 
-void ObjectEditor::setObjectId(const ldraw::id_t id)
+std::map<PropertyKey, PropertyValue> getProperties(const ModelElement& element)
 {
-	this->objectId = id;
-	const ldraw::Object* object = this->document->getModel().get(id);
-	this->ui.properties->setEnabled(object != nullptr);
-	if (object != nullptr)
-	{
-		this->ui.typeNameLabel->setText("<b>" + titleCase(object->typeName()) + "</b>");
-		this->ui.typeIconLabel->setPixmap(QPixmap{object->iconName()}.scaledToWidth(24));
-		for (const ldraw::Property property : ldraw::ALL_PROPERTIES)
-		{
-			const QVariant value = object->getProperty(property);
-			const auto it = this->propertyWidgets.find(property);
-			if (it != this->propertyWidgets.end())
-			{
-				it->first->setEnabled(not value.isNull());
-				it->second->setEnabled(not value.isNull());
-				if (not value.isNull())
-				{
-					setValueToWidget(it->second, value);
-				}
-			}
-		}
-	}
-	else
-	{
-		this->ui.typeNameLabel->setText(tr("No object selected"));
-		this->ui.typeIconLabel->clear();
-	}
-}
-
-void ObjectEditor::handleColorChange(ldraw::Color value)
-{
-	this->handlePropertyChange(this->sender(), QVariant::fromValue(value));
-}
-
-void ObjectEditor::handleVec3Change(const glm::vec3 &value)
-{
-	this->handlePropertyChange(this->sender(), QVariant::fromValue(value));
-}
-
-void ObjectEditor::handleBoolChange(bool value)
-{
-	this->handlePropertyChange(this->sender(), QVariant::fromValue(value));
+	std::map<PropertyKey, PropertyValue> result;
+	std::visit<void>(overloaded{
+		[&](const Colored<LineSegment>& edge) {
+			result[Point1] = &edge.p1;
+			result[Point2] = &edge.p2;
+			result[Color] = edge.color;
+		},
+		[&](const Colored<Triangle>& tri) {
+			result[Point1] = &tri.p1;
+			result[Point2] = &tri.p2;
+			result[Point3] = &tri.p3;
+			result[Color] = tri.color;
+		},
+		[&](const Colored<Quadrilateral>& quad) {
+			result[Point1] = &quad.p1;
+			result[Point2] = &quad.p2;
+			result[Point3] = &quad.p3;
+			result[Point4] = &quad.p4;
+			result[Color] = quad.color;
+		},
+		[&](const Colored<ConditionalEdge>& cedge) {
+			result[Point1] = &cedge.p1;
+			result[Point2] = &cedge.p2;
+			result[Control1] = &cedge.c1;
+			result[Control2] = &cedge.c2;
+			result[Color] = cedge.color;
+		},
+		[&](const Colored<SubfileReference>& ref) {
+			result[Transformation] = &ref.transformation;
+			result[Name] = &ref.name;
+			result[Color] = ref.color;
+		},
+		[&](Empty) {},
+		[&](const Comment& comment) {
+			result[Text] = &comment.text;
+		},
+		[&](const ParseError& parseError) {
+			result[Code] = &parseError.code;
+		},
+	}, element);
+	return result;
 }
-
-void ObjectEditor::handleStringChange(const QString &value)
-{
-	this->handlePropertyChange(this->sender(), QVariant::fromValue(value));
-}
-
-QWidget* ObjectEditor::makeEditorWidgetForProperty(ldraw::Property property)
-{
-	QWidget* const parent = qobject_cast<QWidget*>(this->parent());
-	if (ldraw::traits(property).type == qMetaTypeId<ldraw::Color>())
-	{
-		ColorIndexInput* colorWidget = new ColorIndexInput{this->document, {0}, parent};
-		connect(colorWidget, &ColorIndexInput::colorChanged, this, &ObjectEditor::handleColorChange);
-		return colorWidget;
-	}
-	else if (ldraw::traits(property).type == qMetaTypeId<glm::vec3>())
-	{
-		Vec3Editor* editor = new Vec3Editor{{}, parent};
-		connect(editor, &Vec3Editor::valueChanged, this, &ObjectEditor::handleVec3Change);
-		return editor;
-	}
-	else if (ldraw::traits(property).type == qMetaTypeId<bool>())
-	{
-		QCheckBox* editor = new QCheckBox{{}, parent};
-		connect(editor, &QCheckBox::clicked, this, &ObjectEditor::handleBoolChange);
-		return editor;
-	}
-	else if (ldraw::traits(property).type == qMetaTypeId<QString>())
-	{
-		QLineEdit* editor = new QLineEdit{{}, parent};
-		connect(editor, &QLineEdit::textChanged, this, &ObjectEditor::handleStringChange);
-		return editor;
-	}
-	else
-	{
-		return nullptr;
-	}
-}
-
-void ObjectEditor::handlePropertyChange(QObject *caller, const QVariant &value)
-{
-	QVariant propertyVariant = caller->property("_property_id");
-	if (not propertyVariant.isNull())
-	{
-		ldraw::Property const property = static_cast<ldraw::Property>(propertyVariant.toInt());
-		this->document->editModel()->setObjectProperty(this->objectId, property, value);
-	}
-}
\ No newline at end of file
--- a/src/ui/objecteditor.h	Wed May 25 20:36:34 2022 +0300
+++ b/src/ui/objecteditor.h	Mon Jun 06 22:01:22 2022 +0300
@@ -2,25 +2,3 @@
 #include <QWidget>
 #include "../main.h"
 #include "../model.h"
-
-class Document;
-
-class ObjectEditor : public QWidget
-{
-	Q_OBJECT
-public:
-	explicit ObjectEditor(Document* document, ldraw::id_t id = ldraw::NULL_ID);
-	~ObjectEditor();
-	void setObjectId(ldraw::id_t id);
-private:
-	Q_SLOT void handleColorChange(ldraw::Color value);
-	Q_SLOT void handleVec3Change(const glm::vec3& value);
-	Q_SLOT void handleBoolChange(bool value);
-	Q_SLOT void handleStringChange(const QString& value);
-	class Ui_ObjectEditor& ui;
-	Document* const document;
-	ldraw::id_t objectId = ldraw::NULL_ID;
-	QMap<ldraw::Property, QPair<QWidget*, QWidget*>> propertyWidgets;
-	QWidget* makeEditorWidgetForProperty(ldraw::Property property);
-	void handlePropertyChange(QObject* caller, const QVariant& value);
-};
--- a/src/utility.h	Wed May 25 20:36:34 2022 +0300
+++ b/src/utility.h	Mon Jun 06 22:01:22 2022 +0300
@@ -73,8 +73,28 @@
 	{
 		return utility::format("(%1, %2, %3)", vertex.x, vertex.y, vertex.z);
 	}
+
+	inline QString transformToString(const glm::mat4& matrix)
+	{
+		return utility::format(
+			"%1 %2 %3 %4 %5 %6 %7 %8 %9 %10 %11 %12",
+			matrix[3][0],
+			matrix[3][1],
+			matrix[3][2],
+			matrix[0][0],
+			matrix[1][0],
+			matrix[2][0],
+			matrix[0][1],
+			matrix[1][1],
+			matrix[2][1],
+			matrix[0][2],
+			matrix[1][2],
+			matrix[2][2]);
+	}
 }
 
+using namespace utility;
+
 template<typename T, glm::qualifier Q>
 inline unsigned int qHash(const glm::vec<3, T, Q>& key)
 {
--- a/src/vertexmap.cpp	Wed May 25 20:36:34 2022 +0300
+++ b/src/vertexmap.cpp	Mon Jun 06 22:01:22 2022 +0300
@@ -1,5 +1,5 @@
 #include "vertexmap.h"
-#include "linetypes/polygonobject.h"
+#include "gl/common.h"
 
 unsigned int hashVertex(const glm::vec3& vec)
 {
@@ -40,14 +40,75 @@
 	const glm::vec3& b;
 };
 
-inline void edges(const ldraw::Object* object, std::function<void(Edge&&)> fn)
+inline void edges(const ModelElement& element, std::function<void(Edge&&)> fn)
+{
+	std::visit<void>(overloaded{
+		[fn](const Colored<LineSegment>& edge) {
+			fn(Edge{edge.p1, edge.p2});
+		},
+		[fn](const Colored<Triangle>& triangle) {
+			fn(Edge{triangle.p1, triangle.p2});
+			fn(Edge{triangle.p2, triangle.p3});
+			fn(Edge{triangle.p3, triangle.p1});
+		},
+		[fn](const Colored<Quadrilateral>& quad) {
+			fn(Edge{quad.p1, quad.p2});
+			fn(Edge{quad.p2, quad.p3});
+			fn(Edge{quad.p3, quad.p4});
+			fn(Edge{quad.p4, quad.p1});
+		},
+		[fn](const Colored<ConditionalEdge>& cedge) {
+			fn(Edge{cedge.p1, cedge.p2});
+		},
+		[](const ModelElement&&){}
+	}, element);
+}
+
+inline void points(
+	const ModelElement& element,
+	std::function<void(const glm::vec3&)> fn)
 {
-	for (int i = 0; i < object->numPoints() - 1; i += 1)
-	{
-		const glm::vec3 p1 = object->getPoint(i);
-		const glm::vec3 p2 = object->getPoint((i + 1) % object->numPoints());
-		fn(Edge{p1, p2});
-	}
+	std::visit<void>(overloaded{
+		[fn](const Colored<LineSegment>& edge) {
+			fn(edge.p1);
+			fn(edge.p2);
+		},
+		[fn](const Colored<Triangle>& triangle) {
+			fn(triangle.p1);
+			fn(triangle.p2);
+			fn(triangle.p3);
+		},
+		[fn](const Colored<Quadrilateral>& quad) {
+			fn(quad.p1);
+			fn(quad.p2);
+			fn(quad.p3);
+			fn(quad.p4);
+		},
+		[fn](const Colored<ConditionalEdge>& cedge) {
+			fn(cedge.p1);
+			fn(cedge.p2);
+			fn(cedge.c1);
+			fn(cedge.c2);
+		},
+		[](const ModelElement&&){}
+	}, element);
+}
+
+template<typename R>
+auto ifplanar(
+	const ModelElement& element,
+	std::function<R(const glm::vec3&, const glm::vec3&, const glm::vec3&)> fn
+)
+{
+	return std::visit(overloaded{
+		[fn](const Triangle& triangle) -> std::optional<R> {
+			return fn(triangle.p1, triangle.p2, triangle.p3);
+		},
+		[fn](const Quadrilateral quad) -> std::optional<R> {
+			return fn(quad.p1, quad.p2, quad.p3);
+		},
+		[](const ModelElement&&) -> std::optional<R> {return {};}
+	}, element);
 }
 
 void VertexMap::build()
@@ -55,30 +116,27 @@
 	this->map.clear();
 	this->vertices.clear();
 	this->vertexHashes.clear();
-	applyToModel<ldraw::Object>(*this->model, [&](const ldraw::Object* object)
+	for (int i = 0; i < this->model->size(); ++i)
 	{
-		glm::mat4 matrix;
-		if (object->numPoints() > 2)
-		{
-			const auto& p0 = object->getPoint(0);
-			const auto& p1 = object->getPoint(1);
-			const auto& p2 = object->getPoint(2);
-			const glm::vec3 a = glm::normalize(p1 - p0);
-			const glm::vec3 b = glm::normalize(p2 - p0);
-			const glm::vec3 c = glm::normalize(glm::cross(a, b));
-			const glm::vec3 d = glm::normalize(glm::cross(a, c));
-			matrix = glm::mat4{{a, 0}, {-c, 0}, {d, 0}, {}};
-		}
-		for (int i = 0; i < object->numPoints(); i += 1)
-		{
-			const glm::vec3& point = object->getPoint(i);
+		const ModelElement& element = this->model->at(i);
+		std::optional<glm::mat4> matrix = ifplanar<glm::mat4>(
+			element,
+			[](const glm::vec3& p1, const glm::vec3& p2, const glm::vec3& p3)
+			{
+				const glm::vec3 a = glm::normalize(p2 - p1);
+				const glm::vec3 b = glm::normalize(p3 - p1);
+				const glm::vec3 c = glm::normalize(glm::cross(a, b));
+				const glm::vec3 d = glm::normalize(glm::cross(a, c));
+				return glm::mat4{{a, 0}, {-c, 0}, {d, 0}, {}};
+			});
+		points(element, [&](const glm::vec3 point) {
 			const unsigned int hash = hashVertex(point);
 			VertexInfo& info = this->map[hash];
 			info.point = point;
-			info.objects.insert(object->id);
-			if (object->numPoints() > 2 and not info.transformSet)
+			info.objects.insert(this->model->idAt(i));
+			if (matrix.has_value() and not info.transformSet)
 			{
-				info.transform = matrix;
+				info.transform = matrix.value();
 				info.transform[3] = {point, 1};
 				info.transformSet = true;
 			}
@@ -87,8 +145,8 @@
 				this->vertexHashes.insert(hash);
 				this->vertices.push_back(point);
 			}
-		}
-	});
+		});
+	}
 	for (auto& pair : this->map)
 	{
 		auto& info = pair.second;
@@ -100,15 +158,18 @@
 			info.transformSet = true;
 		}
 		float scale = 1.0f;
-		for (const ldraw::id_t objectId : info.objects)
+		for (const ModelId objectId : info.objects)
 		{
-			edges(model->get(objectId), [&](Edge&& edge)
-			{
-				if (hashVertex(edge.a) == pair.first or hashVertex(edge.b) == pair.first)
+			const std::optional<int> index = model->find(objectId);
+			if (index.has_value()) {
+				edges(this->model->at(index.value()), [&](Edge&& edge)
 				{
-					scale = std::min(scale, glm::length(edge.b - edge.a) / 3);
-				}
-			});
+					if (hashVertex(edge.a) == pair.first or hashVertex(edge.b) == pair.first)
+					{
+						scale = std::min(scale, glm::length(edge.b - edge.a) / 3);
+					}
+				});
+			}
 		}
 		info.transform = glm::scale(info.transform, glm::vec3{scale, scale, scale});
 	}
--- a/src/vertexmap.h	Wed May 25 20:36:34 2022 +0300
+++ b/src/vertexmap.h	Mon Jun 06 22:01:22 2022 +0300
@@ -14,7 +14,7 @@
 	struct VertexInfo
 	{
 		glm::vec3 point;
-		std::set<ldraw::id_t> objects;
+		std::set<ModelId> objects;
 		bool transformSet = false;
 		glm::mat4 transform;
 	};
--- a/src/widgets/colorindexinput.cpp	Wed May 25 20:36:34 2022 +0300
+++ b/src/widgets/colorindexinput.cpp	Mon Jun 06 22:01:22 2022 +0300
@@ -3,7 +3,7 @@
 #include "colorselectdialog.h"
 #include "uiutilities.h"
 
-ColorIndexInput::ColorIndexInput(Document *document, ldraw::Color color, QWidget *parent) :
+ColorIndexInput::ColorIndexInput(EditorTabWidget *document, ldraw::Color color, QWidget *parent) :
 	QWidget{parent},
 	document{document},
 	ui{*new Ui_ColorIndexInput}
--- a/src/widgets/colorindexinput.h	Wed May 25 20:36:34 2022 +0300
+++ b/src/widgets/colorindexinput.h	Mon Jun 06 22:01:22 2022 +0300
@@ -5,13 +5,13 @@
 {
 	Q_OBJECT
 public:
-	ColorIndexInput(Document *document, ldraw::Color color = ldraw::MAIN_COLOR, QWidget *parent = nullptr);
+	ColorIndexInput(EditorTabWidget *document, ldraw::Color color = ldraw::MAIN_COLOR, QWidget *parent = nullptr);
 	~ColorIndexInput();
 	ldraw::Color selectedColor() const;
 	void setSelectedColor(ldraw::Color color);
 Q_SIGNALS:
 	void colorChanged(ldraw::Color color);
 private:
-	Document* const document;
+	EditorTabWidget* const document;
 	class Ui_ColorIndexInput& ui;
 };

mercurial