summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CGALEvaluator.cc9
-rw-r--r--src/CSGTermEvaluator.cc12
-rw-r--r--src/MainWindow.h1
-rw-r--r--src/PlatformUtils-posix.cc13
-rw-r--r--src/PolySetCGALEvaluator.cc4
-rw-r--r--src/Preferences.cc127
-rw-r--r--src/Preferences.h7
-rw-r--r--src/Preferences.ui151
-rw-r--r--src/calc.cc40
-rw-r--r--src/calc.h8
-rw-r--r--src/csgtermnormalizer.cc24
-rw-r--r--src/csgtermnormalizer.h1
-rw-r--r--src/dxfdata.cc40
-rw-r--r--src/evalcontext.cc2
-rw-r--r--src/feature.cc79
-rw-r--r--src/feature.h44
-rw-r--r--src/func.cc19
-rw-r--r--src/function.h9
-rw-r--r--src/highlighter.cc150
-rw-r--r--src/highlighter.h3
-rw-r--r--src/linalg.cc1
-rw-r--r--src/linearextrude.cc4
-rw-r--r--src/mainwin.cc8
-rw-r--r--src/modcontext.cc11
-rw-r--r--src/module.h7
-rw-r--r--src/openscad.cc16
-rw-r--r--src/openscad.h1
-rw-r--r--src/parsersettings.cc5
-rw-r--r--src/parsersettings.h2
-rw-r--r--src/primitives.cc51
-rw-r--r--src/value.cc10
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 &quoteFormat = 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;
}
contact: Jan Huwald // Impressum