diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/CGALEvaluator.cc | 9 | ||||
-rw-r--r-- | src/CSGTermEvaluator.cc | 12 | ||||
-rw-r--r-- | src/MainWindow.h | 1 | ||||
-rw-r--r-- | src/PlatformUtils-posix.cc | 13 | ||||
-rw-r--r-- | src/PolySetCGALEvaluator.cc | 4 | ||||
-rw-r--r-- | src/Preferences.cc | 127 | ||||
-rw-r--r-- | src/Preferences.h | 7 | ||||
-rw-r--r-- | src/Preferences.ui | 151 | ||||
-rw-r--r-- | src/calc.cc | 40 | ||||
-rw-r--r-- | src/calc.h | 8 | ||||
-rw-r--r-- | src/csgtermnormalizer.cc | 24 | ||||
-rw-r--r-- | src/csgtermnormalizer.h | 1 | ||||
-rw-r--r-- | src/dxfdata.cc | 40 | ||||
-rw-r--r-- | src/evalcontext.cc | 2 | ||||
-rw-r--r-- | src/feature.cc | 79 | ||||
-rw-r--r-- | src/feature.h | 44 | ||||
-rw-r--r-- | src/func.cc | 19 | ||||
-rw-r--r-- | src/function.h | 9 | ||||
-rw-r--r-- | src/highlighter.cc | 150 | ||||
-rw-r--r-- | src/highlighter.h | 3 | ||||
-rw-r--r-- | src/linalg.cc | 1 | ||||
-rw-r--r-- | src/linearextrude.cc | 4 | ||||
-rw-r--r-- | src/mainwin.cc | 8 | ||||
-rw-r--r-- | src/modcontext.cc | 11 | ||||
-rw-r--r-- | src/module.h | 7 | ||||
-rw-r--r-- | src/openscad.cc | 16 | ||||
-rw-r--r-- | src/openscad.h | 1 | ||||
-rw-r--r-- | src/parsersettings.cc | 5 | ||||
-rw-r--r-- | src/parsersettings.h | 2 | ||||
-rw-r--r-- | src/primitives.cc | 51 | ||||
-rw-r--r-- | src/value.cc | 10 |
31 files changed, 720 insertions, 139 deletions
diff --git a/src/CGALEvaluator.cc b/src/CGALEvaluator.cc index 20c5d5e..60d98b8 100644 --- a/src/CGALEvaluator.cc +++ b/src/CGALEvaluator.cc @@ -61,6 +61,11 @@ void CGALEvaluator::process(CGAL_Nef_polyhedron &target, const CGAL_Nef_polyhedr if (target.dim != 2 && target.dim != 3) { assert(false && "Dimension of Nef polyhedron must be 2 or 3"); } + // Intersecting something with nothing results in nothing + if (src.isEmpty() && op == CGE_INTERSECTION) { + target = src; + return; + } if (src.isEmpty()) return; // Empty polyhedron. This can happen for e.g. square([0,0]) if (target.isEmpty() && op != CGE_UNION) return; // empty op <something> => empty if (target.dim != src.dim) return; // If someone tries to e.g. union 2d and 3d objects @@ -708,7 +713,7 @@ CGAL_Nef_polyhedron CGALEvaluator::evaluateCGALMesh(const PolySet &ps) PRINTB("Alternate construction failed. CGAL error in CGAL_Nef_polyhedron3(): %s", e.what()); } CGAL::set_error_behaviour(old_behaviour); - return CGAL_Nef_polyhedron(N); + if (N) return CGAL_Nef_polyhedron(N); } - return CGAL_Nef_polyhedron(); + return CGAL_Nef_polyhedron(ps.is2d?2:3); } diff --git a/src/CSGTermEvaluator.cc b/src/CSGTermEvaluator.cc index 647a3dc..56f7b3a 100644 --- a/src/CSGTermEvaluator.cc +++ b/src/CSGTermEvaluator.cc @@ -108,7 +108,7 @@ static shared_ptr<CSGTerm> evaluate_csg_term_from_ps(const State &state, Response CSGTermEvaluator::visit(State &state, const AbstractPolyNode &node) { - if (state.isPrefix()) { + if (state.isPostfix()) { shared_ptr<CSGTerm> t1; if (this->psevaluator) { shared_ptr<PolySet> ps = this->psevaluator->getPolySet(node, true); @@ -121,7 +121,7 @@ Response CSGTermEvaluator::visit(State &state, const AbstractPolyNode &node) this->stored_term[node.index()] = t1; addToParent(state, node); } - return PruneTraversal; + return ContinueTraversal; } Response CSGTermEvaluator::visit(State &state, const CsgNode &node) @@ -174,7 +174,7 @@ Response CSGTermEvaluator::visit(State &state, const ColorNode &node) // FIXME: If we've got CGAL support, render this node as a CGAL union into a PolySet Response CSGTermEvaluator::visit(State &state, const RenderNode &node) { - if (state.isPrefix()) { + if (state.isPostfix()) { shared_ptr<CSGTerm> t1; shared_ptr<PolySet> ps; if (this->psevaluator) { @@ -188,12 +188,12 @@ Response CSGTermEvaluator::visit(State &state, const RenderNode &node) this->stored_term[node.index()] = t1; addToParent(state, node); } - return PruneTraversal; + return ContinueTraversal; } Response CSGTermEvaluator::visit(State &state, const CgaladvNode &node) { - if (state.isPrefix()) { + if (state.isPostfix()) { shared_ptr<CSGTerm> t1; // FIXME: Calling evaluator directly since we're not a PolyNode. Generalize this. shared_ptr<PolySet> ps; @@ -208,7 +208,7 @@ Response CSGTermEvaluator::visit(State &state, const CgaladvNode &node) this->stored_term[node.index()] = t1; addToParent(state, node); } - return PruneTraversal; + return ContinueTraversal; } /*! diff --git a/src/MainWindow.h b/src/MainWindow.h index ac999bf..4948d46 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -70,6 +70,7 @@ private slots: void updateTVal(); void setFileName(const QString &filename); void setFont(const QString &family, uint size); + void setSyntaxHighlight(const QString &s); void showProgress(); void openCSGSettingsChanged(); diff --git a/src/PlatformUtils-posix.cc b/src/PlatformUtils-posix.cc index d7b7b6d..d2b8792 100644 --- a/src/PlatformUtils-posix.cc +++ b/src/PlatformUtils-posix.cc @@ -3,8 +3,13 @@ std::string PlatformUtils::documentsPath() { - fs::path docpath(getenv("HOME")); - docpath = docpath / ".local" / "share"; - - return boosty::stringy(docpath); + const char *home = getenv("HOME"); + if (home) { + fs::path docpath(home); + docpath = docpath / ".local" / "share"; + return boosty::stringy(docpath); + } + else { + return ""; + } } diff --git a/src/PolySetCGALEvaluator.cc b/src/PolySetCGALEvaluator.cc index a2d896d..0b57de1 100644 --- a/src/PolySetCGALEvaluator.cc +++ b/src/PolySetCGALEvaluator.cc @@ -13,10 +13,10 @@ #include "dxfdata.h" #include "dxftess.h" #include "module.h" +#include "calc.h" #include "svg.h" #include "printutils.h" -#include "openscad.h" // get_fragments_from_r() #include <boost/foreach.hpp> #include <vector> @@ -469,7 +469,7 @@ PolySet *PolySetCGALEvaluator::rotateDxfData(const RotateExtrudeNode &node, DxfD } } - int fragments = get_fragments_from_r(max_x-min_x, node.fn, node.fs, node.fa); + int fragments = Calc::get_fragments_from_r(max_x-min_x, node.fn, node.fs, node.fa); double ***points; points = new double**[fragments]; diff --git a/src/Preferences.cc b/src/Preferences.cc index 0f3115e..92f11c7 100644 --- a/src/Preferences.cc +++ b/src/Preferences.cc @@ -33,12 +33,16 @@ #include <QStatusBar> #include "PolySetCache.h" #include "AutoUpdater.h" +#include "feature.h" #ifdef ENABLE_CGAL #include "CGALCache.h" #endif Preferences *Preferences::instance = NULL; +const char * Preferences::featurePropertyName = "FeatureProperty"; +Q_DECLARE_METATYPE(Feature *); + Preferences::Preferences(QWidget *parent) : QMainWindow(parent) { setupUi(this); @@ -59,6 +63,7 @@ Preferences::Preferences(QWidget *parent) : QMainWindow(parent) QString found_family(QFontInfo(font).family()); this->defaultmap["editor/fontfamily"] = found_family; this->defaultmap["editor/fontsize"] = 12; + this->defaultmap["editor/syntaxhighlight"] = "For Light Background"; uint savedsize = getValue("editor/fontsize").toUInt(); QFontDatabase db; @@ -86,13 +91,13 @@ Preferences::Preferences(QWidget *parent) : QMainWindow(parent) this->defaultmap["advanced/openCSGLimit"] = RenderSettings::inst()->openCSGTermLimit; this->defaultmap["advanced/forceGoldfeather"] = false; - // Toolbar QActionGroup *group = new QActionGroup(this); - group->addAction(prefsAction3DView); - group->addAction(prefsActionEditor); - group->addAction(prefsActionUpdate); - group->addAction(prefsActionAdvanced); + addPrefPage(group, prefsAction3DView, page3DView); + addPrefPage(group, prefsActionEditor, pageEditor); + addPrefPage(group, prefsActionUpdate, pageUpdate); + addPrefPage(group, prefsActionFeatures, pageFeatures); + addPrefPage(group, prefsActionAdvanced, pageAdvanced); connect(group, SIGNAL(triggered(QAction*)), this, SLOT(actionTriggered(QAction*))); prefsAction3DView->setChecked(true); @@ -140,6 +145,7 @@ Preferences::Preferences(QWidget *parent) : QMainWindow(parent) this->polysetCacheSizeEdit->setValidator(validator); this->opencsgLimitEdit->setValidator(validator); + setupFeaturesPage(); updateGUI(); RenderSettings::inst()->setColors(this->colorschemes[getValue("3dview/colorscheme").toString()]); @@ -150,21 +156,102 @@ Preferences::~Preferences() removeDefaultSettings(); } +/** + * Add a page for the preferences GUI. This handles both the action grouping + * and the registration of the widget for each action to have a generalized + * callback to switch pages. + * + * @param group The action group for all page actions. This one will have the + * callback attached after creating all actions/pages. + * @param action The action specific for the added page. + * @param widget The widget that should be shown when the action is triggered. + * This must be a child page of the stackedWidget. + */ +void +Preferences::addPrefPage(QActionGroup *group, QAction *action, QWidget *widget) +{ + group->addAction(action); + prefPages[action] = widget; +} + +/** + * Callback to switch pages in the preferences GUI. + * + * @param action The action triggered by the user. + */ void Preferences::actionTriggered(QAction *action) { - if (action == this->prefsAction3DView) { - this->stackedWidget->setCurrentWidget(this->page3DView); - } - else if (action == this->prefsActionEditor) { - this->stackedWidget->setCurrentWidget(this->pageEditor); + this->stackedWidget->setCurrentWidget(prefPages[action]); +} + +/** + * Callback for the dynamically created checkboxes on the features + * page. The specific Feature object is associated as property with + * the callback. + * + * @param state the state of the checkbox. + */ +void Preferences::featuresCheckBoxToggled(bool state) +{ + const QObject *sender = QObject::sender(); + if (sender == NULL) { + return; } - else if (action == this->prefsActionUpdate) { - this->stackedWidget->setCurrentWidget(this->pageUpdate); + QVariant v = sender->property(featurePropertyName); + if (!v.isValid()) { + return; } - else if (action == this->prefsActionAdvanced) { - this->stackedWidget->setCurrentWidget(this->pageAdvanced); + Feature *feature = v.value<Feature *>(); + feature->enable(state); + QSettings settings; + settings.setValue(QString("feature/%1").arg(QString::fromStdString(feature->get_name())), state); +} + +/** + * Setup feature GUI and synchronize the Qt settings with the feature values. + * + * When running in GUI mode, the feature setting that might have been set + * from commandline is ignored. This always uses the value coming from the + * QSettings. + */ +void +Preferences::setupFeaturesPage() +{ + int row = 0; + for (Feature::iterator it = Feature::begin();it != Feature::end();it++) { + Feature *feature = *it; + + QString featurekey = QString("feature/%1").arg(QString::fromStdString(feature->get_name())); + this->defaultmap[featurekey] = false; + + // spacer item between the features, just for some optical separation + gridLayoutExperimentalFeatures->addItem(new QSpacerItem(1, 8, QSizePolicy::Expanding, QSizePolicy::Fixed), row, 1, 1, 1, Qt::AlignCenter); + row++; + + QCheckBox *cb = new QCheckBox(QString::fromStdString(feature->get_name()), pageFeatures); + QFont bold_font(cb->font()); + bold_font.setBold(true); + cb->setFont(bold_font); + // synchronize Qt settings with the feature settings + bool value = getValue(featurekey).toBool(); + feature->enable(value); + cb->setChecked(value); + cb->setProperty(featurePropertyName, QVariant::fromValue<Feature *>(feature)); + connect(cb, SIGNAL(toggled(bool)), this, SLOT(featuresCheckBoxToggled(bool))); + gridLayoutExperimentalFeatures->addWidget(cb, row, 0, 1, 2, Qt::AlignLeading); + row++; + + QLabel *l = new QLabel(QString::fromStdString(feature->get_description()), pageFeatures); + l->setTextFormat(Qt::RichText); + gridLayoutExperimentalFeatures->addWidget(l, row, 1, 1, 1, Qt::AlignLeading); + row++; } + // Force fixed indentation, the checkboxes use column span of 2 so + // first row is not constrained in size by the visible controls. The + // fixed size space essentially gives the first row the width of the + // spacer item itself. + gridLayoutExperimentalFeatures->addItem(new QSpacerItem(20, 0, QSizePolicy::Fixed, QSizePolicy::Fixed), 1, 0, 1, 1, Qt::AlignLeading); } void Preferences::on_colorSchemeChooser_itemSelectionChanged() @@ -193,6 +280,13 @@ void Preferences::on_fontSize_editTextChanged(const QString &size) emit fontChanged(getValue("editor/fontfamily").toString(), intsize); } +void Preferences::on_syntaxHighlight_currentIndexChanged(const QString &s) +{ + QSettings settings; + settings.setValue("editor/syntaxhighlight", s); + emit syntaxHighlightChanged(s); +} + void unimplemented_msg() { QMessageBox mbox; @@ -309,7 +403,6 @@ QVariant Preferences::getValue(const QString &key) const void Preferences::updateGUI() { - QSettings settings; QList<QListWidgetItem *> found = this->colorSchemeChooser->findItems(getValue("3dview/colorscheme").toString(), Qt::MatchExactly); @@ -330,6 +423,10 @@ void Preferences::updateGUI() this->fontSize->setEditText(fontsize); } + QString shighlight = getValue("editor/syntaxhighlight").toString(); + int shidx = this->syntaxHighlight->findText(shighlight); + if (shidx >= 0) this->syntaxHighlight->setCurrentIndex(shidx); + if (AutoUpdater *updater = AutoUpdater::updater()) { this->updateCheckBox->setChecked(updater->automaticallyChecksForUpdates()); this->snapshotCheckBox->setChecked(updater->enableSnapshots()); diff --git a/src/Preferences.h b/src/Preferences.h index 4656793..d74ada9 100644 --- a/src/Preferences.h +++ b/src/Preferences.h @@ -21,9 +21,11 @@ public: public slots: void actionTriggered(class QAction *); + void featuresCheckBoxToggled(bool); void on_colorSchemeChooser_itemSelectionChanged(); void on_fontChooser_activated(const QString &); void on_fontSize_editTextChanged(const QString &); + void on_syntaxHighlight_currentIndexChanged(const QString &); void on_openCSGWarningBox_toggled(bool); void on_enableOpenCSGBox_toggled(bool); void on_cgalCacheSizeEdit_textChanged(const QString &); @@ -38,17 +40,22 @@ signals: void requestRedraw() const; void fontChanged(const QString &family, uint size) const; void openCSGSettingsChanged() const; + void syntaxHighlightChanged(const QString &s); private: Preferences(QWidget *parent = NULL); void keyPressEvent(QKeyEvent *e); void updateGUI(); void removeDefaultSettings(); + void setupFeaturesPage(); + void addPrefPage(QActionGroup *group, QAction *action, QWidget *widget); QSettings::SettingsMap defaultmap; QHash<QString, std::map<RenderSettings::RenderColor, Color4f> > colorschemes; + QHash<const QAction *, QWidget *> prefPages; static Preferences *instance; + static const char *featurePropertyName; }; #endif diff --git a/src/Preferences.ui b/src/Preferences.ui index d67db6a..100bcb1 100644 --- a/src/Preferences.ui +++ b/src/Preferences.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>473</width> - <height>320</height> + <width>823</width> + <height>433</height> </rect> </property> <property name="sizePolicy"> @@ -27,7 +27,7 @@ <item> <widget class="QStackedWidget" name="stackedWidget"> <property name="currentIndex"> - <number>2</number> + <number>1</number> </property> <widget class="QWidget" name="page3DView"> <layout class="QVBoxLayout" name="verticalLayout_4"> @@ -136,7 +136,7 @@ <widget class="QFontComboBox" name="fontChooser"> <property name="currentFont"> <font> - <family>Monaco</family> + <family>DejaVu Sans</family> <pointsize>12</pointsize> </font> </property> @@ -165,6 +165,61 @@ </layout> </item> <item> + <layout class="QHBoxLayout" name="horizontalLayout_5"> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="label_9"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Color syntax highlighting</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Maximum</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QComboBox" name="syntaxHighlight"> + <item> + <property name="text"> + <string>For Light Background</string> + </property> + </item> + <item> + <property name="text"> + <string>For Dark Background</string> + </property> + </item> + <item> + <property name="text"> + <string>Off</string> + </property> + </item> + </widget> + </item> + </layout> + </item> + <item> <spacer name="verticalSpacer_4"> <property name="orientation"> <enum>Qt::Vertical</enum> @@ -323,6 +378,78 @@ </item> </layout> </widget> + <widget class="QWidget" name="pageFeatures"> + <layout class="QGridLayout" name="gridLayout_2"> + <property name="margin"> + <number>0</number> + </property> + <item row="0" column="0"> + <layout class="QVBoxLayout" name="verticalLayout_9"> + <item> + <widget class="QLabel" name="label_9"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Features</string> + </property> + </widget> + </item> + <item> + <widget class="QScrollArea" name="scrollArea"> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Plain</enum> + </property> + <property name="horizontalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="widgetResizable"> + <bool>true</bool> + </property> + <widget class="QWidget" name="scrollAreaWidgetContents"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>803</width> + <height>311</height> + </rect> + </property> + <layout class="QVBoxLayout" name="verticalLayout_10"> + <item> + <layout class="QGridLayout" name="gridLayoutExperimentalFeatures" rowminimumheight="0"> + <property name="spacing"> + <number>8</number> + </property> + </layout> + </item> + <item> + <spacer name="verticalSpacer_3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>282</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </widget> + </item> + </layout> + </item> + </layout> + </widget> <widget class="QWidget" name="pageAdvanced"> <layout class="QVBoxLayout" name="verticalLayout_3"> <item> @@ -465,6 +592,7 @@ <addaction name="prefsAction3DView"/> <addaction name="prefsActionEditor"/> <addaction name="prefsActionUpdate"/> + <addaction name="prefsActionFeatures"/> <addaction name="prefsActionAdvanced"/> </widget> <action name="prefsAction3DView"> @@ -515,6 +643,21 @@ <string>Update</string> </property> </action> + <action name="prefsActionFeatures"> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="icon"> + <iconset resource="../openscad.qrc"> + <normaloff>:/icons/prefsFeatures.png</normaloff>:/icons/prefsFeatures.png</iconset> + </property> + <property name="text"> + <string>Features</string> + </property> + <property name="toolTip"> + <string>Enable/Disable experimental features</string> + </property> + </action> </widget> <resources> <include location="../openscad.qrc"/> diff --git a/src/calc.cc b/src/calc.cc new file mode 100644 index 0000000..bdae085 --- /dev/null +++ b/src/calc.cc @@ -0,0 +1,40 @@ +/* + * OpenSCAD (www.openscad.org) + * Copyright (C) 2009-2011 Clifford Wolf <clifford@clifford.at> and + * Marius Kintel <marius@kintel.net> + * + * 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 2 of the License, or + * (at your option) any later version. + * + * As a special exception, you have permission to link this program + * with the CGAL library and distribute executables, as long as you + * follow the requirements of the GNU GPL in regard to all of the + * software in the executable aside from CGAL. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "calc.h" +#include "grid.h" + +/*! + Returns the number of subdivision of a whole circle, given radius and + the three special variables $fn, $fs and $fa +*/ +int Calc::get_fragments_from_r(double r, double fn, double fs, double fa) +{ + if (r < GRID_FINE) return 3; + if (fn > 0.0) return (int)(fn >= 3 ? fn : 3); + return (int)ceil(fmax(fmin(360.0 / fa, r*2*M_PI / fs), 5)); +} + diff --git a/src/calc.h b/src/calc.h new file mode 100644 index 0000000..1ac9e17 --- /dev/null +++ b/src/calc.h @@ -0,0 +1,8 @@ +#ifndef CALC_H_ +#define CALC_H_ + +namespace Calc { + int get_fragments_from_r(double r, double fn, double fs, double fa); +} + +#endif diff --git a/src/csgtermnormalizer.cc b/src/csgtermnormalizer.cc index 461e965..2f79841 100644 --- a/src/csgtermnormalizer.cc +++ b/src/csgtermnormalizer.cc @@ -32,6 +32,22 @@ shared_ptr<CSGTerm> CSGTermNormalizer::normalize(const shared_ptr<CSGTerm> &root return temp; } +/*! + After aborting, a subtree might have become invalidated (NULL child term) + since terms can be instantiated multiple times. + This will search for NULL children an recursively repair the corresponding + subtree. + */ +shared_ptr<CSGTerm> CSGTermNormalizer::cleanup_term(shared_ptr<CSGTerm> &t) +{ + if (t->type != CSGTerm::TYPE_PRIMITIVE) { + if (t->left) t->left = cleanup_term(t->left); + if (t->right) t->right = cleanup_term(t->right); + return collapse_null_terms(t); + } + else return t; +} + shared_ptr<CSGTerm> CSGTermNormalizer::normalizePass(shared_ptr<CSGTerm> term) { // This function implements the CSG normalization @@ -62,7 +78,13 @@ shared_ptr<CSGTerm> CSGTermNormalizer::normalizePass(shared_ptr<CSGTerm> term) if (!this->aborted) term->right = normalizePass(term->right); // FIXME: Do we need to take into account any transformation of item here? - return collapse_null_terms(term); + shared_ptr<CSGTerm> t = collapse_null_terms(term); + + if (this->aborted) { + if (t) t = cleanup_term(t); + } + + return t; } shared_ptr<CSGTerm> CSGTermNormalizer::collapse_null_terms(const shared_ptr<CSGTerm> &term) diff --git a/src/csgtermnormalizer.h b/src/csgtermnormalizer.h index f7a444f..bb55141 100644 --- a/src/csgtermnormalizer.h +++ b/src/csgtermnormalizer.h @@ -15,6 +15,7 @@ private: shared_ptr<CSGTerm> normalizePass(shared_ptr<CSGTerm> term) ; bool match_and_replace(shared_ptr<CSGTerm> &term); shared_ptr<CSGTerm> collapse_null_terms(const shared_ptr<CSGTerm> &term); + shared_ptr<CSGTerm> cleanup_term(shared_ptr<CSGTerm> &t); unsigned int count(const shared_ptr<CSGTerm> &term) const; bool aborted; diff --git a/src/dxfdata.cc b/src/dxfdata.cc index 8415228..ffea169 100644 --- a/src/dxfdata.cc +++ b/src/dxfdata.cc @@ -28,7 +28,7 @@ #include "grid.h" #include "printutils.h" #include "handle_dep.h" -#include "openscad.h" // get_fragments_from_r() +#include "calc.h" #include <fstream> #include "mathc99.h" @@ -175,22 +175,23 @@ DxfData::DxfData(double fn, double fs, double fa, in_blocks_section = iddata == "BLOCKS"; } else if (mode == "LINE") { - ADD_LINE(xverts[0], yverts[0], xverts[1], yverts[1]); + ADD_LINE(xverts.at(0), yverts.at(0), xverts.at(1), yverts.at(1)); } else if (mode == "LWPOLYLINE") { - assert(xverts.size() == yverts.size()); - // polyline flag is stored in 'dimtype' - int numverts = xverts.size(); + // assert(xverts.size() == yverts.size()); + // Get maximum to enforce managed exception if xverts.size() != yverts.size() + int numverts = std::max(xverts.size(), yverts.size()); for (int i=1;i<numverts;i++) { - ADD_LINE(xverts[i-1], yverts[i-1], xverts[i%numverts], yverts[i%numverts]); + ADD_LINE(xverts.at(i-1), yverts.at(i-1), xverts.at(i%numverts), yverts.at(i%numverts)); } + // polyline flag is stored in 'dimtype' if (dimtype & 0x01) { // closed polyline - ADD_LINE(xverts[numverts-1], yverts[numverts-1], xverts[0], yverts[0]); + ADD_LINE(xverts.at(numverts-1), yverts.at(numverts-1), xverts.at(0), yverts.at(0)); } } else if (mode == "CIRCLE") { - int n = get_fragments_from_r(radius, fn, fs, fa); - Vector2d center(xverts[0], yverts[0]); + int n = Calc::get_fragments_from_r(radius, fn, fs, fa); + Vector2d center(xverts.at(0), yverts.at(0)); for (int i = 0; i < n; i++) { double a1 = (2*M_PI*i)/n; double a2 = (2*M_PI*(i+1))/n; @@ -199,8 +200,8 @@ DxfData::DxfData(double fn, double fs, double fa, } } else if (mode == "ARC") { - Vector2d center(xverts[0], yverts[0]); - int n = get_fragments_from_r(radius, fn, fs, fa); + Vector2d center(xverts.at(0), yverts.at(0)); + int n = Calc::get_fragments_from_r(radius, fn, fs, fa); while (arc_start_angle > arc_stop_angle) arc_stop_angle += 360.0; n = (int)ceil(n * (arc_stop_angle-arc_start_angle) / 360); @@ -217,9 +218,9 @@ DxfData::DxfData(double fn, double fs, double fa, // Commented code is meant as documentation of vector math while (ellipse_start_angle > ellipse_stop_angle) ellipse_stop_angle += 2 * M_PI; // Vector2d center(xverts[0], yverts[0]); - Vector2d center(xverts[0], yverts[0]); + Vector2d center(xverts.at(0), yverts.at(0)); // Vector2d ce(xverts[1], yverts[1]); - Vector2d ce(xverts[1], yverts[1]); + Vector2d ce(xverts.at(1), yverts.at(1)); // double r_major = ce.length(); double r_major = sqrt(ce[0]*ce[0] + ce[1]*ce[1]); // double rot_angle = ce.angle(); @@ -237,7 +238,7 @@ DxfData::DxfData(double fn, double fs, double fa, // the ratio stored in 'radius; due to the parser code not checking entity type double r_minor = r_major * radius; double sweep_angle = ellipse_stop_angle-ellipse_start_angle; - int n = get_fragments_from_r(r_major, fn, fs, fa); + int n = Calc::get_fragments_from_r(r_major, fn, fs, fa); n = (int)ceil(n * sweep_angle / (2 * M_PI)); // Vector2d p1; Vector2d p1; p1 << 0,0; @@ -270,10 +271,10 @@ DxfData::DxfData(double fn, double fs, double fa, double ly1 = this->points[blockdata[iddata][i].idx[0]][1] * ellipse_stop_angle; double lx2 = this->points[blockdata[iddata][i].idx[1]][0] * ellipse_start_angle; double ly2 = this->points[blockdata[iddata][i].idx[1]][1] * ellipse_stop_angle; - double px1 = (cos(a)*lx1 - sin(a)*ly1) * scale + xverts[0]; - double py1 = (sin(a)*lx1 + cos(a)*ly1) * scale + yverts[0]; - double px2 = (cos(a)*lx2 - sin(a)*ly2) * scale + xverts[0]; - double py2 = (sin(a)*lx2 + cos(a)*ly2) * scale + yverts[0]; + double px1 = (cos(a)*lx1 - sin(a)*ly1) * scale + xverts.at(0); + double py1 = (sin(a)*lx1 + cos(a)*ly1) * scale + yverts.at(0); + double px2 = (cos(a)*lx2 - sin(a)*ly2) * scale + xverts.at(0); + double py2 = (sin(a)*lx2 + cos(a)*ly2) * scale + yverts.at(0); ADD_LINE(px1, py1, px2, py2); } } @@ -385,6 +386,9 @@ DxfData::DxfData(double fn, double fs, double fa, catch (boost::bad_lexical_cast &blc) { PRINTB("WARNING: Illegal value %s in '%s'", data % filename); } + catch (const std::out_of_range& oor) { + PRINTB("WARNING: not enough input values for %s in '%s'", data % filename); + } } BOOST_FOREACH(const EntityList::value_type &i, unsupported_entities_list) { diff --git a/src/evalcontext.cc b/src/evalcontext.cc index 57c206f..7752451 100644 --- a/src/evalcontext.cc +++ b/src/evalcontext.cc @@ -41,7 +41,7 @@ void EvalContext::dump(const AbstractModule *mod, const ModuleInstantiation *ins PRINTB(" document path: %s", this->document_path); PRINT(" eval args:"); - for (int i=0;i<this->eval_arguments.size();i++) { + for (size_t i=0;i<this->eval_arguments.size();i++) { PRINTB(" %s = %s", this->eval_arguments[i].first % this->eval_arguments[i].second); } if (this->scope && this->scope->children.size() > 0) { diff --git a/src/feature.cc b/src/feature.cc new file mode 100644 index 0000000..80d7887 --- /dev/null +++ b/src/feature.cc @@ -0,0 +1,79 @@ +#include <stdio.h> +#include <iostream> +#include <string> +#include <map> + +#include "feature.h" +#include "printutils.h" + +/** + * Feature registration map/list for later lookup. This must be initialized + * before the static feature instances as those register with this map. + */ +Feature::map_t Feature::feature_map; +Feature::list_t Feature::feature_list; + +/* + * List of features, the names given here are used in both command line + * argument to enable the option and for saving the option value in GUI + * context. + */ +const Feature Feature::ExperimentalConcatFunction("concat", "Enable the <code>concat()</code> function."); + +Feature::Feature(const std::string &name, const std::string &description) + : enabled(false), name(name), description(description) +{ + feature_map[name] = this; + feature_list.push_back(this); +} + +Feature::~Feature() +{ +} + +const std::string &Feature::get_name() const +{ + return name; +} + +const std::string &Feature::get_description() const +{ + return description; +} + +bool Feature::is_enabled() const +{ + return enabled; +} + +void Feature::enable(bool status) +{ + enabled = status; +} + +void Feature::enable_feature(const std::string &feature_name, bool status) +{ + map_t::iterator it = feature_map.find(feature_name); + if (it != feature_map.end()) { + it->second->enable(status); + } else { + PRINTB("WARNING: Ignoring request to enable unknown feature '%s'.", feature_name); + } +} + +Feature::iterator Feature::begin() +{ + return feature_list.begin(); +} + +Feature::iterator Feature::end() +{ + return feature_list.end(); +} + +void Feature::dump_features() +{ + for (map_t::iterator it = feature_map.begin(); it != feature_map.end(); it++) { + std::cout << "Feature('" << it->first << "') = " << (it->second->is_enabled() ? "enabled" : "disabled") << std::endl; + } +} diff --git a/src/feature.h b/src/feature.h new file mode 100644 index 0000000..20b4f16 --- /dev/null +++ b/src/feature.h @@ -0,0 +1,44 @@ +#ifndef FEATURE_H_ +#define FEATURE_H_ + +#include <stdio.h> +#include <iostream> +#include <string> +#include <map> +#include <vector> + +class Feature +{ +public: + typedef std::vector<Feature *> list_t; + typedef list_t::iterator iterator; + + static const Feature ExperimentalConcatFunction; + + const std::string& get_name() const; + const std::string& get_description() const; + + bool is_enabled() const; + void enable(bool status); + + static iterator begin(); + static iterator end(); + + static void dump_features(); + static void enable_feature(const std::string &feature_name, bool status = true); + +private: + bool enabled; + + const std::string name; + const std::string description; + + typedef std::map<std::string, Feature *> map_t; + static map_t feature_map; + static list_t feature_list; + + Feature(const std::string &name, const std::string &description); + virtual ~Feature(); +}; + +#endif diff --git a/src/func.cc b/src/func.cc index 4587f72..878bc1c 100644 --- a/src/func.cc +++ b/src/func.cc @@ -343,6 +343,24 @@ Value builtin_str(const Context *, const EvalContext *evalctx) return Value(stream.str()); } +Value builtin_concat(const Context *, const EvalContext *evalctx) +{ + Value::VectorType result; + + for (size_t i = 0; i < evalctx->numArgs(); i++) { + const Value v = evalctx->getArgValue(i); + if (v.type() == Value::VECTOR) { + Value::VectorType vec = v.toVector(); + for (Value::VectorType::const_iterator it = vec.begin(); it != vec.end(); it++) { + result.push_back(*it); + } + } else { + result.push_back(v); + } + } + return Value(result); +} + Value builtin_lookup(const Context *, const EvalContext *evalctx) { double p, low_p, low_v, high_p, high_v; @@ -604,6 +622,7 @@ void register_builtin_functions() Builtins::init("log", new BuiltinFunction(&builtin_log)); Builtins::init("ln", new BuiltinFunction(&builtin_ln)); Builtins::init("str", new BuiltinFunction(&builtin_str)); + Builtins::init("concat", new BuiltinFunction(&builtin_concat, Feature::ExperimentalConcatFunction)); Builtins::init("lookup", new BuiltinFunction(&builtin_lookup)); Builtins::init("search", new BuiltinFunction(&builtin_search)); Builtins::init("version", new BuiltinFunction(&builtin_version)); diff --git a/src/function.h b/src/function.h index a1fde3c..2491809 100644 --- a/src/function.h +++ b/src/function.h @@ -3,13 +3,21 @@ #include "value.h" #include "typedefs.h" +#include "feature.h" + #include <string> #include <vector> + class AbstractFunction { +private: + const Feature *feature; public: + AbstractFunction() : feature(NULL) {} + AbstractFunction(const Feature& feature) : feature(&feature) {} virtual ~AbstractFunction(); + virtual bool is_enabled() const { return (feature == NULL) || feature->is_enabled(); }; virtual Value evaluate(const class Context *ctx, const class EvalContext *evalctx) const; virtual std::string dump(const std::string &indent, const std::string &name) const; }; @@ -21,6 +29,7 @@ public: eval_func_t eval_func; BuiltinFunction(eval_func_t f) : eval_func(f) { } + BuiltinFunction(eval_func_t f, const Feature& feature) : AbstractFunction(feature), eval_func(f) { } virtual ~BuiltinFunction(); virtual Value evaluate(const Context *ctx, const EvalContext *evalctx) const; diff --git a/src/highlighter.cc b/src/highlighter.cc index 1da0e88..2fb7c65 100644 --- a/src/highlighter.cc +++ b/src/highlighter.cc @@ -121,72 +121,110 @@ */ #include "highlighter.h" +#include "Preferences.h" #include <QTextDocument> #include <QTextCursor> #include <QColor> -//#include <iostream> +//#include "printutils.h" + +void format_colors_for_light_background(QMap<QString,QTextCharFormat> &formats) +{ + //PRINT("format for light"); + formats["operator"].setForeground(Qt::blue); + formats["math"].setForeground(QColor("Green")); + formats["keyword"].setForeground(QColor("Green")); + formats["keyword"].setToolTip("Keyword"); + formats["transform"].setForeground(QColor("Indigo")); + formats["csgop"].setForeground(QColor("DarkGreen")); + formats["prim3d"].setForeground(QColor("DarkBlue")); + formats["prim2d"].setForeground(QColor("MidnightBlue")); + formats["import"].setForeground(Qt::darkYellow); + formats["special"].setForeground(Qt::darkGreen); + formats["extrude"].setForeground(Qt::darkGreen); + formats["bracket"].setForeground(QColor("Green")); + formats["curlies"].setForeground(QColor(32,32,20)); + formats["bool"].setForeground(QColor("DarkRed")); + + formats["_$quote"].setForeground(Qt::darkMagenta); + formats["_$comment"].setForeground(Qt::darkCyan); + formats["_$number"].setForeground(QColor("DarkRed")); +} + +void format_colors_for_dark_background(QMap<QString,QTextCharFormat> &formats) +{ + //PRINT("format for dark"); + formats["operator"].setForeground(Qt::blue); + formats["math"].setForeground(Qt::green); + formats["keyword"].setForeground(QColor("LightGreen")); + formats["keyword"].setToolTip("Keyword"); + formats["transform"].setForeground(QColor("Indigo")); + formats["csgop"].setForeground(QColor("LightGreen")); + formats["prim3d"].setForeground(QColor("LightBlue")); + formats["prim2d"].setForeground(QColor("LightBlue")); + formats["import"].setForeground(QColor("LightYellow")); + formats["special"].setForeground(QColor("LightGreen")); + formats["extrude"].setForeground(QColor("LightGreen")); + formats["bracket"].setForeground(QColor("Green")); + formats["curlies"].setForeground(QColor(132,132,120)); + formats["bool"].setForeground(QColor("LightRed")); + + formats["_$quote"].setForeground(Qt::magenta); + formats["_$comment"].setForeground(Qt::cyan); + formats["_$number"].setForeground(Qt::red); +} + +void Highlighter::assignFormatsToTokens(const QString &s) +{ + //PRINTB("assign fmts %s",s.toStdString()); + if (s=="For Light Background") { + format_colors_for_light_background(this->typeformats); + } else if (s=="For Dark Background") { + format_colors_for_dark_background(this->typeformats); + } else return; + + // Put each token into single QHash, and map it to it's appropriate + // qtextchar format (color, bold, etc). For example, '(' is type + // 'bracket' so it should get the 'bracket' format from + // typeformats[] which is, maybe, Green. + + QList<QString>::iterator ki; + QList<QString> toktypes = tokentypes.keys(); + for ( ki=toktypes.begin(); ki!=toktypes.end(); ++ki ) { + QString toktype = *ki; + QStringList::iterator it; + for ( it = tokentypes[toktype].begin(); it < tokentypes[toktype].end(); ++it) { + QString token = *it; + //PRINTB("set format for %s: type %s", token.toStdString()%toktype.toStdString() );; + tokenFormats[ token ] = typeformats [ toktype ]; + } + } +} Highlighter::Highlighter(QTextDocument *parent) : QSyntaxHighlighter(parent) { tokentypes["operator"] << "=" << "!" << "&&" << "||" << "+" << "-" << "*" << "/" << "%" << "!" << "#" << ";"; - typeformats["operator"].setForeground(Qt::blue); - tokentypes["math"] << "abs" << "sign" << "acos" << "asin" << "atan" << "atan2" << "sin" << "cos" << "floor" << "round" << "ceil" << "ln" << "log" << "lookup" << "min" << "max" << "pow" << "sqrt" << "exp" << "rands"; - typeformats["math"].setForeground(Qt::green); - tokentypes["keyword"] << "module" << "function" << "for" << "intersection_for" << "if" << "assign" << "echo"<< "search" << "str"; - typeformats["keyword"].setForeground(QColor("Green")); - typeformats["keyword"].setToolTip("Keyword"); - tokentypes["transform"] << "scale" << "translate" << "rotate" << "multmatrix" << "color" << "projection" << "hull" << "resize" << "mirror" << "minkowski"; - typeformats["transform"].setForeground(QColor("Indigo")); - tokentypes["csgop"] << "union" << "intersection" << "difference" << "render"; - typeformats["csgop"].setForeground(QColor("DarkGreen")); - tokentypes["prim3d"] << "cube" << "cylinder" << "sphere" << "polyhedron"; - typeformats["prim3d"].setForeground(QColor("DarkBlue")); - tokentypes["prim2d"] << "square" << "polygon" << "circle"; - typeformats["prim2d"].setForeground(QColor("MidnightBlue")); - tokentypes["import"] << "include" << "use" << "import_stl" << "import" << "import_dxf" << "dxf_dim" << "dxf_cross" << "surface"; - typeformats["import"].setForeground(Qt::darkYellow); - tokentypes["special"] << "$children" << "child" << "children" << "$fn" << "$fa" << "$fs" << "$t" << "$vpt" << "$vpr"; - typeformats["special"].setForeground(Qt::darkGreen); - tokentypes["extrude"] << "linear_extrude" << "rotate_extrude"; - typeformats["extrude"].setForeground(Qt::darkGreen); - tokentypes["bracket"] << "[" << "]" << "(" << ")"; - typeformats["bracket"].setForeground(QColor("Green")); - tokentypes["curlies"] << "{" << "}"; - typeformats["curlies"].setForeground(QColor(32,32,20)); - tokentypes["bool"] << "true" << "false"; - typeformats["bool"].setForeground(QColor("DarkRed")); - // Put each token into single QHash, mapped to it's format - QList<QString>::iterator ki; - QList<QString> toktypes = tokentypes.keys(); - for ( ki=toktypes.begin(); ki!=toktypes.end(); ++ki ) { - QString toktype = *ki; - QStringList::iterator it; - for ( it = tokentypes[toktype].begin(); it < tokentypes[toktype].end(); ++it) { - QString token = *it; - //std::cout << token.toStdString() << "\n"; - tokenFormats[ token ] = typeformats [ toktype ]; - } - } + tokentypes["_$comment"] << "_$comment"; // bit of a kludge here + tokentypes["_$quote"] << "_$quote"; + tokentypes["_$number"] << "_$number"; - quoteFormat.setForeground(Qt::darkMagenta); - commentFormat.setForeground(Qt::darkCyan); - errorFormat.setBackground(Qt::red); - numberFormat.setForeground(QColor("DarkRed")); + QString syntaxhighlight = Preferences::inst()->getValue("editor/syntaxhighlight").toString(); + this->assignFormatsToTokens(syntaxhighlight); + errorFormat.setBackground(Qt::red); errorState = false; errorPos = -1; lastErrorBlock = parent->begin(); @@ -198,10 +236,10 @@ void Highlighter::highlightError(int error_pos) errorPos = error_pos; QTextBlock err_block = document()->findBlock( errorPos ); - //std::cout << "error pos: " << error_pos << " doc len: " << document()->characterCount() << "\n"; + //PRINTB( "error pos: %i doc len: %i ", error_pos % document()->characterCount() ); while (err_block.text().remove(QRegExp("\\s+")).size()==0) { - //std::cout << "special case - errors at end of file w whitespace\n"; + //PRINT("special case - errors at end of file w whitespace"; err_block = err_block.previous(); errorPos = err_block.position()+err_block.length() - 2; } @@ -211,7 +249,7 @@ void Highlighter::highlightError(int error_pos) int block_last_pos = err_block.position() + err_block.length() - 1; if ( errorPos == block_last_pos ) { - //std::cout << "special case - errors at ends of certain blocks\n"; + //PRINT( "special case - errors at ends of certain blocks"); errorPos--; } err_block = document()->findBlock(errorPos); @@ -254,6 +292,18 @@ void Highlighter::highlightBlock(const QString &text) // << ", err:" << errorPos << "," << errorState // << ", text:'" << text.toStdString() << "'\n"; + // If desired, skip all highlighting .. except for error highlighting. + if (Preferences::inst()->getValue("editor/syntaxhighlight").toString()==QString("Off")) { + if (errorState) + setFormat( errorPos - block_first_pos, 1, errorFormat); + return; + } + + // bit of a kludge (for historical convenience) + QTextCharFormat "eFormat = tokenFormats["_$quote"]; + QTextCharFormat &commentFormat = tokenFormats["_$comment"]; + QTextCharFormat &numberFormat = tokenFormats["_$number"]; + // Split the block into chunks (tokens), based on whitespace, // and then highlight each token as appropriate QString newtext = text; @@ -265,7 +315,7 @@ void Highlighter::highlightBlock(const QString &text) for ( sh = splitHelpers.begin(); sh!=splitHelpers.end(); ++sh ) { newtext = newtext.replace( *sh, " " + *sh + " "); } - //std::cout << "\nnewtext: " << newtext.toStdString() << "\n"; + //PRINTB("\nnewtext: %s", newtext.toStdString() ); QStringList tokens = newtext.split(QRegExp("\\s")); int tokindex = 0; // tokindex helps w duplicate tokens in a single block bool numtest; @@ -273,14 +323,16 @@ void Highlighter::highlightBlock(const QString &text) if ( tokenFormats.contains( *token ) ) { tokindex = text.indexOf( *token, tokindex ); setFormat( tokindex, token->size(), tokenFormats[ *token ]); - //std::cout << "found tok '" << (*token).toStdString() << "' at " << tokindex << "\n"; + std::string tokprint = (*token).toStdString(); + //PRINTB("found tok '%s' at %i", tokprint % tokindex ); tokindex += token->size(); } else { (*token).toDouble( &numtest ); if ( numtest ) { tokindex = text.indexOf( *token, tokindex ); setFormat( tokindex, token->size(), numberFormat ); - //std::cout << "found num '" << (*token).toStdString() << "' at " << tokindex << "\n"; + std::string tokprint = (*token).toStdString(); + //PRINTB("found num '%s' at %i", tokprint % tokindex ); tokindex += token->size(); } } diff --git a/src/highlighter.h b/src/highlighter.h index b4ffae8..10f9b0a 100644 --- a/src/highlighter.h +++ b/src/highlighter.h @@ -10,11 +10,12 @@ class Highlighter : public QSyntaxHighlighter public: enum state_e {NORMAL=-1,QUOTE,COMMENT}; QHash<QString, QTextCharFormat> tokenFormats; - QTextCharFormat errorFormat, commentFormat, quoteFormat, numberFormat; + QTextCharFormat errorFormat; Highlighter(QTextDocument *parent); void highlightBlock(const QString &text); void highlightError(int error_pos); void unhighlightLastError(); + void assignFormatsToTokens(const QString &); private: QTextBlock lastErrorBlock; int errorPos; diff --git a/src/linalg.cc b/src/linalg.cc index 6799055..274a70a 100644 --- a/src/linalg.cc +++ b/src/linalg.cc @@ -14,6 +14,7 @@ */ BoundingBox operator*(const Transform3d &m, const BoundingBox &box) { + if (box.isEmpty()) return box; BoundingBox newbox; Vector3d boxvec[2] = { box.min(), box.max() }; for (int k=0;k<2;k++) { diff --git a/src/linearextrude.cc b/src/linearextrude.cc index c5d4529..1812504 100644 --- a/src/linearextrude.cc +++ b/src/linearextrude.cc @@ -32,7 +32,7 @@ #include "fileutils.h" #include "builtin.h" #include "PolySetEvaluator.h" -#include "openscad.h" // get_fragments_from_r() +#include "calc.h" #include "mathc99.h" #include <sstream> @@ -113,7 +113,7 @@ AbstractNode *LinearExtrudeModule::instantiate(const Context *ctx, const ModuleI if (slices.type() == Value::NUMBER) { node->slices = (int)slices.toDouble(); } else { - node->slices = (int)fmax(2, fabs(get_fragments_from_r(node->height, + node->slices = (int)fmax(2, fabs(Calc::get_fragments_from_r(node->height, node->fn, node->fs, node->fa) * node->twist / 360)); } node->has_twist = true; diff --git a/src/mainwin.cc b/src/mainwin.cc index 1ad8bc8..c2a7b7e 100644 --- a/src/mainwin.cc +++ b/src/mainwin.cc @@ -369,6 +369,8 @@ MainWindow::MainWindow(const QString &filename) this, SLOT(setFont(const QString&,uint))); connect(Preferences::inst(), SIGNAL(openCSGSettingsChanged()), this, SLOT(openCSGSettingsChanged())); + connect(Preferences::inst(), SIGNAL(syntaxHighlightChanged(const QString&)), + this, SLOT(setSyntaxHighlight(const QString&))); Preferences::inst()->apply(); // make sure it looks nice.. @@ -1899,6 +1901,12 @@ void MainWindow::setFont(const QString &family, uint size) editor->setFont(font); } +void MainWindow::setSyntaxHighlight(const QString &s) +{ + this->highlighter->assignFormatsToTokens( s ); + this->highlighter->rehighlight(); // slow on large files +} + void MainWindow::quit() { QCloseEvent ev; diff --git a/src/modcontext.cc b/src/modcontext.cc index 7941cf5..21b04fd 100644 --- a/src/modcontext.cc +++ b/src/modcontext.cc @@ -95,7 +95,12 @@ void ModuleContext::registerBuiltin() const AbstractFunction *ModuleContext::findLocalFunction(const std::string &name) const { if (this->functions_p && this->functions_p->find(name) != this->functions_p->end()) { - return this->functions_p->find(name)->second; + AbstractFunction *f = this->functions_p->find(name)->second; + if (!f->is_enabled()) { + PRINTB("WARNING: Experimental builtin function '%s' is not enabled.", name); + return NULL; + } + return f; } return NULL; } @@ -104,6 +109,10 @@ const AbstractModule *ModuleContext::findLocalModule(const std::string &name) co { if (this->modules_p && this->modules_p->find(name) != this->modules_p->end()) { AbstractModule *m = this->modules_p->find(name)->second; + if (!m->is_enabled()) { + PRINTB("WARNING: Experimental builtin module '%s' is not enabled.", name); + return NULL; + } std::string replacement = Builtins::instance()->isDeprecated(name); if (!replacement.empty()) { PRINTB("DEPRECATED: The %s() module will be removed in future releases. Use %s() instead.", name % replacement); diff --git a/src/module.h b/src/module.h index 8414706..81e5f10 100644 --- a/src/module.h +++ b/src/module.h @@ -13,6 +13,7 @@ #include "value.h" #include "typedefs.h" #include "localscope.h" +#include "feature.h" class ModuleInstantiation { @@ -60,8 +61,13 @@ public: class AbstractModule { +private: + const Feature *feature; public: + AbstractModule() : feature(NULL) {} + AbstractModule(const Feature& feature) : feature(&feature) {} virtual ~AbstractModule(); + virtual bool is_enabled() const { return (feature == NULL) || feature->is_enabled(); }; virtual class AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, const class EvalContext *evalctx = NULL) const; virtual std::string dump(const std::string &indent, const std::string &name) const; }; @@ -70,6 +76,7 @@ class Module : public AbstractModule { public: Module() { } + Module(const Feature& feature) : AbstractModule(feature) { } virtual ~Module(); virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx = NULL) const; diff --git a/src/openscad.cc b/src/openscad.cc index ab84235..0fa9f92 100644 --- a/src/openscad.cc +++ b/src/openscad.cc @@ -33,6 +33,7 @@ #include "builtin.h" #include "printutils.h" #include "handle_dep.h" +#include "feature.h" #include "parsersettings.h" #include "rendersettings.h" #include "PlatformUtils.h" @@ -107,10 +108,12 @@ static void help(const char *progname) PRINTB("Usage: %1% [ -o output_file [ -d deps_file ] ]\\\n" "%2%[ -m make_command ] [ -D var=val [..] ] \\\n" + "%2%[ --version ] [ --info ] \\\n" "%2%[ --camera=translatex,y,z,rotx,y,z,dist | \\\n" "%2% --camera=eyex,y,z,centerx,y,z ] \\\n" "%2%[ --imgsize=width,height ] [ --projection=(o)rtho|(p)ersp] \\\n" "%2%[ --render | --preview[=throwntogether] ] \\\n" + "%2%[ --enable=<feature> ] \\\n" "%2%filename\n", progname % (const char *)tabstr); exit(1); @@ -203,7 +206,7 @@ int cmdline(const char *deps_output_file, const std::string &filename, Camera &c #else const std::string application_path = boosty::stringy(boosty::absolute(boost::filesystem::path(argv[0]).parent_path())); #endif - parser_init(application_path, false); + parser_init(application_path); Tree tree; #ifdef ENABLE_CGAL CGALEvaluator cgalevaluator(tree); @@ -513,7 +516,7 @@ int gui(vector<string> &inputFiles, const fs::path &original_path, int argc, cha qexamplesdir = exdir.path(); } MainWindow::setExamplesDir(qexamplesdir); - parser_init(app_path.toLocal8Bit().constData(), true); + parser_init(app_path.toLocal8Bit().constData()); #ifdef Q_WS_MAC installAppleEventHandlers(); @@ -568,7 +571,6 @@ int main(int argc, char **argv) fs::path original_path = fs::current_path(); - const char *filename = NULL; const char *output_file = NULL; const char *deps_output_file = NULL; @@ -587,7 +589,8 @@ int main(int argc, char **argv) ("x,x", po::value<string>(), "dxf-file") ("d,d", po::value<string>(), "deps-file") ("m,m", po::value<string>(), "makefile") - ("D,D", po::value<vector<string> >(), "var=val"); + ("D,D", po::value<vector<string> >(), "var=val") + ("enable", po::value<vector<string> >(), "enable experimental features"); po::options_description hidden("Hidden options"); hidden.add_options() @@ -651,6 +654,11 @@ int main(int argc, char **argv) commandline_commands += ";\n"; } } + if (vm.count("enable")) { + BOOST_FOREACH(const string &feature, vm["enable"].as<vector<string> >()) { + Feature::enable_feature(feature); + } + } vector<string> inputFiles; if (vm.count("input-file")) { inputFiles = vm["input-file"].as<vector<string> >(); diff --git a/src/openscad.h b/src/openscad.h index dd379a9..0d146c1 100644 --- a/src/openscad.h +++ b/src/openscad.h @@ -28,7 +28,6 @@ #define OPENSCAD_H extern class FileModule *parse(const char *text, const char *path, int debug); -extern int get_fragments_from_r(double r, double fn, double fs, double fa); #include <string> extern std::string commandline_commands; diff --git a/src/parsersettings.cc b/src/parsersettings.cc index de1ff98..ba4a223 100644 --- a/src/parsersettings.cc +++ b/src/parsersettings.cc @@ -88,7 +88,7 @@ fs::path find_valid_path(const fs::path &sourcepath, return fs::path(); } -void parser_init(const std::string &applicationpath, bool isgui) +void parser_init(const std::string &applicationpath) { // Add paths from OPENSCADPATH before adding built-in paths const char *openscadpaths = getenv("OPENSCADPATH"); @@ -117,7 +117,8 @@ void parser_init(const std::string &applicationpath, bool isgui) fs::path tmpdir; #ifdef __APPLE__ // Libraries can be bundled on Mac. If not, fall back to development layout - if (isgui) { + bool isbundle = is_directory(libdir / ".." / "Resources"); + if (isbundle) { libdir /= "../Resources"; if (!is_directory(libdir / "libraries")) libdir /= "../../.."; } diff --git a/src/parsersettings.h b/src/parsersettings.h index 2aef85b..52172b6 100644 --- a/src/parsersettings.h +++ b/src/parsersettings.h @@ -6,7 +6,7 @@ extern int parser_error_pos; -void parser_init(const std::string &applicationpath, bool isgui); +void parser_init(const std::string &applicationpath); void add_librarydir(const std::string &libdir); fs::path search_libs(const fs::path &localpath); fs::path find_valid_path(const fs::path &sourcepath, diff --git a/src/primitives.cc b/src/primitives.cc index f1a4ba7..c9e1072 100644 --- a/src/primitives.cc +++ b/src/primitives.cc @@ -34,11 +34,16 @@ #include "printutils.h" #include "visitor.h" #include "context.h" +#include "calc.h" +#include "mathc99.h" #include <sstream> #include <assert.h> #include <boost/assign/std/vector.hpp> using namespace boost::assign; // bring 'operator+=()' into scope +#include <boost/math/special_functions/fpclassify.hpp> +using boost::math::isinf; + #define F_MINIMUM 0.01 enum primitive_type_e { @@ -275,17 +280,6 @@ AbstractNode *PrimitiveModule::instantiate(const Context *ctx, const ModuleInsta return node; } -/*! - Returns the number of subdivision of a whole circle, given radius and - the three special variables $fn, $fs and $fa -*/ -int get_fragments_from_r(double r, double fn, double fs, double fa) -{ - if (r < GRID_FINE) return 3; - if (fn > 0.0) return (int)(fn >= 3 ? fn : 3); - return (int)ceil(fmax(fmin(360.0 / fa, r*2*M_PI / fs), 5)); -} - struct point2d { double x, y; }; @@ -303,8 +297,9 @@ PolySet *PrimitiveNode::evaluate_polyset(class PolySetEvaluator *) const { PolySet *p = new PolySet(); - if (this->type == CUBE && this->x > 0 && this->y > 0 && this->z > 0) - { + if (this->type == CUBE && + this->x > 0 && this->y > 0 && this->z > 0 && + !isinf(this->x) > 0 && !isinf(this->y) > 0 && !isinf(this->z) > 0) { double x1, x2, y1, y2, z1, z2; if (this->center) { x1 = -this->x/2; @@ -357,14 +352,14 @@ PolySet *PrimitiveNode::evaluate_polyset(class PolySetEvaluator *) const p->append_vertex(x1, y2, z2); } - if (this->type == SPHERE && this->r1 > 0) + if (this->type == SPHERE && this->r1 > 0 && !isinf(this->r1)) { struct ring_s { point2d *points; double z; }; - int fragments = get_fragments_from_r(r1, fn, fs, fa); + int fragments = Calc::get_fragments_from_r(r1, fn, fs, fa); int rings = (fragments+1)/2; // Uncomment the following three lines to enable experimental sphere tesselation // if (rings % 2 == 0) rings++; // To ensure that the middle ring is at phi == 0 degrees @@ -425,9 +420,10 @@ sphere_next_r2: } if (this->type == CYLINDER && - this->h > 0 && this->r1 >=0 && this->r2 >= 0 && (this->r1 > 0 || this->r2 > 0)) - { - int fragments = get_fragments_from_r(fmax(this->r1, this->r2), this->fn, this->fs, this->fa); + this->h > 0 && !isinf(this->h) && + this->r1 >=0 && this->r2 >= 0 && (this->r1 + this->r2) > 0 && + !isinf(this->r1) && !isinf(this->r2)) { + int fragments = Calc::get_fragments_from_r(fmax(this->r1, this->r2), this->fn, this->fs, this->fa); double z1, z2; if (this->center) { @@ -490,12 +486,18 @@ sphere_next_r2: for (size_t i=0; i<this->faces.toVector().size(); i++) { p->append_poly(); - for (size_t j=0; j<this->faces.toVector()[i].toVector().size(); j++) { - size_t pt = this->faces.toVector()[i].toVector()[j].toDouble(); + const Value::VectorType &vec = this->faces.toVector()[i].toVector(); + for (size_t j=0; j<vec.size(); j++) { + size_t pt = vec[j].toDouble(); if (pt < this->points.toVector().size()) { double px, py, pz; - if (this->points.toVector()[pt].getVec3(px, py, pz)) - p->insert_vertex(px, py, pz); + if (!this->points.toVector()[pt].getVec3(px, py, pz) || + isinf(px) || isinf(py) || isinf(pz)) { + PRINTB("ERROR: Unable to convert point at index %d to a vec3 of numbers", j); + delete p; + return NULL; + } + p->insert_vertex(px, py, pz); } } } @@ -525,7 +527,7 @@ sphere_next_r2: if (this->type == CIRCLE) { - int fragments = get_fragments_from_r(this->r1, this->fn, this->fs, this->fa); + int fragments = Calc::get_fragments_from_r(this->r1, this->fn, this->fs, this->fa); p->is2d = true; p->append_poly(); @@ -542,7 +544,8 @@ sphere_next_r2: for (size_t i=0; i<this->points.toVector().size(); i++) { double x,y; - if (!this->points.toVector()[i].getVec2(x, y)) { + if (!this->points.toVector()[i].getVec2(x, y) || + isinf(x) || isinf(y)) { PRINTB("ERROR: Unable to convert point at index %d to a vec2 of numbers", i); delete p; return NULL; diff --git a/src/value.cc b/src/value.cc index c8a88c6..3a6540f 100644 --- a/src/value.cc +++ b/src/value.cc @@ -39,6 +39,10 @@ /*Unicode support for string lengths and array accesses*/ #include <glib.h> +#include <boost/math/special_functions/fpclassify.hpp> +using boost::math::isnan; +using boost::math::isinf; + std::ostream &operator<<(std::ostream &stream, const Filename &filename) { fs::path fnpath = fs::path( (std::string)filename ); @@ -642,7 +646,11 @@ void Value::RangeType::normalize() { } uint32_t Value::RangeType::nbsteps() const { - if (begin_val == end_val) { + if (isnan(step_val) || isinf(begin_val) || (isinf(end_val))) { + return std::numeric_limits<uint32_t>::max(); + } + + if ((begin_val == end_val) || isinf(step_val)) { return 0; } |