Wed, 08 Jan 2014 13:57:10 +0200
- if loading another file to replace an explicitly loaded file, this file won't get closed automatically and thus needs to be manually closed. We also need to check that it's safe to close before doing this. Also fixed a rather argh problem with ::save not using the proper path...
/* * 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 <http://www.gnu.org/licenses/>. */ #include <QProcess> #include <QTemporaryFile> #include <QDialog> #include <QDialogButtonBox> #include <QSpinBox> #include <QCheckBox> #include <QComboBox> #include <QGridLayout> #include "main.h" #include "config.h" #include "misc.h" #include "gui.h" #include "document.h" #include "widgets.h" #include "history.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 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 bool mkTempFile (QTemporaryFile& tmp, QString& fname) { if (!tmp.open()) return false; fname = tmp.fileName(); tmp.close(); return true; } // ============================================================================= // ----------------------------------------------------------------------------- static void writeObjects (const QList<LDObject*>& objects, File& f) { for (LDObject* obj : objects) { if (obj->getType() == LDObject::ESubfile) { LDSubfile* ref = static_cast<LDSubfile*> (obj); QList<LDObject*> objs = ref->inlineContents (LDSubfile::DeepInline); writeObjects (objs, f); for (LDObject* obj : objs) obj->deleteSelf(); } else f.write (obj->raw() + "\r\n"); } } // ============================================================================= // ----------------------------------------------------------------------------- static void writeObjects (const QList<LDObject*>& objects, QString fname) { // Write the input file File f (fname, File::Write); if (!f) { critical (fmt ("Couldn't open temporary file %1 for writing.\n", fname)); 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) { QList<LDObject*> 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, output; QString inputname, outputname; 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 (" ")); // Temporary files for stdin and stdout if (!mkTempFile (input, inputname) || !mkTempFile (output, outputname)) return false; QProcess proc; // Init stdin File stdinfp (inputname, File::Write); // Begin! proc.setStandardInputFile (inputname); 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 stdinfp.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<int> colorsToReplace) { #ifdef DEBUG QFile::copy (fname, "./debug_lastOutput"); #endif // RELEASE // Read the output file File f (fname, File::Read); if (!f) { critical (fmt ("Couldn't open temporary file %1 for reading.\n", fname)); return; } QList<LDObject*> objs = loadFileContents (&f, null); // If we replace the objects, delete the selection now. if (replace) g_win->deleteSelection(); for (const 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, {}); }