src/toolsets/algorithmtoolset.cpp

Thu, 04 Jan 2018 22:42:01 +0200

author
Santeri Piippo
date
Thu, 04 Jan 2018 22:42:01 +0200
changeset 1223
4f8fa42aed07
parent 1222
34def2630300
child 1224
5a31b6d4bf81
permissions
-rw-r--r--

simplified RoundToDecimals

/*
 *  LDForge: LDraw parts authoring CAD
 *  Copyright (C) 2013 - 2018 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 <limits>
#include <QBoxLayout>
#include <QCheckBox>
#include <QDir>
#include <QInputDialog>
#include <QSpinBox>
#include "../mainwindow.h"
#include "../main.h"
#include "../ldDocument.h"
#include "../miscallenous.h"
#include "../radioGroup.h"
#include "../glRenderer.h"
#include "../dialogs.h"
#include "../colors.h"
#include "../ldObjectMath.h"
#include "../ldobjectiterator.h"
#include "../documentmanager.h"
#include "ui_replcoords.h"
#include "ui_editraw.h"
#include "ui_flip.h"
#include "ui_addhistoryline.h"
#include "algorithmtoolset.h"

ConfigOption(int RoundPositionPrecision = 3)
ConfigOption(int RoundMatrixPrecision = 4)
ConfigOption(int SplitLinesSegments = 5)

AlgorithmToolset::AlgorithmToolset(MainWindow* parent) :
	Toolset(parent)
{
}

void AlgorithmToolset::splitQuads()
{
	int num = 0;

	for (LDObjectIterator<LDQuad> it(selectedObjects()); it.isValid(); ++it)
	{
		// Find the index of this quad
		int index = it->lineNumber();

		if (index == -1)
			return;

		QList<LDTriangle*> triangles = it->splitToTriangles();

		// Replace the quad with the first triangle and add the second triangle
		// after the first one.
		currentDocument()->setObject(index, triangles[0]);
		currentDocument()->insertObj(index + 1, triangles[1]);
		num++;
	}

	print("%1 quadrilaterals split", num);
}

void AlgorithmToolset::editRaw()
{
	if (selectedObjects().size() != 1)
		return;

	LDObject* obj = selectedObjects()[0];
	QDialog* dlg = new QDialog;
	Ui::EditRawUI ui;

	ui.setupUi(dlg);
	ui.code->setText(obj->asText());

	if (obj->type() == OBJ_Error)
		ui.errorDescription->setText(static_cast<LDError*>(obj)->reason());
	else
	{
		ui.errorDescription->hide();
		ui.errorIcon->hide();
	}

	if (dlg->exec() == QDialog::Rejected)
		return;

	// Reinterpret it from the text of the input field
	LDObject* newobj = ParseLine(ui.code->text());
	obj->replace(newobj);
}

void AlgorithmToolset::makeBorders()
{
	LDObjectList objs = selectedObjects();
	int num = 0;

	for (LDObject* obj : objs)
	{
		const LDObjectType type = obj->type();

		if (type != OBJ_Quad and type != OBJ_Triangle)
			continue;

		LDLine* lines[4];

		if (type == OBJ_Quad)
		{
			LDQuad* quad = static_cast<LDQuad*>(obj);
			lines[0] = LDSpawn<LDLine>(quad->vertex(0), quad->vertex(1));
			lines[1] = LDSpawn<LDLine>(quad->vertex(1), quad->vertex(2));
			lines[2] = LDSpawn<LDLine>(quad->vertex(2), quad->vertex(3));
			lines[3] = LDSpawn<LDLine>(quad->vertex(3), quad->vertex(0));
		}
		else
		{
			LDTriangle* tri = static_cast<LDTriangle*>(obj);
			lines[0] = LDSpawn<LDLine>(tri->vertex(0), tri->vertex(1));
			lines[1] = LDSpawn<LDLine>(tri->vertex(1), tri->vertex(2));
			lines[2] = LDSpawn<LDLine>(tri->vertex(2), tri->vertex(0));
			lines[3] = nullptr;
		}

		for (int i = 0; i < countof(lines); ++i)
		{
			if (lines[i] == nullptr)
				continue;

			long idx = obj->lineNumber() + i + 1;
			currentDocument()->insertObj(idx, lines[i]);
			++num;
		}
	}

	print(tr("Added %1 border lines"), num);
}

void AlgorithmToolset::roundCoordinates()
{
	setlocale(LC_ALL, "C");
	int num = 0;

	for (LDObject* obj : selectedObjects())
	{
		LDMatrixObject* mo = dynamic_cast<LDMatrixObject*>(obj);

		if (mo)
		{
			Vertex v = mo->position();
			Matrix t = mo->transform();

			v.apply([&](Axis, double& a)
			{
				a = RoundToDecimals(a, config.roundPositionPrecision());
			});

			ApplyToMatrix(t, [&](int, double& a)
			{
				a = RoundToDecimals(a, config.roundMatrixPrecision());
			});

			mo->setPosition(v);
			mo->setTransform(t);
			num += 12;
		}
		else
		{
			for (int i = 0; i < obj->numVertices(); ++i)
			{
				Vertex v = obj->vertex(i);
				v.apply([&](Axis, double& a)
				{
					a = RoundToDecimals(a, config.roundPositionPrecision());
				});
				obj->setVertex(i, v);
				num += 3;
			}
		}
	}

	print(tr("Rounded %1 values"), num);
	m_window->refreshObjectList();
}

void AlgorithmToolset::replaceCoordinates()
{
	QDialog* dlg = new QDialog(m_window);
	Ui::ReplaceCoordsUI ui;
	ui.setupUi(dlg);

	if (not dlg->exec())
		return;

	const double search = ui.search->value(),
		replacement = ui.replacement->value();
	const bool any = ui.any->isChecked(),
		rel = ui.relative->isChecked();

	QList<Axis> sel;
	int num = 0;

	if (ui.x->isChecked()) sel << X;
	if (ui.y->isChecked()) sel << Y;
	if (ui.z->isChecked()) sel << Z;

	for (LDObject* obj : selectedObjects())
	{
		for (int i = 0; i < obj->numVertices(); ++i)
		{
			Vertex v = obj->vertex(i);

			v.apply([&](Axis ax, double& coord)
			{
				if (not sel.contains(ax) or
					(not any and coord != search))
				{
					return;
				}

				if (not rel)
					coord = 0;

				coord += replacement;
				num++;
			});

			obj->setVertex(i, v);
		}
	}

	print(tr("Altered %1 values"), num);
}

void AlgorithmToolset::flip()
{
	QDialog* dlg = new QDialog;
	Ui::FlipUI ui;
	ui.setupUi(dlg);

	if (not dlg->exec())
		return;

	QList<Axis> sel;

	if (ui.x->isChecked()) sel << X;
	if (ui.y->isChecked()) sel << Y;
	if (ui.z->isChecked()) sel << Z;

	for (LDObject* obj : selectedObjects())
	{
        LDMatrixObject* mo = dynamic_cast<LDMatrixObject*>(obj);

		if (mo)
		{
            Matrix transformationMatrix = mo->transform();
            if (sel.contains(X))
            {
                transformationMatrix[0] *= -1;
                transformationMatrix[1] *= -1;
                transformationMatrix[2] *= -1;
            }
            if (sel.contains(Y))
            {
                transformationMatrix[3] *= -1;
                transformationMatrix[4] *= -1;
                transformationMatrix[5] *= -1;
            }
            if (sel.contains(Z))
            {
                transformationMatrix[6] *= -1;
                transformationMatrix[7] *= -1;
                transformationMatrix[8] *= -1;
            }
            mo->setTransform(transformationMatrix);
            Vertex position = mo->position();
			position.apply([&](Axis ax, double& a)
			{
				if (sel.contains(ax))
					a = -a;
			});
            mo->setPosition(position);
        }
		for (int i = 0; i < obj->numVertices(); ++i)
		{
			Vertex v = obj->vertex(i);

			v.apply([&](Axis ax, double& a)
			{
				if (sel.contains(ax))
					a = -a;
			});

			obj->setVertex(i, v);
		}
		if (not mo and sel.size() % 2 == 1)
            obj->invert();
	}
}

void AlgorithmToolset::demote()
{
	int num = 0;

	for (LDObjectIterator<LDCondLine> it(selectedObjects()); it.isValid(); ++it)
	{
		it->toEdgeLine();
		++num;
	}

	print(tr("Converted %1 conditional lines"), num);
}

bool AlgorithmToolset::isColorUsed(LDColor color)
{
	for (LDObject* obj : currentDocument()->objects())
	{
		if (obj->isColored() and obj->color() == color)
			return true;
	}

	return false;
}

void AlgorithmToolset::autocolor()
{
	LDColor color;

	for (color = 0; color.isLDConfigColor(); ++color)
	{
		if (color.isValid() and not isColorUsed(color))
			break;
	}

	if (not color.isLDConfigColor())
	{
		print(tr("Cannot auto-color: all colors are in use!"));
		return;
	}

	for (LDObject* obj : selectedObjects())
	{
		if (not obj->isColored())
			continue;

		obj->setColor(color);
	}

	print(tr("Auto-colored: new color is [%1] %2"), color.index(), color.name());
}

void AlgorithmToolset::addHistoryLine()
{
	LDObject* obj;
	bool ishistory = false;
	bool prevIsHistory = false;

	QDialog* dlg = new QDialog;
	Ui_AddHistoryLine* ui = new Ui_AddHistoryLine;
	ui->setupUi(dlg);
	ui->m_username->setText(config.defaultUser());
	ui->m_date->setDate(QDate::currentDate());
	ui->m_comment->setFocus();

	if (not dlg->exec())
		return;

	// Create the comment object based on input
	LDComment* comment = new LDComment(format("!HISTORY %1 [%2] %3",
		ui->m_date->date().toString("yyyy-MM-dd"),
		ui->m_username->text(),
		ui->m_comment->text()));

	// Find a spot to place the new comment
	for (obj = currentDocument()->getObject(0);
		obj and obj->next() and not obj->next()->isScemantic();
		obj = obj->next())
	{
		LDComment* comment = dynamic_cast<LDComment*>(obj);

		if (comment and comment->text().startsWith("!HISTORY "))
			ishistory = true;

		if (prevIsHistory and not ishistory)
			break; // Last line was history, this isn't, thus insert the new history line here.

		prevIsHistory = ishistory;
	}

	int idx = obj ? obj->lineNumber() : 0;
	currentDocument()->insertObj(idx++, comment);

	// If we're adding a history line right before a scemantic object, pad it
	// an empty line
	if (obj and obj->next() and obj->next()->isScemantic())
		currentDocument()->insertObj(idx, new LDEmpty);

	m_window->buildObjectList();
	delete ui;
}

void AlgorithmToolset::splitLines()
{
	bool ok;
	int segments = QInputDialog::getInt(m_window, APPNAME, "Amount of segments:",
		config.splitLinesSegments(), 0, std::numeric_limits<int>::max(), 1, &ok);

	if (not ok)
		return;

	config.setSplitLinesSegments(segments);

	for (LDObject* obj : selectedObjects())
	{
		if (not isOneOf(obj->type(), OBJ_Line, OBJ_CondLine))
			continue;

		QVector<LDObject*> newsegs;

		for (int i = 0; i < segments; ++i)
		{
			LDObject* segment;
			Vertex v0, v1;

			v0.apply([&](Axis ax, double& a)
			{
				double len = obj->vertex(1)[ax] - obj->vertex(0)[ax];
				a = (obj->vertex(0)[ax] +((len * i) / segments));
			});

			v1.apply([&](Axis ax, double& a)
			{
				double len = obj->vertex(1)[ax] - obj->vertex(0)[ax];
				a = (obj->vertex(0)[ax] +((len *(i + 1)) / segments));
			});

			if (obj->type() == OBJ_Line)
				segment = LDSpawn<LDLine>(v0, v1);
			else
				segment = LDSpawn<LDCondLine>(v0, v1, obj->vertex(2), obj->vertex(3));

			newsegs << segment;
		}

		int ln = obj->lineNumber();

		for (LDObject* seg : newsegs)
			currentDocument()->insertObj(ln++, seg);

		obj->destroy();
	}

	m_window->buildObjectList();
	m_window->refresh();
}

void AlgorithmToolset::subfileSelection()
{
	if (selectedObjects().size() == 0)
		return;

	QString			parentpath(currentDocument()->fullPath());

	// BFC type of the new subfile - it shall inherit the BFC type of the parent document
	BfcStatement	bfctype(BfcStatement::NoCertify);

	// Dirname of the new subfile
	QString			subdirname(Dirname(parentpath));

	// Title of the new subfile
	QString			subtitle;

	// Comment containing the title of the parent document
	LDComment*	titleobj = dynamic_cast<LDComment*>(currentDocument()->getObject(0));

	// License text for the subfile
	QString			license(PreferredLicenseText());

	// LDraw code body of the new subfile(i.e. code of the selection)
	QStringList		code;

	// Full path of the subfile to be
	QString			fullsubname;

	// Where to insert the subfile reference?
	int				refidx(selectedObjects()[0]->lineNumber());

	// Determine title of subfile
	if (titleobj)
		subtitle = "~" + titleobj->text();
	else
		subtitle = "~subfile";

	// Remove duplicate tildes
	while (subtitle.startsWith("~~"))
		subtitle.remove(0, 1);

	// If this the parent document isn't already in s/, we need to stuff it into
	// a subdirectory named s/. Ensure it exists!
	QString topdirname = Basename(Dirname(currentDocument()->fullPath()));

	if (topdirname != "s")
	{
		QString desiredPath = subdirname + "/s";
		QString title = tr("Create subfile directory?");
		QString text = format(tr("The directory <b>%1</b> is suggested for "
			"subfiles. This directory does not exist, create it?"), desiredPath);

		if (QDir(desiredPath).exists() or Confirm(title, text))
		{
			subdirname = desiredPath;
			QDir().mkpath(subdirname);
		}
		else
			return;
	}

	// Determine the body of the name of the subfile
	if (not parentpath.isEmpty())
	{
		// Chop existing '.dat' suffix
		if (parentpath.endsWith(".dat"))
			parentpath.chop(4);

		// Remove the s?? suffix if it's there, otherwise we'll get filenames
		// like s01s01.dat when subfiling subfiles.
		QRegExp subfilesuffix("s[0-9][0-9]$");
		if (subfilesuffix.indexIn(parentpath) != -1)
			parentpath.chop(subfilesuffix.matchedLength());

		int subidx = 1;
		QString digits;

		// Now find the appropriate filename. Increase the number of the subfile
		// until we find a name which isn't already taken.
		do
		{
			digits.setNum(subidx++);

			// pad it with a zero
			if (digits.length() == 1)
				digits.prepend("0");

			fullsubname = subdirname + "/" + Basename(parentpath) + "s" + digits + ".dat";
		} while (m_documents->findDocumentByName("s\\" + Basename(fullsubname)) or QFile(fullsubname).exists());
	}

	// Determine the BFC winding type used in the main document - it is to
	// be carried over to the subfile.
	for (LDObjectIterator<LDBfc> it(currentDocument()); it.isValid(); ++it)
	{
		if (isOneOf(it->statement(), BfcStatement::CertifyCCW, BfcStatement::CertifyCW, BfcStatement::NoCertify))
		{
			bfctype = it->statement();
			break;
		}
	}

	// Get the body of the document in LDraw code
	for (LDObject* obj : selectedObjects())
		code << obj->asText();

	// Create the new subfile document
	LDDocument* doc = m_window->newDocument();
	doc->openForEditing();
	doc->setFullPath(fullsubname);
	doc->setName(LDDocument::shortenName(fullsubname));

	LDObjectList objs;
	objs << LDSpawn<LDComment>(subtitle);
	objs << LDSpawn<LDComment>("Name: "); // This gets filled in when the subfile is saved
	objs << LDSpawn<LDComment>(format("Author: %1 [%2]", config.defaultName(), config.defaultUser()));
	objs << LDSpawn<LDComment>("!LDRAW_ORG Unofficial_Subpart");

	if (not license.isEmpty())
		objs << LDSpawn<LDComment>(license);

	objs << LDSpawn<LDEmpty>();
	objs << LDSpawn<LDBfc>(bfctype);
	objs << LDSpawn<LDEmpty>();

	doc->addObjects(objs);

	// Add the actual subfile code to the new document
	for (QString line : code)
	{
		LDObject* obj = ParseLine(line);
		doc->addObject(obj);
	}

	// Try save it
	if (m_window->save(doc, true))
	{
		// Save was successful. Delete the original selection now from the
		// main document.
		for (LDObject* obj : selectedObjects())
			obj->destroy();

		// Add a reference to the new subfile to where the selection was
		LDSubfileReference* ref = LDSpawn<LDSubfileReference>();
		ref->setColor(MainColor);
		ref->setFileInfo(doc);
		ref->setPosition(Vertex {});
		ref->setTransform(Matrix {});
		currentDocument()->insertObj(refidx, ref);

		// Refresh stuff
		m_window->updateDocumentList();
		m_window->doFullRefresh();
	}
	else
	{
		// Failed to save.
		doc->close();
	}
}

mercurial