diff options
-rw-r--r-- | RELEASE_NOTES | 3 | ||||
-rw-r--r-- | doc/TODO.txt | 8 | ||||
-rw-r--r-- | openscad.pro | 6 | ||||
-rw-r--r-- | src/MainWindow.h | 17 | ||||
-rw-r--r-- | src/MainWindow.ui | 6 | ||||
-rw-r--r-- | src/ProgressWidget.cc | 12 | ||||
-rw-r--r-- | src/ProgressWidget.h | 5 | ||||
-rw-r--r-- | src/cgalworker.cc | 40 | ||||
-rw-r--r-- | src/cgalworker.h | 28 | ||||
-rw-r--r-- | src/mainwin.cc | 181 |
10 files changed, 179 insertions, 127 deletions
diff --git a/RELEASE_NOTES b/RELEASE_NOTES index 295f6f4..9722a01 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -1,6 +1,9 @@ OpenSCAD 2012.XX ================ +Features: +o Snappier GUI while performing CGAL computations (computations running in separate thread) + Deprecations: o The old include syntax "<filename.scad>" without the include keyword is no longer supported and will cause a syntax error. diff --git a/doc/TODO.txt b/doc/TODO.txt index 8f6d257..97f1a95 100644 --- a/doc/TODO.txt +++ b/doc/TODO.txt @@ -100,13 +100,9 @@ o Error reporting/debugging -> could aid debugging a lot - Optionally output console log to a file o Computation - - Run CGAL rendering in a background thread - - Enable viewing/editing while rendering - - Progress: Call progresswidget more often to avoid app hanging for multiple - seconds (i.e. make cancel button more responsive) + - Multi-threaded computation (mostly important for CGAL) + - Progress: Call progresswidget more often to improve feedback o Misc - - Reload and compile: Ask for confirmation if file is locally edited - (make this configurable in preferences?) - Save: Ask for confirmation if file has been externally changed - Rename OpenCSG and CGAL to smth. not specific to the underlying libraries (e.g Preview, Render) diff --git a/openscad.pro b/openscad.pro index b26122a..f104cf1 100644 --- a/openscad.pro +++ b/openscad.pro @@ -264,7 +264,8 @@ HEADERS += src/cgal.h \ src/CGALCache.h \ src/PolySetCGALEvaluator.h \ src/CGALRenderer.h \ - src/CGAL_Nef_polyhedron.h + src/CGAL_Nef_polyhedron.h \ + src/cgalworker.h SOURCES += src/cgalutils.cc \ src/CGALEvaluator.cc \ @@ -273,7 +274,8 @@ SOURCES += src/cgalutils.cc \ src/CGALRenderer.cc \ src/CGAL_Nef_polyhedron.cc \ src/CGAL_Nef_polyhedron_DxfData.cc \ - src/cgaladv_minkowski2.cc + src/cgaladv_minkowski2.cc \ + src/cgalworker.cc } macx { diff --git a/src/MainWindow.h b/src/MainWindow.h index 0f2a922..577209f 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -9,6 +9,7 @@ #include "Tree.h" #include "memory.h" #include <vector> +#include <QMutex> class MainWindow : public QMainWindow, public Ui::MainWindow { @@ -67,9 +68,7 @@ private slots: void updateTVal(); void setFileName(const QString &filename); void setFont(const QString &family, uint size); -#ifdef USE_PROGRESSWIDGET void showProgress(); -#endif private: void openFile(const QString &filename); @@ -80,9 +79,7 @@ private: bool maybeSave(); bool checkModified(); QString dumpCSGTree(AbstractNode *root); - static void consoleOutput(const std::string &msg, void *userdata) { - static_cast<MainWindow*>(userdata)->console->append(QString::fromStdString(msg)); - } + static void consoleOutput(const std::string &msg, void *userdata); void loadViewSettings(); void loadDesignSettings(); @@ -110,6 +107,7 @@ private slots: void actionCompile(); #ifdef ENABLE_CGAL void actionRenderCGAL(); + void actionRenderCGALDone(class CGAL_Nef_polyhedron *); #endif void actionDisplayAST(); void actionDisplayCSGTree(); @@ -163,6 +161,13 @@ public slots: void actionReloadCompile(); void checkAutoReload(); void autoReloadSet(bool); + +private: + static void report_func(const class AbstractNode*, void *vp, int mark); + + class ProgressWidget *progresswidget; + class CGALWorker *cgalworker; + QMutex consolemutex; }; class GuiLocker @@ -175,6 +180,8 @@ public: gui_locked--; } static bool isLocked() { return gui_locked > 0; } + static void lock() { gui_locked++; } + static void unlock() { gui_locked--; } private: static unsigned int gui_locked; diff --git a/src/MainWindow.ui b/src/MainWindow.ui index d1bdb00..f89e0d4 100644 --- a/src/MainWindow.ui +++ b/src/MainWindow.ui @@ -39,7 +39,11 @@ <enum>Qt::Vertical</enum> </property> <widget class="GLView" name="glview" native="true"/> - <widget class="QTextEdit" name="console"/> + <widget class="QTextEdit" name="console"> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> </widget> </item> <item> diff --git a/src/ProgressWidget.cc b/src/ProgressWidget.cc index 112e239..ce66405 100644 --- a/src/ProgressWidget.cc +++ b/src/ProgressWidget.cc @@ -5,7 +5,11 @@ ProgressWidget::ProgressWidget(QWidget *parent) :QWidget(parent) { setupUi(this); + setRange(0, 100); + setValue(0); this->wascanceled = false; + this->starttime.start(); + connect(this->stopButton, SIGNAL(clicked()), this, SLOT(cancel())); QTimer::singleShot(1000, this, SIGNAL(requestShow())); } @@ -15,6 +19,14 @@ bool ProgressWidget::wasCanceled() const return this->wascanceled; } +/*! + Returns milliseconds since this widget was created +*/ +int ProgressWidget::elapsedTime() const +{ + return this->starttime.elapsed(); +} + void ProgressWidget::cancel() { this->wascanceled = true; diff --git a/src/ProgressWidget.h b/src/ProgressWidget.h index 83e4d40..f610279 100644 --- a/src/ProgressWidget.h +++ b/src/ProgressWidget.h @@ -2,15 +2,17 @@ #define PROGRESSWIDGET_H_ #include "ui_ProgressWidget.h" +#include <QTime> class ProgressWidget : public QWidget, public Ui::ProgressWidget { Q_OBJECT; Q_PROPERTY(bool wasCanceled READ wasCanceled); - + public: ProgressWidget(QWidget *parent = NULL); bool wasCanceled() const; + int elapsedTime() const; public slots: void setRange(int minimum, int maximum); @@ -23,6 +25,7 @@ signals: private: bool wascanceled; + QTime starttime; }; #endif diff --git a/src/cgalworker.cc b/src/cgalworker.cc new file mode 100644 index 0000000..30bb1fe --- /dev/null +++ b/src/cgalworker.cc @@ -0,0 +1,40 @@ +#include "cgalworker.h" +#include <QThread> + +#include "Tree.h" +#include "CGALEvaluator.h" +#include "progress.h" +#include "printutils.h" + +CGALWorker::CGALWorker() +{ + this->thread = new QThread(); + connect(this->thread, SIGNAL(started()), this, SLOT(work())); + moveToThread(this->thread); +} + +CGALWorker::~CGALWorker() +{ + delete this->thread; +} + +void CGALWorker::start(const Tree &tree) +{ + this->tree = &tree; + this->thread->start(); +} + +void CGALWorker::work() +{ + CGAL_Nef_polyhedron *root_N = NULL; + try { + CGALEvaluator evaluator(*this->tree); + root_N = new CGAL_Nef_polyhedron(evaluator.evaluateCGALMesh(*this->tree->root())); + } + catch (ProgressCancelException e) { + PRINT("Rendering cancelled."); + } + + emit done(root_N); + thread->quit(); +} diff --git a/src/cgalworker.h b/src/cgalworker.h new file mode 100644 index 0000000..cf60c24 --- /dev/null +++ b/src/cgalworker.h @@ -0,0 +1,28 @@ +#ifndef CGALWORKER_H_ +#define CGALWORKER_H_ + +#include <QObject> + +class CGALWorker : public QObject +{ + Q_OBJECT; +public: + CGALWorker(); + virtual ~CGALWorker(); + +public slots: + void start(const class Tree &tree); + +protected slots: + void work(); + +signals: + void done(class CGAL_Nef_polyhedron *); + +protected: + + class QThread *thread; + const class Tree *tree; +}; + +#endif diff --git a/src/mainwin.cc b/src/mainwin.cc index 2c52eeb..0c5537f 100644 --- a/src/mainwin.cc +++ b/src/mainwin.cc @@ -41,9 +41,7 @@ #include "CSGTermEvaluator.h" #include "OpenCSGRenderer.h" #endif -#ifdef USE_PROGRESSWIDGET #include "ProgressWidget.h" -#endif #include "ThrownTogetherRenderer.h" #include "csgtermnormalizer.h" @@ -53,8 +51,6 @@ #include <QSplitter> #include <QFileDialog> #include <QApplication> -#include <QProgressDialog> -#include <QProgressBar> #include <QHBoxLayout> #include <QVBoxLayout> #include <QLabel> @@ -68,6 +64,8 @@ #include <QMessageBox> #include <QDesktopServices> #include <QSettings> +#include <QProgressDialog> +#include <QMutexLocker> #ifdef _QCODE_EDIT_ #include "qdocument.h" #include "qformatscheme.h" @@ -90,6 +88,7 @@ using namespace boost::lambda; #include "CGALRenderer.h" #include "CGAL_Nef_polyhedron.h" #include "cgal.h" +#include "cgalworker.h" #endif // ENABLE_CGAL @@ -141,9 +140,14 @@ settings_valueList(const QString &key, const QList<int> &defaultList = QList<int } MainWindow::MainWindow(const QString &filename) + : progresswidget(NULL) { setupUi(this); + this->cgalworker = new CGALWorker(); + connect(this->cgalworker, SIGNAL(done(CGAL_Nef_polyhedron *)), + this, SLOT(actionRenderCGALDone(CGAL_Nef_polyhedron *))); + register_builtin(root_ctx); this->openglbox = NULL; @@ -316,7 +320,6 @@ MainWindow::MainWindow(const QString &filename) connect(this->helpActionOpenGLInfo, SIGNAL(triggered()), this, SLOT(helpOpenGL())); - console->setReadOnly(true); setCurrentOutput(); PRINT(helptitle); @@ -421,32 +424,23 @@ MainWindow::~MainWindow() #endif } -#ifdef USE_PROGRESSWIDGET void MainWindow::showProgress() { this->statusBar()->addPermanentWidget(qobject_cast<ProgressWidget*>(sender())); } -#endif -static void report_func(const class AbstractNode*, void *vp, int mark) +void MainWindow::report_func(const class AbstractNode*, void *vp, int mark) { -#ifdef USE_PROGRESSWIDGET - ProgressWidget *pw = static_cast<ProgressWidget*>(vp); + MainWindow *thisp = static_cast<MainWindow*>(vp); int v = (int)((mark*100.0) / progress_report_count); int percent = v < 100 ? v : 99; - if (percent > pw->value()) pw->setValue(percent); - QApplication::processEvents(); - if (pw->wasCanceled()) throw ProgressCancelException(); -#else - QProgressDialog *pd = static_cast<QProgressDialog*>(vp); - int v = (int)((mark*100.0) / progress_report_count); - pd->setValue(v < 100 ? v : 99); - QString label; - label.sprintf("Rendering Polygon Mesh (%d/%d)", mark, progress_report_count); - pd->setLabelText(label); - QApplication::processEvents(); - if (pd->wasCanceled()) throw ProgressCancelException(); -#endif + + if (percent > thisp->progresswidget->value()) { + QMetaObject::invokeMethod(thisp->progresswidget, "setValue", Qt::QueuedConnection, + Q_ARG(int, percent)); + } + + if (thisp->progresswidget->wasCanceled()) throw ProgressCancelException(); } /*! @@ -740,21 +734,11 @@ void MainWindow::compileCSG(bool procevents) QTime t; t.start(); -#ifdef USE_PROGRESSWIDGET - ProgressWidget *pd = new ProgressWidget(this); - pd->setRange(0, 100); - pd->setValue(0); - connect(pd, SIGNAL(requestShow()), this, SLOT(showProgress())); -#else - QProgressDialog *pd = new QProgressDialog("Rendering CSG products...", "Cancel", 0, 100); - pd->setRange(0, 100); - pd->setValue(0); - pd->setAutoClose(false); - pd->show(); -#endif + this->progresswidget = new ProgressWidget(this); + connect(this->progresswidget, SIGNAL(requestShow()), this, SLOT(showProgress())); QApplication::processEvents(); - progress_report_prep(root_node, report_func, pd); + progress_report_prep(root_node, report_func, this); try { CGALEvaluator cgalevaluator(this->tree); PolySetCGALEvaluator psevaluator(cgalevaluator); @@ -772,10 +756,9 @@ void MainWindow::compileCSG(bool procevents) PRINT("CSG generation cancelled."); } progress_report_fin(); -#ifdef USE_PROGRESSWIDGET - this->statusBar()->removeWidget(pd); -#endif - delete pd; + this->statusBar()->removeWidget(this->progresswidget); + delete this->progresswidget; + this->progresswidget = NULL; if (root_raw_term) { PRINT("Compiling design (CSG Products normalization)..."); @@ -1157,88 +1140,53 @@ void MainWindow::actionRenderCGAL() PRINT("Rendering Polygon Mesh using CGAL..."); QApplication::processEvents(); - QTime t; - t.start(); + this->progresswidget = new ProgressWidget(this); + connect(this->progresswidget, SIGNAL(requestShow()), this, SLOT(showProgress())); + QApplication::processEvents(); -#ifdef USE_PROGRESSWIDGET - ProgressWidget *pd = new ProgressWidget(this); - pd->setRange(0, 100); - pd->setValue(0); - connect(pd, SIGNAL(requestShow()), this, SLOT(showProgress())); -#else - QProgressDialog *pd = new QProgressDialog("Rendering Polygon Mesh using CGAL...", "Cancel", 0, 100); - pd->setRange(0, 100); - pd->setValue(0); - pd->setAutoClose(false); - pd->show(); -#endif + progress_report_prep(this->root_node, report_func, this); - QApplication::processEvents(); + GuiLocker::lock(); // Will be unlocked in actionRenderCGALDone() + this->cgalworker->start(this->tree); +} - progress_report_prep(this->root_node, report_func, pd); - try { - CGALEvaluator evaluator(this->tree); - this->root_N = new CGAL_Nef_polyhedron(evaluator.evaluateCGALMesh(*this->root_node)); - PolySetCache::instance()->print(); - CGALCache::instance()->print(); - } - catch (ProgressCancelException e) { - PRINT("Rendering cancelled."); - } +void MainWindow::actionRenderCGALDone(CGAL_Nef_polyhedron *root_N) +{ progress_report_fin(); - if (this->root_N) - { - // FIXME: Reenable cache cost info -// PRINTF("Number of vertices currently in CGAL cache: %d", AbstractNode::cgal_nef_cache.totalCost()); -// PRINTF("Number of objects currently in CGAL cache: %d", AbstractNode::cgal_nef_cache.size()); - QApplication::processEvents(); + if (root_N) { + PolySetCache::instance()->print(); + CGALCache::instance()->print(); - if (this->root_N->dim == 2) { + if (root_N->dim == 2) { PRINTF(" Top level object is a 2D object:"); - QApplication::processEvents(); - PRINTF(" Empty: %6s", this->root_N->p2->is_empty() ? "yes" : "no"); - QApplication::processEvents(); - PRINTF(" Plane: %6s", this->root_N->p2->is_plane() ? "yes" : "no"); - QApplication::processEvents(); - PRINTF(" Vertices: %6d", (int)this->root_N->p2->explorer().number_of_vertices()); - QApplication::processEvents(); - PRINTF(" Halfedges: %6d", (int)this->root_N->p2->explorer().number_of_halfedges()); - QApplication::processEvents(); - PRINTF(" Edges: %6d", (int)this->root_N->p2->explorer().number_of_edges()); - QApplication::processEvents(); - PRINTF(" Faces: %6d", (int)this->root_N->p2->explorer().number_of_faces()); - QApplication::processEvents(); - PRINTF(" FaceCycles: %6d", (int)this->root_N->p2->explorer().number_of_face_cycles()); - QApplication::processEvents(); - PRINTF(" ConnComp: %6d", (int)this->root_N->p2->explorer().number_of_connected_components()); - QApplication::processEvents(); + PRINTF(" Empty: %6s", root_N->p2->is_empty() ? "yes" : "no"); + PRINTF(" Plane: %6s", root_N->p2->is_plane() ? "yes" : "no"); + PRINTF(" Vertices: %6d", (int)root_N->p2->explorer().number_of_vertices()); + PRINTF(" Halfedges: %6d", (int)root_N->p2->explorer().number_of_halfedges()); + PRINTF(" Edges: %6d", (int)root_N->p2->explorer().number_of_edges()); + PRINTF(" Faces: %6d", (int)root_N->p2->explorer().number_of_faces()); + PRINTF(" FaceCycles: %6d", (int)root_N->p2->explorer().number_of_face_cycles()); + PRINTF(" ConnComp: %6d", (int)root_N->p2->explorer().number_of_connected_components()); } - if (this->root_N->dim == 3) { + if (root_N->dim == 3) { PRINTF(" Top level object is a 3D object:"); - PRINTF(" Simple: %6s", this->root_N->p3->is_simple() ? "yes" : "no"); - QApplication::processEvents(); - PRINTF(" Valid: %6s", this->root_N->p3->is_valid() ? "yes" : "no"); - QApplication::processEvents(); - PRINTF(" Vertices: %6d", (int)this->root_N->p3->number_of_vertices()); - QApplication::processEvents(); - PRINTF(" Halfedges: %6d", (int)this->root_N->p3->number_of_halfedges()); - QApplication::processEvents(); - PRINTF(" Edges: %6d", (int)this->root_N->p3->number_of_edges()); - QApplication::processEvents(); - PRINTF(" Halffacets: %6d", (int)this->root_N->p3->number_of_halffacets()); - QApplication::processEvents(); - PRINTF(" Facets: %6d", (int)this->root_N->p3->number_of_facets()); - QApplication::processEvents(); - PRINTF(" Volumes: %6d", (int)this->root_N->p3->number_of_volumes()); - QApplication::processEvents(); + PRINTF(" Simple: %6s", root_N->p3->is_simple() ? "yes" : "no"); + PRINTF(" Valid: %6s", root_N->p3->is_valid() ? "yes" : "no"); + PRINTF(" Vertices: %6d", (int)root_N->p3->number_of_vertices()); + PRINTF(" Halfedges: %6d", (int)root_N->p3->number_of_halfedges()); + PRINTF(" Edges: %6d", (int)root_N->p3->number_of_edges()); + PRINTF(" Halffacets: %6d", (int)root_N->p3->number_of_halffacets()); + PRINTF(" Facets: %6d", (int)root_N->p3->number_of_facets()); + PRINTF(" Volumes: %6d", (int)root_N->p3->number_of_volumes()); } - int s = t.elapsed() / 1000; + int s = this->progresswidget->elapsedTime() / 1000; PRINTF("Total rendering time: %d hours, %d minutes, %d seconds", s / (60*60), (s / 60) % 60, s % 60); + this->root_N = root_N; if (!this->root_N->empty()) { this->cgalRenderer = new CGALRenderer(*this->root_N); // Go to CGAL view mode @@ -1256,11 +1204,12 @@ void MainWindow::actionRenderCGAL() } } -#ifdef USE_PROGRESSWIDGET - this->statusBar()->removeWidget(pd); -#endif - delete pd; + this->statusBar()->removeWidget(this->progresswidget); + delete this->progresswidget; + this->progresswidget = NULL; clearCurrentOutput(); + + GuiLocker::unlock(); } #endif /* ENABLE_CGAL */ @@ -1813,6 +1762,15 @@ void MainWindow::quit() if (ev.isAccepted()) QApplication::instance()->quit(); } +void MainWindow::consoleOutput(const std::string &msg, void *userdata) +{ + // Invoke the append function in the main thread in case the output + // originates in a worker thread. + MainWindow *thisp = static_cast<MainWindow*>(userdata); + QMetaObject::invokeMethod(thisp->console, "append", Qt::QueuedConnection, + Q_ARG(QString, QString::fromStdString(msg))); +} + void MainWindow::setCurrentOutput() { set_output_handler(&MainWindow::consoleOutput, this); @@ -1822,4 +1780,3 @@ void MainWindow::clearCurrentOutput() { set_output_handler(NULL, NULL); } - |