diff options
Diffstat (limited to 'src/mainwin.cc')
-rw-r--r-- | src/mainwin.cc | 354 |
1 files changed, 209 insertions, 145 deletions
diff --git a/src/mainwin.cc b/src/mainwin.cc index 2fd75c9..087cb30 100644 --- a/src/mainwin.cc +++ b/src/mainwin.cc @@ -25,6 +25,7 @@ */ #include "PolySetCache.h" +#include "ModuleCache.h" #include "MainWindow.h" #include "openscad.h" // examplesdir #include "parsersettings.h" @@ -77,10 +78,10 @@ #include <fstream> #include <algorithm> +#include <boost/version.hpp> #include <boost/foreach.hpp> -#include <boost/lambda/lambda.hpp> -#include <boost/lambda/bind.hpp> -using namespace boost::lambda; +#include <boost/version.hpp> +#include <sys/stat.h> #ifdef ENABLE_CGAL @@ -94,6 +95,10 @@ using namespace boost::lambda; #endif // ENABLE_CGAL +#ifndef OPENCSG_VERSION_STRING +#define OPENCSG_VERSION_STRING "unknown, <1.3.2" +#endif + // Global application state unsigned int GuiLocker::gui_locked = 0; @@ -199,8 +204,8 @@ MainWindow::MainWindow(const QString &filename) autoReloadTimer->setSingleShot(false); connect(autoReloadTimer, SIGNAL(timeout()), this, SLOT(checkAutoReload())); - connect(e_tval, SIGNAL(textChanged(QString)), this, SLOT(actionCompile())); - connect(e_fps, SIGNAL(textChanged(QString)), this, SLOT(updatedFps())); + connect(this->e_tval, SIGNAL(textChanged(QString)), this, SLOT(actionCompile())); + connect(this->e_fps, SIGNAL(textChanged(QString)), this, SLOT(updatedFps())); animate_panel->hide(); @@ -323,7 +328,7 @@ MainWindow::MainWindow(const QString &filename) connect(this->helpActionAbout, SIGNAL(triggered()), this, SLOT(helpAbout())); connect(this->helpActionHomepage, SIGNAL(triggered()), this, SLOT(helpHomepage())); connect(this->helpActionManual, SIGNAL(triggered()), this, SLOT(helpManual())); - connect(this->helpActionOpenGLInfo, SIGNAL(triggered()), this, SLOT(helpOpenGL())); + connect(this->helpActionLibraryInfo, SIGNAL(triggered()), this, SLOT(helpLibrary())); setCurrentOutput(); @@ -488,7 +493,7 @@ MainWindow::openFile(const QString &new_filename) #endif setFileName(new_filename); - load(); + refreshDocument(); updateRecentFiles(); } @@ -544,11 +549,11 @@ void MainWindow::updateRecentFiles() void MainWindow::updatedFps() { bool fps_ok; - double fps = e_fps->text().toDouble(&fps_ok); + double fps = this->e_fps->text().toDouble(&fps_ok); animate_timer->stop(); if (fps_ok && fps > 0) { animate_timer->setSingleShot(false); - animate_timer->setInterval(int(1000 / e_fps->text().toDouble())); + animate_timer->setInterval(int(1000 / this->e_fps->text().toDouble())); animate_timer->start(); } } @@ -556,27 +561,28 @@ void MainWindow::updatedFps() void MainWindow::updateTVal() { bool fps_ok; - double fps = e_fps->text().toDouble(&fps_ok); + double fps = this->e_fps->text().toDouble(&fps_ok); if (fps_ok) { if (fps <= 0) { actionCompile(); } else { - double s = e_fsteps->text().toDouble(); - double t = e_tval->text().toDouble() + 1/s; + double s = this->e_fsteps->text().toDouble(); + double t = this->e_tval->text().toDouble() + 1/s; QString txt; txt.sprintf("%.5f", t >= 1.0 ? 0.0 : t); - e_tval->setText(txt); + this->e_tval->setText(txt); } } } -void MainWindow::load() +void MainWindow::refreshDocument() { setCurrentOutput(); if (!this->fileName.isEmpty()) { QFile file(this->fileName); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - PRINTB("Failed to open file: %s (%s)", this->fileName.toStdString() % file.errorString().toStdString()); + PRINTB("Failed to open file %s: %s", + this->fileName.toStdString() % file.errorString().toStdString()); } else { QString text = QTextStream(&file).readAll(); @@ -598,42 +604,30 @@ AbstractNode *MainWindow::find_root_tag(AbstractNode *n) /*! Parse and evaluate the design => this->root_node + + Returns true if something was compiled, false if nothing was changed + and the root_node was left untouched. */ -void MainWindow::compile(bool procevents) +bool MainWindow::compile(bool reload, bool procevents) { - PRINT("Parsing design (AST generation)..."); - if (procevents) - QApplication::processEvents(); + if (!compileTopLevelDocument(reload)) return false; // Invalidate renderers before we kill the CSG tree this->glview->setRenderer(NULL); - if (this->opencsgRenderer) { - delete this->opencsgRenderer; - this->opencsgRenderer = NULL; - } - if (this->thrownTogetherRenderer) { - delete this->thrownTogetherRenderer; - this->thrownTogetherRenderer = NULL; - } + delete this->opencsgRenderer; + this->opencsgRenderer = NULL; + delete this->thrownTogetherRenderer; + this->thrownTogetherRenderer = NULL; // Remove previous CSG tree - if (this->root_module) { - delete this->root_module; - this->root_module = NULL; - } - - if (this->absolute_root_node) { - delete this->absolute_root_node; - this->absolute_root_node = NULL; - } + delete this->absolute_root_node; + this->absolute_root_node = NULL; this->root_raw_term.reset(); this->root_norm_term.reset(); - if (this->root_chain) { - delete this->root_chain; - this->root_chain = NULL; - } + delete this->root_chain; + this->root_chain = NULL; this->highlight_terms.clear(); delete this->highlights_chain; @@ -646,96 +640,43 @@ void MainWindow::compile(bool procevents) this->root_node = NULL; this->tree.setRoot(NULL); - // Initialize special variables - this->root_ctx.set_variable("$t", Value(e_tval->text().toDouble())); - - Value vpt; - vpt.type = Value::VECTOR; - vpt.append(new Value(-this->glview->object_trans_x)); - vpt.append(new Value(-this->glview->object_trans_y)); - vpt.append(new Value(-this->glview->object_trans_z)); - this->root_ctx.set_variable("$vpt", vpt); - - Value vpr; - vpr.type = Value::VECTOR; - vpr.append(new Value(fmodf(360 - this->glview->object_rot_x + 90, 360))); - vpr.append(new Value(fmodf(360 - this->glview->object_rot_y, 360))); - vpr.append(new Value(fmodf(360 - this->glview->object_rot_z, 360))); - root_ctx.set_variable("$vpr", vpr); - - // Parse - this->last_compiled_doc = editor->toPlainText(); - this->root_module = parse((this->last_compiled_doc + "\n" + - QString::fromStdString(commandline_commands)).toAscii().data(), - this->fileName.isEmpty() ? - "" : - QFileInfo(this->fileName).absolutePath().toLocal8Bit(), - false); - - // Error highlighting - if (this->highlighter) { - delete this->highlighter; - this->highlighter = NULL; - } - if (parser_error_pos >= 0) { - this->highlighter = new Highlighter(editor->document()); - } - - if (!this->root_module) { - if (!animate_panel->isVisible()) { -#ifdef _QCODE_EDIT_ - QDocumentCursor cursor = editor->cursor(); - cursor.setPosition(parser_error_pos); -#else - QTextCursor cursor = editor->textCursor(); - cursor.setPosition(parser_error_pos); - editor->setTextCursor(cursor); -#endif + if (this->root_module) { + // Evaluate CSG tree + PRINT("Compiling design (CSG Tree generation)..."); + if (procevents) QApplication::processEvents(); + + AbstractNode::resetIndexCounter(); + this->root_inst = ModuleInstantiation(); + this->absolute_root_node = this->root_module->evaluate(&this->root_ctx, &this->root_inst); + + if (this->absolute_root_node) { + // Do we have an explicit root node (! modifier)? + if (!(this->root_node = find_root_tag(this->absolute_root_node))) { + this->root_node = this->absolute_root_node; + } + // FIXME: Consider giving away ownership of root_node to the Tree, or use reference counted pointers + this->tree.setRoot(this->root_node); + // Dump the tree (to initialize caches). + // FIXME: We shouldn't really need to do this explicitly.. + this->tree.getString(*this->root_node); } - goto fail; } - // Evaluate CSG tree - PRINT("Compiling design (CSG Tree generation)..."); - if (procevents) - QApplication::processEvents(); - - AbstractNode::resetIndexCounter(); - this->root_inst = ModuleInstantiation(); - this->absolute_root_node = this->root_module->evaluate(&this->root_ctx, &this->root_inst); - - if (!this->absolute_root_node) - goto fail; - - // Do we have an explicit root node (! modifier)? - if (!(this->root_node = find_root_tag(this->absolute_root_node))) { - this->root_node = this->absolute_root_node; - } - // FIXME: Consider giving away ownership of root_node to the Tree, or use reference counted pointers - this->tree.setRoot(this->root_node); - // Dump the tree (to initialize caches). - // FIXME: We shouldn't really need to do this explicitly.. - this->tree.getString(*this->root_node); - - if (1) { - PRINT("Compilation finished."); - if (procevents) - QApplication::processEvents(); - } else { -fail: + if (!this->root_node) { if (parser_error_pos < 0) { PRINT("ERROR: Compilation failed! (no top level object found)"); } else { PRINT("ERROR: Compilation failed!"); } - if (procevents) - QApplication::processEvents(); + if (procevents) QApplication::processEvents(); } + + return true; } /*! Generates CSG tree for OpenCSG evaluation. - Assumes that the design has been parsed and evaluated + Assumes that the design has been parsed and evaluated (this->root_node is set) */ void MainWindow::compileCSG(bool procevents) { @@ -991,7 +932,7 @@ void MainWindow::actionSaveAs() void MainWindow::actionReload() { - if (checkModified()) load(); + if (checkEditorModified()) refreshDocument(); } void MainWindow::hideEditor() @@ -1031,16 +972,127 @@ void MainWindow::pasteViewportRotation() cursor.insertText(txt); } -void MainWindow::checkAutoReload() +void MainWindow::updateTemporalVariables() +{ + this->root_ctx.set_variable("$t", Value(this->e_tval->text().toDouble())); + + Value vpt; + vpt.type = Value::VECTOR; + vpt.append(new Value(-this->glview->object_trans_x)); + vpt.append(new Value(-this->glview->object_trans_y)); + vpt.append(new Value(-this->glview->object_trans_z)); + this->root_ctx.set_variable("$vpt", vpt); + + Value vpr; + vpr.type = Value::VECTOR; + vpr.append(new Value(fmodf(360 - this->glview->object_rot_x + 90, 360))); + vpr.append(new Value(fmodf(360 - this->glview->object_rot_y, 360))); + vpr.append(new Value(fmodf(360 - this->glview->object_rot_z, 360))); + root_ctx.set_variable("$vpr", vpr); +} + +bool MainWindow::fileChangedOnDisk() { if (!this->fileName.isEmpty()) { - QString new_stinfo; - QFileInfo finfo(this->fileName); - new_stinfo = QString::number(finfo.size()) + QString::number(finfo.lastModified().toTime_t()); - if (new_stinfo != autoReloadInfo) - actionReloadCompile(); - autoReloadInfo = new_stinfo; + struct stat st; + memset(&st, 0, sizeof(struct stat)); + stat(this->fileName.toLocal8Bit(), &st); + std::string newid = str(boost::format("%x.%x") % st.st_mtime % st.st_size); + + if (newid != this->autoReloadId) { + this->autoReloadId = newid; + return true; + } + } + return false; +} + +// FIXME: The following two methods are duplicated in ModuleCache.cc - refactor +static bool is_modified(const std::string &filename, const time_t &mtime) +{ + struct stat st; + memset(&st, 0, sizeof(struct stat)); + stat(filename.c_str(), &st); + return (st.st_mtime > mtime); +} + +bool MainWindow::includesChanged() +{ + if (this->root_module) { + BOOST_FOREACH(const Module::IncludeContainer::value_type &item, this->root_module->includes) { + if (is_modified(item.first, item.second)) return true; + } } + return false; +} + +/*! + If reload is true, does a timestamp check on the document and tries to reload it. + Otherwise, just reparses the current document and any dependencies, updates the + GUI accordingly and populates this->root_module. + + Returns true if anything was compiled. +*/ +bool MainWindow::compileTopLevelDocument(bool reload) +{ + bool shouldcompiletoplevel = !reload; + + if ((reload && fileChangedOnDisk() && checkEditorModified()) || + includesChanged()) { + shouldcompiletoplevel = true; + refreshDocument(); + } + + if (shouldcompiletoplevel) { + console->clear(); + + updateTemporalVariables(); + + this->last_compiled_doc = editor->toPlainText(); + std::string fulltext = + this->last_compiled_doc.toStdString() + "\n" + commandline_commands; + + delete this->root_module; + this->root_module = NULL; + + this->root_module = parse(fulltext.c_str(), + this->fileName.isEmpty() ? + "" : + QFileInfo(this->fileName).absolutePath().toLocal8Bit(), + false); + + // Error highlighting + delete this->highlighter; + this->highlighter = NULL; + + if (!this->root_module) { + this->highlighter = new Highlighter(editor->document()); + + if (!animate_panel->isVisible()) { +#ifdef _QCODE_EDIT_ + QDocumentCursor cursor = editor->cursor(); + cursor.setPosition(parser_error_pos); +#else + QTextCursor cursor = editor->textCursor(); + cursor.setPosition(parser_error_pos); + editor->setTextCursor(cursor); +#endif + } + } + } + + bool changed = shouldcompiletoplevel; + if (this->root_module) { + changed |= this->root_module->handleDependencies(); + if (changed) PRINTB("Module cache size: %d modules", ModuleCache::instance()->size()); + } + + return changed; +} + +void MainWindow::checkAutoReload() +{ + if (!this->fileName.isEmpty()) actionReloadCompile(); } void MainWindow::autoReloadSet(bool on) @@ -1048,14 +1100,14 @@ void MainWindow::autoReloadSet(bool on) QSettings settings; settings.setValue("design/autoReload",designActionAutoReload->isChecked()); if (on) { - autoReloadInfo = QString(); + autoReloadId = ""; autoReloadTimer->start(200); } else { autoReloadTimer->stop(); } } -bool MainWindow::checkModified() +bool MainWindow::checkEditorModified() { if (editor->isContentModified()) { QMessageBox::StandardButton ret; @@ -1075,15 +1127,11 @@ void MainWindow::actionReloadCompile() { if (GuiLocker::isLocked()) return; GuiLocker lock; - - if (!checkModified()) return; - - console->clear(); - - load(); - setCurrentOutput(); - compile(true); + + // PRINT("Parsing design (AST generation)..."); + // QApplication::processEvents(); + if (!compile(true, true)) return; if (this->root_node) compileCSG(true); // Go to non-CGAL view mode @@ -1105,11 +1153,12 @@ void MainWindow::actionCompile() { if (GuiLocker::isLocked()) return; GuiLocker lock; - setCurrentOutput(); console->clear(); - compile(!viewActionAnimate->isChecked()); + PRINT("Parsing design (AST generation)..."); + QApplication::processEvents(); + compile(false, !viewActionAnimate->isChecked()); if (this->root_node) compileCSG(!viewActionAnimate->isChecked()); // Go to non-CGAL view mode @@ -1127,8 +1176,8 @@ void MainWindow::actionCompile() if (viewActionAnimate->isChecked() && e_dump->isChecked()) { QImage img = this->glview->grabFrameBuffer(); QString filename; - double s = e_fsteps->text().toDouble(); - double t = e_tval->text().toDouble(); + double s = this->e_fsteps->text().toDouble(); + double t = this->e_tval->text().toDouble(); filename.sprintf("frame%05d.png", int(round(s*t))); img.save(filename, "PNG"); } @@ -1146,7 +1195,9 @@ void MainWindow::actionRenderCGAL() setCurrentOutput(); console->clear(); - compile(true); + PRINT("Parsing design (AST generation)..."); + QApplication::processEvents(); + compile(false, true); if (!this->root_module || !this->root_node) { return; @@ -1449,7 +1500,7 @@ void MainWindow::actionFlushCaches() #endif dxf_dim_cache.clear(); dxf_cross_cache.clear(); - Module::clear_library_cache(); + ModuleCache::instance()->clear(); } void MainWindow::viewModeActionsUncheck() @@ -1556,7 +1607,7 @@ void MainWindow::animateUpdate() { if (animate_panel->isVisible()) { bool fps_ok; - double fps = e_fps->text().toDouble(&fps_ok); + double fps = this->e_fps->text().toDouble(&fps_ok); if (fps_ok && fps <= 0 && !animate_timer->isActive()) { animate_timer->stop(); animate_timer->setSingleShot(true); @@ -1699,15 +1750,28 @@ MainWindow::helpManual() QDesktopServices::openUrl(QUrl("http://en.wikibooks.org/wiki/OpenSCAD_User_Manual")); } -void MainWindow::helpOpenGL() +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) +void MainWindow::helpLibrary() { + QString libinfo; + libinfo.sprintf("Boost version: %s\n" + "Eigen version: %d.%d.%d\n" + "CGAL version: %s\n" + "OpenCSG version: %s\n\n", + BOOST_LIB_VERSION, + EIGEN_WORLD_VERSION, EIGEN_MAJOR_VERSION, EIGEN_MINOR_VERSION, + TOSTRING(CGAL_VERSION), + OPENCSG_VERSION_STRING); + if (!this->openglbox) { this->openglbox = new QMessageBox(QMessageBox::Information, - "OpenGL Info", "Detailed OpenGL Info", + "OpenGL Info", "Detailed Library Info", QMessageBox::Ok, this); } - this->openglbox->setDetailedText(this->glview->getRendererInfo()); + + this->openglbox->setDetailedText(libinfo + this->glview->getRendererInfo()); this->openglbox->show(); } |