src/extprogs.cpp

Sat, 06 Jul 2013 02:52:54 +0300

author
Santeri Piippo <crimsondusk64@gmail.com>
date
Sat, 06 Jul 2013 02:52:54 +0300
changeset 343
75487752f3e6
parent 312
97620579d86c
child 345
1376ad3262ba
permissions
-rw-r--r--

Completed Edger 2 interface

/*
 *  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_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<LDSubfile*> (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;
	
	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<QDoubleSpinBox>* dsb_depth = new LabeledWidget<QDoubleSpinBox> ("Plane depth"),
		*dsb_condAngle = new LabeledWidget<QDoubleSpinBox> ("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 = 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, {} );
}

mercurial