src/model.cpp

Sat, 08 Apr 2023 21:48:49 +0300

author
Teemu Piippo <teemu.s.piippo@gmail.com>
date
Sat, 08 Apr 2023 21:48:49 +0300
changeset 356
65b4741b302d
parent 338
719b909a7d2b
child 374
75efc3ba5a56
permissions
-rw-r--r--

Made editor font configurable
Add grid matrix editor

/*
 *  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 <QPixmap>
#include "src/model.h"

constexpr unsigned int gcd(unsigned int a, unsigned int b)
{
	while (a != b) {
		if (b > a) {
			b -= a;
		}
		else if (a > b) {
			a -= b;
		}
	}
	return a;
}

static_assert(gcd(16, 15) == 1);
static_assert(gcd(16, 4) == 4);
static_assert(gcd(272, 192) == 16);

static constexpr const char* circularPrimitiveTypeString(const CircularPrimitive& circ)
{
	return circularPrimitiveStems[circ.type];
}

static QString circularPrimitiveFilePath(const CircularPrimitive& circ)
{
	QString result;
	if (circ.fraction.divisions != 16) {
		result += QString::number(circ.fraction.divisions) + QStringLiteral("\\");
	}
	const unsigned int factor = gcd(circ.fraction.segments, circ.fraction.divisions);
	unsigned int num = circ.fraction.segments / factor;
	unsigned int denom = circ.fraction.divisions / factor;
	if (denom < 4) {
		num *= 4 / denom;
		denom = 4;
	}
	result += QStringLiteral("%1-%2").arg(num).arg(denom);
	result += QString::fromLatin1(circularPrimitiveTypeString(circ));
	result += QStringLiteral(".dat");
	return result;
}

static const char* iconPathForElement(const ModelElement& element)
{
	return std::visit(overloaded{
		[](const Colored<SubfileReference>&) {
			return ":/icons/linetype-subfile.png";
		},
		[](const Colored<LineSegment>&) {
			return ":/icons/linetype-edgeline.png";
		},
		[](const Colored<Triangle>&) {
			return ":/icons/linetype-triangle.png";
		},
		[](const Colored<Quadrilateral>&) {
			return ":/icons/linetype-quadrilateral.png";
		},
		[](const Colored<ConditionalEdge>&) {
			return ":/icons/linetype-conditionaledge.png";
		},
		[](const Colored<CircularPrimitive>&) {
			return ":/icons/linetype-circularprimitive.png";
		},
		[](const Comment&) {
			return ":/icons/chatbubble-ellipses-outline.png";
		},
		[](const Empty&) {
			return "";
		},
		[](const ParseError&) {
			return ":/icons/linetype-errorline.png";
		},
	}, element);
}

static QPixmap iconForElement(const ModelElement& element)
{
	// We avoid processing the same image over and over again by storing it
	// in a static constant. However, we need one per each possible type
	// of ModelElement, so we put the pixmap constant inside a templated lambda,
	// which gets instiated once for each type of ModelElement.
	return std::visit([](auto&& element){
		static const QPixmap pixmap = QPixmap::fromImage(
			QImage{iconPathForElement(element)}
				.scaledToHeight(24, Qt::SmoothTransformation)
		);
		return pixmap;
	}, element);
}

QString modelElementToString(const ModelElement &element)
{
	return std::visit(overloaded{
		[](const Colored<SubfileReference>& ref) {
			QString result;
			result += QStringLiteral("1 %1 %2 %3")
				.arg(ref.color.index)
				.arg(transformToString(ref.transformation))
				.arg(ref.name);
			return result;
		},
		[](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 Colored<CircularPrimitive>& circ) {
			return QStringLiteral("1 %1 %2 %3")
				.arg(circ.color.index)
				.arg(transformToString(circ.transformation))
				.arg(circularPrimitiveFilePath(circ));
		},
		[](const Comment& comment) {
			return "0 " + comment.text;
		},
		[](const Empty&) {
			return QStringLiteral("");
		},
		[](const ParseError& parseError) {
			return parseError.code;
		},
	}, element);
}

template<typename K, typename V>
void removeFromMap(std::map<K, V>& map, const K& key)
{
	const auto it = map.find(key);
	if (it != map.end()) {
		map.erase(it);
	}
}

/**
 * @brief Sets the path to the model
 * @param path New path to use
 */
void updateHeaderNameField(QTextDocument& model, const QString &name)
{
#if 0
	// Update the "Name: 1234.dat" comment
	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
}

mercurial