diff -r 3d8ab0f89102 -r 450827da2376 src/ExternalPrograms.cc
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ExternalPrograms.cc Tue Jan 21 02:09:14 2014 +0200
@@ -0,0 +1,696 @@
+/*
+ * LDForge: LDraw parts authoring CAD
+ * Copyright (C) 2013, 2014 Santeri 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 .
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "Main.h"
+#include "Configuration.h"
+#include "Misc.h"
+#include "MainWindow.h"
+#include "Document.h"
+#include "Widgets.h"
+#include "EditHistory.h"
+#include "ui_ytruder.h"
+#include "ui_intersector.h"
+#include "ui_rectifier.h"
+#include "ui_coverer.h"
+#include "ui_isecalc.h"
+#include "ui_edger2.h"
+#include "Dialogs.h"
+
+enum extprog
+{
+ Isecalc,
+ Intersector,
+ Coverer,
+ Ytruder,
+ Rectifier,
+ Edger2,
+};
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+cfg (String, prog_isecalc, "");
+cfg (String, prog_intersector, "");
+cfg (String, prog_coverer, "");
+cfg (String, prog_ytruder, "");
+cfg (String, prog_rectifier, "");
+cfg (String, prog_edger2, "");
+
+QString* const g_extProgPaths[] =
+{
+ &prog_isecalc,
+ &prog_intersector,
+ &prog_coverer,
+ &prog_ytruder,
+ &prog_rectifier,
+ &prog_edger2,
+};
+
+#ifndef _WIN32
+cfg (Bool, prog_isecalc_wine, false);
+cfg (Bool, prog_intersector_wine, false);
+cfg (Bool, prog_coverer_wine, false);
+cfg (Bool, prog_ytruder_wine, false);
+cfg (Bool, prog_rectifier_wine, false);
+cfg (Bool, prog_edger2_wine, false);
+
+bool* const g_extProgWine[] =
+{
+ &prog_isecalc_wine,
+ &prog_intersector_wine,
+ &prog_coverer_wine,
+ &prog_ytruder_wine,
+ &prog_rectifier_wine,
+ &prog_edger2_wine,
+};
+#endif // _WIN32
+
+const char* g_extProgNames[] =
+{
+ "Isecalc",
+ "Intersector",
+ "Coverer",
+ "Ytruder",
+ "Rectifier",
+ "Edger2"
+};
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+static bool mkTempFile (QTemporaryFile& tmp, QString& fname)
+{
+ if (!tmp.open())
+ return false;
+
+ fname = tmp.fileName();
+ tmp.close();
+ return true;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+static bool checkProgPath (const extprog prog)
+{
+ QString& path = *g_extProgPaths[prog];
+
+ if (path.length() > 0)
+ return true;
+
+ ExtProgPathPrompt* dlg = new ExtProgPathPrompt (g_extProgNames[prog]);
+
+ if (dlg->exec() && !dlg->getPath().isEmpty())
+ {
+ path = dlg->getPath();
+ return true;
+ }
+
+ return false;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+static QString processErrorString (extprog prog, QProcess& proc)
+{
+ switch (proc.error())
+ {
+ case QProcess::FailedToStart:
+ {
+ QString wineblurb;
+
+#ifndef _WIN32
+ if (*g_extProgWine[prog])
+ wineblurb = "make sure Wine is installed and ";
+#endif
+
+ return fmt ("Program failed to start, %1check your permissions", wineblurb);
+ } break;
+
+ case QProcess::Crashed:
+ return "Crashed.";
+
+ case QProcess::WriteError:
+ case QProcess::ReadError:
+ return "I/O error.";
+
+ case QProcess::UnknownError:
+ return "Unknown error";
+
+ case QProcess::Timedout:
+ return fmt ("Timed out (30 seconds)");
+ }
+
+ return "";
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+static void writeObjects (const LDObjectList& objects, QFile& f)
+{
+ for (LDObject* obj : objects)
+ {
+ if (obj->getType() == LDObject::ESubfile)
+ {
+ LDSubfile* ref = static_cast (obj);
+ LDObjectList objs = ref->inlineContents (LDSubfile::DeepInline);
+
+ writeObjects (objs, f);
+
+ for (LDObject* obj : objs)
+ obj->deleteSelf();
+ }
+ else
+ f.write ((obj->raw() + "\r\n").toUtf8());
+ }
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+static void writeObjects (const LDObjectList& objects, QString fname)
+{
+ // Write the input file
+ QFile f (fname);
+
+ if (!f.open (QIODevice::WriteOnly | QIODevice::Text))
+ {
+ critical (fmt ("Couldn't open temporary file %1 for writing: %2\n", fname, f.errorString()));
+ return;
+ }
+
+ writeObjects (objects, f);
+ f.close();
+
+#ifdef DEBUG
+ QFile::copy (fname, "debug_lastInput");
+#endif
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void writeSelection (QString fname)
+{
+ writeObjects (selection(), fname);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+void writeColorGroup (const int colnum, QString fname)
+{
+ LDObjectList objects;
+
+ for (LDObject* obj : getCurrentDocument()->getObjects())
+ {
+ if (obj->isColored() == false || obj->getColor() != colnum)
+ continue;
+
+ objects << obj;
+ }
+
+ writeObjects (objects, fname);
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+bool runUtilityProcess (extprog prog, QString path, QString argvstr)
+{
+ QTemporaryFile input;
+ QStringList argv = argvstr.split (" ", QString::SkipEmptyParts);
+
+#ifndef _WIN32
+ if (*g_extProgWine[prog])
+ {
+ argv.insert (0, path);
+ path = "wine";
+ }
+#endif // _WIN32
+
+ log ("cmdline: %1 %2\n", path, argv.join (" "));
+
+ if (!input.open())
+ return false;
+
+ QProcess proc;
+
+ // Begin!
+ proc.setStandardInputFile (input.fileName());
+ proc.start (path, argv);
+
+ if (!proc.waitForStarted())
+ {
+ critical (fmt ("Couldn't start %1: %2\n", g_extProgNames[prog], processErrorString (prog, proc)));
+ return false;
+ }
+
+ // Write an enter, the utility tools all expect one
+ input.write ("\n");
+
+ // Wait while it runs
+ proc.waitForFinished();
+
+ QString err = "";
+
+ if (proc.exitStatus() != QProcess::NormalExit)
+ err = processErrorString (prog, proc);
+
+ // Check the return code
+ if (proc.exitCode() != 0)
+ err = fmt ("Program exited abnormally (return code %1).", proc.exitCode());
+
+ if (!err.isEmpty())
+ {
+ critical (fmt ("%1 failed: %2\n", g_extProgNames[prog], err));
+ return false;
+ }
+
+ return true;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+static void insertOutput (QString fname, bool replace, QList colorsToReplace)
+{
+#ifdef DEBUG
+ QFile::copy (fname, "./debug_lastOutput");
+#endif // RELEASE
+
+ // Read the output file
+ QFile f (fname);
+
+ if (!f.open (QIODevice::ReadOnly))
+ {
+ critical (fmt ("Couldn't open temporary file %1 for reading.\n", fname));
+ return;
+ }
+
+ LDObjectList objs = loadFileContents (&f, null);
+
+ // If we replace the objects, delete the selection now.
+ if (replace)
+ g_win->deleteSelection();
+
+ for (int colnum : colorsToReplace)
+ g_win->deleteByColor (colnum);
+
+ // Insert the new objects
+ getCurrentDocument()->clearSelection();
+
+ for (LDObject* obj : objs)
+ {
+ if (!obj->isScemantic())
+ {
+ obj->deleteSelf();
+ continue;
+ }
+
+ getCurrentDocument()->addObject (obj);
+ obj->select();
+ }
+
+ g_win->doFullRefresh();
+}
+
+// =============================================================================
+// Interface for Ytruder
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (Ytruder, 0)
+{
+ setlocale (LC_ALL, "C");
+
+ if (!checkProgPath (Ytruder))
+ return;
+
+ QDialog* dlg = new QDialog;
+ Ui::YtruderUI ui;
+ ui.setupUi (dlg);
+
+ if (!dlg->exec())
+ return;
+
+ // Read the user's choices
+ const enum { Distance, Symmetry, Projection, Radial } mode =
+ ui.mode_distance->isChecked() ? Distance :
+ ui.mode_symmetry->isChecked() ? Symmetry :
+ ui.mode_projection->isChecked() ? Projection : Radial;
+
+ const Axis axis =
+ ui.axis_x->isChecked() ? X :
+ ui.axis_y->isChecked() ? Y : Z;
+
+ const double depth = ui.planeDepth->value(),
+ condAngle = ui.condAngle->value();
+
+ QTemporaryFile indat, outdat;
+ QString inDATName, outDATName;
+
+ // Make temp files for the input and output files
+ if (!mkTempFile (indat, inDATName) || !mkTempFile (outdat, outDATName))
+ return;
+
+ // Compose the command-line arguments
+ QString argv = join (
+ {
+ (axis == X) ? "-x" : (axis == Y) ? "-y" : "-z",
+ (mode == Distance) ? "-d" : (mode == Symmetry) ? "-s" : (mode == Projection) ? "-p" : "-r",
+ depth,
+ "-a",
+ condAngle,
+ inDATName,
+ outDATName
+ });
+
+ writeSelection (inDATName);
+
+ if (!runUtilityProcess (Ytruder, prog_ytruder, argv))
+ return;
+
+ insertOutput (outDATName, false, {});
+}
+
+// =============================================================================
+// Rectifier interface
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (Rectifier, 0)
+{
+ setlocale (LC_ALL, "C");
+
+ if (!checkProgPath (Rectifier))
+ return;
+
+ QDialog* dlg = new QDialog;
+ Ui::RectifierUI ui;
+ ui.setupUi (dlg);
+
+ if (!dlg->exec())
+ return;
+
+ QTemporaryFile indat, outdat;
+ QString inDATName, outDATName;
+
+ // Make temp files for the input and output files
+ if (!mkTempFile (indat, inDATName) || !mkTempFile (outdat, outDATName))
+ return;
+
+ // Compose arguments
+ QString argv = join (
+ {
+ (!ui.cb_condense->isChecked()) ? "-q" : "",
+ (!ui.cb_subst->isChecked()) ? "-r" : "",
+ (ui.cb_condlineCheck->isChecked()) ? "-a" : "",
+ (ui.cb_colorize->isChecked()) ? "-c" : "",
+ "-t",
+ ui.dsb_coplthres->value(),
+ inDATName,
+ outDATName
+ });
+
+ writeSelection (inDATName);
+
+ if (!runUtilityProcess (Rectifier, prog_rectifier, argv))
+ return;
+
+ insertOutput (outDATName, true, {});
+}
+
+// =============================================================================
+// Intersector interface
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (Intersector, 0)
+{
+ setlocale (LC_ALL, "C");
+
+ if (!checkProgPath (Intersector))
+ return;
+
+ QDialog* dlg = new QDialog;
+ Ui::IntersectorUI ui;
+ ui.setupUi (dlg);
+
+ makeColorComboBox (ui.cmb_incol);
+ makeColorComboBox (ui.cmb_cutcol);
+ ui.cb_repeat->setWhatsThis ("If this is set, " APPNAME " runs Intersector a second time with inverse files to cut the "
+ " cutter group with the input group. Both groups are cut by the intersection.");
+ ui.cb_edges->setWhatsThis ("Makes " APPNAME " try run Isecalc to create edgelines for the intersection.");
+
+ int inCol, cutCol;
+ const bool repeatInverse = ui.cb_repeat->isChecked();
+
+ forever
+ {
+ if (!dlg->exec())
+ return;
+
+ inCol = ui.cmb_incol->itemData (ui.cmb_incol->currentIndex()).toInt();
+ cutCol = ui.cmb_cutcol->itemData (ui.cmb_cutcol->currentIndex()).toInt();
+
+ if (inCol == cutCol)
+ {
+ critical ("Cannot use the same color group for both input and cutter!");
+ continue;
+ }
+
+ break;
+ }
+
+ // Five temporary files!
+ // indat = input group file
+ // cutdat = cutter group file
+ // outdat = primary output
+ // outdat2 = inverse output
+ // edgesdat = edges output (isecalc)
+ QTemporaryFile indat, cutdat, outdat, outdat2, edgesdat;
+ QString inDATName, cutDATName, outDATName, outDAT2Name, edgesDATName;
+
+ if (!mkTempFile (indat, inDATName) || !mkTempFile (cutdat, cutDATName) ||
+ !mkTempFile (outdat, outDATName) || !mkTempFile (outdat2, outDAT2Name) ||
+ !mkTempFile (edgesdat, edgesDATName))
+ {
+ return;
+ }
+
+ QString parms = join (
+ {
+ (ui.cb_colorize->isChecked()) ? "-c" : "",
+ (ui.cb_nocondense->isChecked()) ? "-t" : "",
+ "-s",
+ ui.dsb_prescale->value()
+ });
+
+ QString argv_normal = join (
+ {
+ parms,
+ inDATName,
+ cutDATName,
+ outDATName
+ });
+
+ QString argv_inverse = join (
+ {
+ parms,
+ cutDATName,
+ inDATName,
+ outDAT2Name
+ });
+
+ writeColorGroup (inCol, inDATName);
+ writeColorGroup (cutCol, cutDATName);
+
+ if (!runUtilityProcess (Intersector, prog_intersector, argv_normal))
+ return;
+
+ insertOutput (outDATName, false, {inCol});
+
+ if (repeatInverse && runUtilityProcess (Intersector, prog_intersector, argv_inverse))
+ insertOutput (outDAT2Name, false, {cutCol});
+
+ if (
+ ui.cb_edges->isChecked() &&
+ checkProgPath (Isecalc) &&
+ runUtilityProcess (Isecalc, prog_isecalc, join ( {inDATName, cutDATName, edgesDATName}))
+ )
+ insertOutput (edgesDATName, false, {});
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (Coverer, 0)
+{
+ setlocale (LC_ALL, "C");
+
+ if (!checkProgPath (Coverer))
+ return;
+
+ QDialog* dlg = new QDialog;
+ Ui::CovererUI ui;
+ ui.setupUi (dlg);
+ makeColorComboBox (ui.cmb_col1);
+ makeColorComboBox (ui.cmb_col2);
+
+ int in1Col, in2Col;
+
+ forever
+ {
+ if (!dlg->exec())
+ return;
+
+ in1Col = ui.cmb_col1->itemData (ui.cmb_col1->currentIndex()).toInt();
+ in2Col = ui.cmb_col2->itemData (ui.cmb_col2->currentIndex()).toInt();
+
+ if (in1Col == in2Col)
+ {
+ critical ("Cannot use the same color group for both input and cutter!");
+ continue;
+ }
+
+ break;
+ }
+
+ QTemporaryFile in1dat, in2dat, outdat;
+ QString in1DATName, in2DATName, outDATName;
+
+ if (!mkTempFile (in1dat, in1DATName) || !mkTempFile (in2dat, in2DATName) || !mkTempFile (outdat, outDATName))
+ return;
+
+ QString argv = join (
+ {
+ (ui.cb_oldsweep->isChecked() ? "-s" : ""),
+ (ui.cb_reverse->isChecked() ? "-r" : ""),
+ (ui.dsb_segsplit->value() != 0 ? fmt ("-l %1", ui.dsb_segsplit->value()) : ""),
+ (ui.sb_bias->value() != 0 ? fmt ("-s %1", ui.sb_bias->value()) : ""),
+ in1DATName,
+ in2DATName,
+ outDATName
+ });
+
+ writeColorGroup (in1Col, in1DATName);
+ writeColorGroup (in2Col, in2DATName);
+
+ if (!runUtilityProcess (Coverer, prog_coverer, argv))
+ return;
+
+ insertOutput (outDATName, false, {});
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (Isecalc, 0)
+{
+ setlocale (LC_ALL, "C");
+
+ if (!checkProgPath (Isecalc))
+ return;
+
+ Ui::IsecalcUI ui;
+ QDialog* dlg = new QDialog;
+ ui.setupUi (dlg);
+
+ makeColorComboBox (ui.cmb_col1);
+ makeColorComboBox (ui.cmb_col2);
+
+ int in1Col, in2Col;
+
+ // Run the dialog and validate input
+ forever
+ {
+ if (!dlg->exec())
+ return;
+
+ in1Col = ui.cmb_col1->itemData (ui.cmb_col1->currentIndex()).toInt(),
+ in2Col = ui.cmb_col1->itemData (ui.cmb_col2->currentIndex()).toInt();
+
+ if (in1Col == in2Col)
+ {
+ critical ("Cannot use the same color group for both input and cutter!");
+ continue;
+ }
+
+ break;
+ }
+
+ QTemporaryFile in1dat, in2dat, outdat;
+ QString in1DATName, in2DATName, outDATName;
+
+ if (!mkTempFile (in1dat, in1DATName) || !mkTempFile (in2dat, in2DATName) || !mkTempFile (outdat, outDATName))
+ return;
+
+ QString argv = join (
+ {
+ in1DATName,
+ in2DATName,
+ outDATName
+ });
+
+ writeColorGroup (in1Col, in1DATName);
+ writeColorGroup (in2Col, in2DATName);
+ runUtilityProcess (Isecalc, prog_isecalc, argv);
+ insertOutput (outDATName, false, {});
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+DEFINE_ACTION (Edger2, 0)
+{
+ setlocale (LC_ALL, "C");
+
+ if (!checkProgPath (Edger2))
+ return;
+
+ QDialog* dlg = new QDialog;
+ Ui::Edger2Dialog ui;
+ ui.setupUi (dlg);
+
+ if (!dlg->exec())
+ return;
+
+ QTemporaryFile in, out;
+ QString inName, outName;
+
+ if (!mkTempFile (in, inName) || !mkTempFile (out, outName))
+ return;
+
+ int unmatched = ui.unmatched->currentIndex();
+
+ QString argv = join (
+ {
+ fmt ("-p %1", ui.precision->value()),
+ fmt ("-af %1", ui.flatAngle->value()),
+ fmt ("-ac %1", ui.condAngle->value()),
+ fmt ("-ae %1", ui.edgeAngle->value()),
+ ui.delLines->isChecked() ? "-de" : "",
+ ui.delCondLines->isChecked() ? "-dc" : "",
+ ui.colored->isChecked() ? "-c" : "",
+ ui.bfc->isChecked() ? "-b" : "",
+ ui.convex->isChecked() ? "-cx" : "",
+ ui.concave->isChecked() ? "-cv" : "",
+ unmatched == 0 ? "-u+" : (unmatched == 2 ? "-u-" : ""),
+ inName,
+ outName,
+ });
+
+ writeSelection (inName);
+
+ if (!runUtilityProcess (Edger2, prog_edger2, argv))
+ return;
+
+ insertOutput (outName, true, {});
+}