diff -r 9374fea8f77f -r f1b8cb53d2a2 src/extprogs.cpp
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/extprogs.cpp Wed May 08 15:19:06 2013 +0300
@@ -0,0 +1,461 @@
+/*
+ * LDForge: LDraw parts authoring CAD
+ * Copyright (C) 2013 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 "common.h"
+#include "config.h"
+#include "misc.h"
+#include "extprogs.h"
+#include "gui.h"
+#include "file.h"
+#include "radiobox.h"
+#include "history.h"
+
+// =============================================================================
+cfg (str, prog_isecalc, "");
+cfg (str, prog_intersector, "");
+cfg (str, prog_coverer, "");
+cfg (str, prog_ytruder, "");
+cfg (str, prog_datheader, "");
+cfg (str, prog_rectifier, "");
+
+const char* g_extProgNames[] = {
+ "Isecalc",
+ "Intersector",
+ "Coverer",
+ "Ytruder",
+ "Rectifier",
+ "DATHeader",
+};
+
+// =============================================================================
+static bool checkProgPath (str path, const extprog prog) {
+ if (~path)
+ return true;
+
+ const char* name = g_extProgNames[prog];
+
+ critical (fmt ("Couldn't run %s as no path has "
+ "been defined for it. Use the configuration dialog's External Programs "
+ "tab to define a path for %s.", name, name));
+ return false;
+}
+
+// =============================================================================
+static void processError (const extprog prog, QProcess& proc) {
+ const char* name = g_extProgNames[prog];
+ str errmsg;
+
+ switch (proc.error ()) {
+ case QProcess::FailedToStart:
+ errmsg = fmt ("Failed to launch %s. Check that you have set the proper path "
+ "to %s and that you have the proper permissions to launch it.", name, name);
+ break;
+
+ case QProcess::Crashed:
+ errmsg = fmt ("%s crashed.", name);
+ break;
+
+ case QProcess::WriteError:
+ case QProcess::ReadError:
+ errmsg = fmt ("I/O error while interacting with %s.", name);
+ break;
+
+ case QProcess::UnknownError:
+ errmsg = fmt ("Unknown error occurred while executing %s.", name);
+ break;
+
+ case QProcess::Timedout:
+ errmsg = fmt ("%s timed out.", name);
+ break;
+ }
+
+ critical (errmsg);
+}
+
+// =============================================================================
+static bool mkTempFile (QTemporaryFile& tmp, str& fname) {
+ if (!tmp.open ())
+ return false;
+
+ fname = tmp.fileName ();
+ tmp.close ();
+ return true;
+}
+
+// =============================================================================
+void writeObjects (std::vector& objects, str fname) {
+ // Write the input file
+ FILE* fp = fopen (fname, "w");
+ if (!fp) {
+ critical (fmt ("Couldn't open temporary file %s for writing.\n", fname.chars ()));
+ return;
+ }
+
+ for (LDObject* obj : objects) {
+ str line = fmt ("%s\r\n", obj->getContents ().chars ());
+ fwrite (line.chars(), 1, ~line, fp);
+ }
+
+#ifndef RELEASE
+ ushort idx = rand ();
+ printf ("%s -> debug_%u\n", fname.chars (), idx);
+ QFile::copy (fname.chars (), fmt ("debug_%u", idx));
+#endif // RELEASE
+
+ fclose (fp);
+}
+
+// =============================================================================
+void writeSelection (str fname) {
+ writeObjects (g_win->sel (), fname);
+}
+
+// =============================================================================
+void writeColorGroup (const short colnum, str fname) {
+ std::vector objects;
+ for (LDObject*& obj : g_curfile->m_objs) {
+ if (obj->isColored () == false || obj->dColor != colnum)
+ continue;
+
+ objects.push_back (obj);
+ }
+
+ writeObjects (objects, fname);
+}
+
+// =============================================================================
+void runUtilityProcess (extprog prog, str path, QString argvstr) {
+ QTemporaryFile input, output;
+ str inputname, outputname;
+ QStringList argv = argvstr.split (" ", QString::SkipEmptyParts);
+
+ printf ("cmdline: %s %s\n", path.chars (), qchars (argvstr));
+
+ if (!mkTempFile (input, inputname) || !mkTempFile (output, outputname))
+ return;
+
+ QProcess proc;
+
+ // Init stdin
+ FILE* stdinfp = fopen (inputname, "w");
+
+ // Begin!
+ proc.setStandardInputFile (inputname);
+ proc.start (path, argv);
+
+ // Write an enter - one is expected
+ char enter[2] = "\n";
+ enter[1] = '\0';
+ fwrite (enter, 1, sizeof enter, stdinfp);
+ fflush (stdinfp);
+
+ // Wait while it runs
+ proc.waitForFinished ();
+
+#ifndef RELASE
+ printf ("%s", qchars (QString (proc.readAllStandardOutput ())));
+#endif // RELEASE
+
+ if (proc.exitStatus () == QProcess::CrashExit) {
+ processError (prog, proc);
+ return;
+ }
+}
+
+// ========================================================================================================================================
+static void insertOutput (str fname, bool replace, vector colorsToReplace) {
+#ifndef RELEASE
+ QFile::copy (fname, "./debug_lastOutput");
+#endif // RELEASE
+
+ // Read the output file
+ FILE* fp = fopen (fname, "r");
+ if (!fp) {
+ critical (fmt ("Couldn't open temporary file %s for reading.\n", fname.chars ()));
+ return;
+ }
+
+ ComboHistory* cmb = new ComboHistory ({});
+ std::vector objs = loadFileContents (fp, null),
+ copies;
+ std::vector indices;
+
+ // If we replace the objects, delete the selection now.
+ if (replace)
+ *cmb << g_win->deleteSelection ();
+
+ for (const short colnum : colorsToReplace)
+ *cmb << g_win->deleteByColor (colnum);
+
+ // Insert the new objects
+ g_win->sel ().clear ();
+ for (LDObject* obj : objs) {
+ if (!obj->isSchemantic ()) {
+ delete obj;
+ continue;
+ }
+
+ ulong idx = g_curfile->addObject (obj);
+ indices.push_back (idx);
+ copies.push_back (obj->clone ());
+ g_win->sel ().push_back (obj);
+ }
+
+ if (indices.size() > 0)
+ *cmb << new AddHistory ({indices, copies});
+
+ if (cmb->paEntries.size () > 0)
+ History::addEntry (cmb);
+ else
+ delete cmb;
+
+ fclose (fp);
+ g_win->refresh ();
+}
+
+QDialogButtonBox* makeButtonBox (QDialog& dlg) {
+ QDialogButtonBox* bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+ QWidget::connect (bbx_buttons, SIGNAL (accepted ()), &dlg, SLOT (accept ()));
+ QWidget::connect (bbx_buttons, SIGNAL (rejected ()), &dlg, SLOT (reject ()));
+ return bbx_buttons;
+}
+
+// =============================================================================
+// Interface for Ytruder
+MAKE_ACTION (ytruder, "Ytruder", "ytruder", "Extrude selected lines to a given plane", KEY (F4)) {
+ setlocale (LC_ALL, "C");
+
+ if (!checkProgPath (prog_ytruder, Ytruder))
+ return;
+
+ QDialog dlg;
+
+ RadioBox* rb_mode = new RadioBox ("Extrusion mode", {"Distance", "Symmetry", "Projection", "Radial"}, 0, Qt::Horizontal);
+ RadioBox* rb_axis = new RadioBox ("Axis", {"X", "Y", "Z"}, 0, Qt::Horizontal);
+ LabeledWidget* dsb_depth = new LabeledWidget ("Plane depth"),
+ *dsb_condAngle = new LabeledWidget ("Conditional line threshold");
+
+ rb_axis->setValue (Y);
+ dsb_depth->w ()->setMinimum (-10000.0);
+ dsb_depth->w ()->setMaximum (10000.0);
+ dsb_depth->w ()->setDecimals (3);
+ dsb_condAngle->w ()->setValue (30.0f);
+
+ QVBoxLayout* layout = new QVBoxLayout (&dlg);
+ layout->addWidget (rb_mode);
+ layout->addWidget (rb_axis);
+ layout->addWidget (dsb_depth);
+ layout->addWidget (dsb_condAngle);
+ layout->addWidget (makeButtonBox (dlg));
+
+ dlg.setWindowIcon (getIcon ("extrude"));
+
+ if (!dlg.exec ())
+ return;
+
+ // Read the user's choices
+ const enum modetype { Distance, Symmetry, Projection, Radial } mode = (modetype) rb_mode->value ();
+ const Axis axis = (Axis) rb_axis->value ();
+ const double depth = dsb_depth->w ()->value (),
+ condAngle = dsb_condAngle->w ()->value ();
+
+ QTemporaryFile indat, outdat;
+ str inDATName, outDATName;
+
+ // Make temp files for the input and output files
+ if (!mkTempFile (indat, inDATName) || !mkTempFile (outdat, outDATName))
+ return;
+
+ // Compose the command-line arguments
+ str argv = fmt ("%s %s %f -a %f %s %s",
+ (axis == X) ? "-x" : (axis == Y) ? "-y" : "-z",
+ (mode == Distance) ? "-d" : (mode == Symmetry) ? "-s" : (mode == Projection) ? "-p" : "-r",
+ depth, condAngle, inDATName.chars (), outDATName.chars ());
+
+ writeSelection (inDATName);
+ runUtilityProcess (Ytruder, prog_ytruder, argv);
+ insertOutput (outDATName, false, {});
+}
+
+// ========================================================================================================================================
+// Rectifier interface
+MAKE_ACTION (rectifier, "Rectifier", "rectifier", "Optimizes quads into rect primitives.", KEY (F8)) {
+ setlocale (LC_ALL, "C");
+
+ if (!checkProgPath (prog_rectifier, Rectifier))
+ return;
+
+ QDialog dlg;
+ QCheckBox* cb_condense = new QCheckBox ("Condense triangles to quads"),
+ *cb_subst = new QCheckBox ("Substitute rect primitives"),
+ *cb_condlineCheck = new QCheckBox ("Don't replace quads with adj. condlines"),
+ *cb_colorize = new QCheckBox ("Colorize resulting objects");
+ LabeledWidget* dsb_coplthres = new LabeledWidget ("Coplanarity threshold");
+
+ dsb_coplthres->w ()->setMinimum (0.0f);
+ dsb_coplthres->w ()->setMaximum (360.0f);
+ dsb_coplthres->w ()->setDecimals (3);
+ dsb_coplthres->w ()->setValue (0.95f);
+ cb_condense->setChecked (true);
+ cb_subst->setChecked (true);
+
+ QVBoxLayout* layout = new QVBoxLayout (&dlg);
+ layout->addWidget (cb_condense);
+ layout->addWidget (cb_subst);
+ layout->addWidget (cb_condlineCheck);
+ layout->addWidget (cb_colorize);
+ layout->addWidget (dsb_coplthres);
+ layout->addWidget (makeButtonBox (dlg));
+
+ if (!dlg.exec ())
+ return;
+
+ const bool condense = cb_condense->isChecked (),
+ subst = cb_subst->isChecked (),
+ condlineCheck = cb_condlineCheck->isChecked (),
+ colorize = cb_colorize->isChecked ();
+ const double coplthres = dsb_coplthres->w ()->value ();
+
+ QTemporaryFile indat, outdat;
+ str inDATName, outDATName;
+
+ // Make temp files for the input and output files
+ if (!mkTempFile (indat, inDATName) || !mkTempFile (outdat, outDATName))
+ return;
+
+ // Compose arguments
+ str argv = fmt ("%s %s %s %s -t %f %s %s",
+ (condense == false) ? "-q" : "",
+ (subst == false) ? "-r" : "",
+ (condlineCheck) ? "-a" : "",
+ (colorize) ? "-c" : "",
+ coplthres, inDATName.chars (), outDATName.chars ());
+
+ writeSelection (inDATName);
+ runUtilityProcess (Rectifier, prog_rectifier, argv);
+ insertOutput (outDATName, true, {});
+}
+
+// =======================================================================================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =======================================================================================================================================
+// Intersector interface
+MAKE_ACTION (intersector, "Intersector", "intersector", "Perform clipping between two input groups.", KEY (F5)) {
+ setlocale (LC_ALL, "C");
+
+ if (!checkProgPath (prog_intersector, Intersector))
+ return;
+
+ QDialog dlg;
+
+ LabeledWidget* cmb_incol = new LabeledWidget ("Input", new QComboBox),
+ *cmb_cutcol = new LabeledWidget ("Cutter", new QComboBox);
+ QCheckBox* cb_colorize = new QCheckBox ("Colorize output"),
+ *cb_nocondense = new QCheckBox ("No condensing"),
+ *cb_repeatInverse = new QCheckBox ("Repeat inverse"),
+ *cb_edges = new QCheckBox ("Add edges");
+ LabeledWidget* dsb_prescale = new LabeledWidget ("Prescaling factor");
+
+ cb_repeatInverse->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.");
+ cb_edges->setWhatsThis ("Makes " APPNAME " try run Isecalc to create edgelines for the intersection.");
+
+ makeColorSelector (cmb_incol->w ());
+ makeColorSelector (cmb_cutcol->w ());
+ dsb_prescale->w ()->setMinimum (0.0f);
+ dsb_prescale->w ()->setMaximum (10000.0f);
+ dsb_prescale->w ()->setSingleStep (0.01f);
+ dsb_prescale->w ()->setValue (1.0f);
+
+ QVBoxLayout* layout = new QVBoxLayout (&dlg);
+ layout->addWidget (cmb_incol);
+ layout->addWidget (cmb_cutcol);
+
+ QHBoxLayout* cblayout = new QHBoxLayout;
+ cblayout->addWidget (cb_colorize);
+ cblayout->addWidget (cb_nocondense);
+
+ QHBoxLayout* cb2layout = new QHBoxLayout;
+ cb2layout->addWidget (cb_repeatInverse);
+ cb2layout->addWidget (cb_edges);
+
+ layout->addLayout (cblayout);
+ layout->addLayout (cb2layout);
+ layout->addWidget (dsb_prescale);
+ layout->addWidget (makeButtonBox (dlg));
+
+exec:
+ if (!dlg.exec ())
+ return;
+
+ const short inCol = cmb_incol->w ()->itemData (cmb_incol->w ()->currentIndex ()).toInt (),
+ cutCol = cmb_cutcol->w ()->itemData (cmb_cutcol->w ()->currentIndex ()).toInt ();
+ const bool repeatInverse = cb_repeatInverse->isChecked ();
+
+ if (inCol == cutCol) {
+ critical ("Cannot use the same color group for both input and cutter!");
+ goto exec;
+ }
+
+ // 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;
+ str inDATName, cutDATName, outDATName, outDAT2Name, edgesDATName;
+
+ if (!mkTempFile (indat, inDATName) || !mkTempFile (cutdat, cutDATName) ||
+ !mkTempFile (outdat, outDATName) || !mkTempFile (outdat2, outDAT2Name) ||
+ !mkTempFile (edgesdat, edgesDATName))
+ {
+ return;
+ }
+
+ str parms = fmt ("%s %s -s %f",
+ (cb_colorize->isChecked ()) ? "-c" : "",
+ (cb_nocondense->isChecked ()) ? "-t" : "",
+ dsb_prescale->w ()->value ());
+
+ str argv_normal = fmt ("%s %s %s %s", parms.chars (), inDATName.chars (), cutDATName.chars (), outDATName.chars ());
+ str argv_inverse = fmt ("%s %s %s %s", parms.chars (), cutDATName.chars (), inDATName.chars (), outDAT2Name.chars ());
+
+ writeColorGroup (inCol, inDATName);
+ writeColorGroup (cutCol, cutDATName);
+ runUtilityProcess (Intersector, prog_intersector, argv_normal);
+ insertOutput (outDATName, false, {inCol});
+
+ if (repeatInverse) {
+ runUtilityProcess (Intersector, prog_intersector, argv_inverse);
+ insertOutput (outDAT2Name, false, {cutCol});
+ }
+
+ if (cb_edges->isChecked ()) {
+ runUtilityProcess (Isecalc, prog_isecalc, fmt ("%s %s %s", inDATName.chars (), cutDATName.chars (), edgesDATName.chars ()));
+ insertOutput (edgesDATName, false, {});
+ }
+}
\ No newline at end of file