Sat, 13 Jul 2013 19:02:48 +0300
Appended 'Object' to the names of all LDObject derivatives so even though the LD prefix isn't reserved for just LDObjects anymore, these classes remain distinct
/* * 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 <http://www.gnu.org/licenses/>. */ #include <QProcess> #include <QTemporaryFile> #include <QDialog> #include <QDialogButtonBox> #include <QSpinBox> #include <QCheckBox> #include <QComboBox> #include <QGridLayout> #include "common.h" #include "config.h" #include "misc.h" #include "extprogs.h" #include "gui.h" #include "file.h" #include "widgets.h" #include "history.h" #include "labeledwidget.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" // ============================================================================= cfg (str, prog_isecalc, ""); cfg (str, prog_intersector, ""); cfg (str, prog_coverer, ""); cfg (str, prog_ytruder, ""); cfg (str, prog_rectifier, ""); cfg (str, 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); boolconfig* 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 (str path, const extprog prog) { if (path.length () > 0) return true; const char* name = g_extProgNames[prog]; critical (fmt ("Couldn't run %1 as no path has " "been defined for it. Use the configuration dialog's External Programs " "tab to define a path for %1.", name)); return false; } // ============================================================================= static str processErrorString (QProcess& proc) { switch (proc.error()) { case QProcess::FailedToStart: return "Failed to start (check your permissions)"; 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, str& fname) { if (!tmp.open ()) return false; fname = tmp.fileName (); tmp.close (); return true; } // ============================================================================= void writeObjects (vector<LDObject*>& objects, File& f) { for (LDObject* obj : objects) { if (obj->getType () == LDObject::Subfile) { vector<LDObject*> objs = static_cast<LDSubfileObject*> (obj)->inlineContents (true, false); writeObjects (objs, f); for (LDObject* obj : objs) delete obj; } else f.write (obj->raw () + "\r\n"); } } void writeObjects (vector<LDObject*>& objects, str 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 (); } // ============================================================================= void writeSelection (str fname) { writeObjects (g_win->sel (), fname); } // ============================================================================= void writeColorGroup (const short colnum, str fname) { vector<LDObject*> objects; for (LDObject*& obj : *g_curfile) { if (obj->isColored () == false || obj->color () != colnum) continue; objects << obj; } writeObjects (objects, fname); } void waitForProcess( QProcess* proc ) { proc->waitForFinished(); #if 0 int msecs = 30000; int msectic = 10; for (int i = 0; i < msecs / msectic; ++i) { if (proc->waitForFinished (msectic)) return; } #endif // 0 } // ============================================================================= bool runUtilityProcess (extprog prog, str path, str argvstr) { QTemporaryFile input, output; str inputname, outputname; QStringList argv = argvstr.split (" ", QString::SkipEmptyParts); #ifndef _WIN32 if (*g_extProgWine[prog]) { argv.insert (0, path); path = "wine"; } #endif // _WIN32 print ("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); // Write an enter, the utility tools all expect one stdinfp.write ("\n"); // Wait while it runs waitForProcess( &proc ); #ifndef RELEASE print ("%1", str (proc.readAllStandardOutput ())); #endif // RELEASE str err = ""; if ( proc.exitStatus() != QProcess::NormalExit ) err = processErrorString (proc); // Check the return code if (proc.exitCode() != 0) err = fmt ("Program exited abnormally (return code %1).", proc.exitCode()); if (err.length() > 0) { critical (fmt ("%1 failed: %2\n", g_extProgNames[prog], err)); return false; } return true; } // ================================================================================================ static void insertOutput (str fname, bool replace, vector<short> colorsToReplace) { #ifndef RELEASE 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; } vector<LDObject*> objs = loadFileContents (&f, null); // If we replace the objects, delete the selection now. if (replace) g_win->deleteSelection (); for (const short colnum : colorsToReplace) g_win->deleteByColor (colnum); // Insert the new objects g_win->sel ().clear (); for (LDObject* obj : objs) { if (!obj->isScemantic ()) { delete obj; continue; } g_curfile->addObject (obj); g_win->sel () << obj; } g_win->fullRefresh (); } // ============================================================================= // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // ============================================================================= // Interface for Ytruder void runYtruder () { setlocale (LC_ALL, "C"); if (!checkProgPath (prog_ytruder, 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; 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 = 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 void runRectifier () { setlocale (LC_ALL, "C"); if (!checkProgPath (prog_rectifier, Rectifier)) return; QDialog* dlg = new QDialog; Ui::RectifierUI ui; ui.setupUi (dlg); if (!dlg->exec ()) return; 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 = 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, {}); } LabeledWidget<QComboBox>* buildColorSelector (const char* label) { LabeledWidget<QComboBox>* widget = new LabeledWidget<QComboBox> (label, new QComboBox); makeColorSelector (widget->w ()); return widget; } // ============================================================================= // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // ============================================================================= // Intersector interface void runIntersector () { setlocale (LC_ALL, "C"); if (!checkProgPath (prog_intersector, Intersector)) return; QDialog* dlg = new QDialog; Ui::IntersectorUI ui; ui.setupUi( dlg ); makeColorSelector( ui.cmb_incol ); makeColorSelector( 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." ); short inCol, cutCol; const bool repeatInverse = ui.cb_repeat->isChecked (); for( ;; ) { 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; 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 = join ({ (ui.cb_colorize->isChecked ()) ? "-c" : "", (ui.cb_nocondense->isChecked ()) ? "-t" : "", "-s", ui.dsb_prescale->value () }); str argv_normal = join ({ parms, inDATName, cutDATName, outDATName }); str 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 () && runUtilityProcess (Isecalc, prog_isecalc, join ({inDATName, cutDATName, edgesDATName}))) { insertOutput (edgesDATName, false, {}); } } // ============================================================================= // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // ============================================================================= void runCoverer () { setlocale (LC_ALL, "C"); if (!checkProgPath (prog_coverer, Coverer)) return; QDialog* dlg = new QDialog; Ui::CovererUI ui; ui.setupUi( dlg ); makeColorSelector( ui.cmb_col1 ); makeColorSelector( ui.cmb_col2 ); short in1Col, in2Col; for (;;) { 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; str in1DATName, in2DATName, outDATName; if (!mkTempFile (in1dat, in1DATName) || !mkTempFile (in2dat, in2DATName) || !mkTempFile (outdat, outDATName)) return; str 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, {}); } // ============================================================================= // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // ============================================================================= void runIsecalc () { setlocale (LC_ALL, "C"); if (!checkProgPath (prog_isecalc, Isecalc)) return; Ui::IsecalcUI ui; QDialog* dlg = new QDialog; ui.setupUi( dlg ); makeColorSelector( ui.cmb_col1 ); makeColorSelector( ui.cmb_col2 ); short in1Col, in2Col; // Run the dialog and validate input for( ;; ) { 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; str in1DATName, in2DATName, outDATName; if (!mkTempFile (in1dat, in1DATName) || !mkTempFile (in2dat, in2DATName) || !mkTempFile (outdat, outDATName)) return; str argv = join ({ in1DATName, in2DATName, outDATName }); writeColorGroup (in1Col, in1DATName); writeColorGroup (in2Col, in2DATName); runUtilityProcess (Isecalc, prog_isecalc, argv); insertOutput (outDATName, false, {}); } // ============================================================================= // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // ============================================================================= MAKE_ACTION( edger2, "Edger 2", "edger2", "Compute edgelines", 0 ) { setlocale (LC_ALL, "C"); if( !checkProgPath( prog_edger2, Edger2 )) return; QDialog* dlg = new QDialog; Ui::Edger2Dialog ui; ui.setupUi( dlg ); if( !dlg->exec() ) return; QTemporaryFile in, out; str inName, outName; if( !mkTempFile( in, inName ) || !mkTempFile( out, outName )) return; int unmatched = ui.unmatched->currentIndex(); str 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, {} ); }