From 6443df394b7754509ddfd781df7758ba912ebcbd Mon Sep 17 00:00:00 2001 From: Torsten Paul Date: Thu, 26 Dec 2013 05:01:40 +0100 Subject: Add concat() builtin (fixes #201). This allows to concatenate multiple vectors and single elements into a single result vector. diff --git a/.gitignore b/.gitignore index 59bac49..040fe53 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,6 @@ parser_yacc.h /tmp /OpenSCAD.app */#*# +/nbproject /openscad +/tests/openscad_nogui diff --git a/src/func.cc b/src/func.cc index 4587f72..23292df 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)); Builtins::init("lookup", new BuiltinFunction(&builtin_lookup)); Builtins::init("search", new BuiltinFunction(&builtin_search)); Builtins::init("version", new BuiltinFunction(&builtin_version)); diff --git a/testdata/scad/functions/concat-tests.scad b/testdata/scad/functions/concat-tests.scad new file mode 100644 index 0000000..0bcb903 --- /dev/null +++ b/testdata/scad/functions/concat-tests.scad @@ -0,0 +1,53 @@ +u = undef; + +echo("--- empty"); +echo(concat()); +echo(concat([])); +echo(concat([], [])); +echo(concat([], [], [])); + +echo("--- single elements"); +echo(concat(u)); +echo(concat(true)); +echo(concat(3)); +echo(concat("abc")); +echo(concat([0:1:10])); + +echo("--- single vectors"); +echo(concat([1, 2, 3])); +echo(concat([[1, 2, 3]])); +echo(concat([[[1, 2, 3]]])); +echo(concat([[[1, 2, [3, 4], 5]]])); + +echo("--- multiple elements"); +echo(concat(3, 3)); +echo(concat(1, 2, 3)); +echo(concat(1, 2, 3, 4, 5)); +echo(concat(1, "text", false, [1:0.5:3])); + +echo("--- vector / element"); +echo(concat([3, 4], u)); +echo(concat([3, 4, 5], 6)); +echo(concat([3, 4, 5, 6], true)); +echo(concat([3, 4, "5", 6], "test")); +echo(concat([3, 4, true, 6], [4:1:3])); + +echo("--- element / vector"); +echo(concat(3, [])); +echo(concat(3, [3, 4])); +echo(concat(true, [3, [4]])); +echo(concat("9", [1, 2, 3])); +echo(concat([6:2:9], [3, [4]])); + +echo("--- vector / vector"); +echo(concat([], [3, 4])); +echo(concat([[]], [3, 4])); +echo(concat([[2, 4]], [3, 4])); +echo(concat([5, 6], ["d", [3, 4]])); +echo(concat([[1, 0, 0], [2, 0, 0]], [3, 0, 0])); +echo(concat([[1, 0, 0], [2, 0, 0]], [[3, 0, 0]])); +echo(concat([[1, 0, 0], [2, 0, 0], [3, 0, 0]], [[4, 4, 4], [5, 5, 5]])); + +echo("--- recursive function"); +function r(i) = i > 0 ? concat(r(i - 1), [[i, i * i]]) : []; +echo(r(10)); diff --git a/tests/regression/echotest/concat-tests-expected.echo b/tests/regression/echotest/concat-tests-expected.echo new file mode 100644 index 0000000..58b41d0 --- /dev/null +++ b/tests/regression/echotest/concat-tests-expected.echo @@ -0,0 +1,43 @@ +ECHO: "--- empty" +ECHO: [] +ECHO: [] +ECHO: [] +ECHO: [] +ECHO: "--- single elements" +ECHO: [undef] +ECHO: [true] +ECHO: [3] +ECHO: ["abc"] +ECHO: [[0 : 1 : 10]] +ECHO: "--- single vectors" +ECHO: [1, 2, 3] +ECHO: [[1, 2, 3]] +ECHO: [[[1, 2, 3]]] +ECHO: [[[1, 2, [3, 4], 5]]] +ECHO: "--- multiple elements" +ECHO: [3, 3] +ECHO: [1, 2, 3] +ECHO: [1, 2, 3, 4, 5] +ECHO: [1, "text", false, [1 : 0.5 : 3]] +ECHO: "--- vector / element" +ECHO: [3, 4, undef] +ECHO: [3, 4, 5, 6] +ECHO: [3, 4, 5, 6, true] +ECHO: [3, 4, "5", 6, "test"] +ECHO: [3, 4, true, 6, [4 : 1 : 3]] +ECHO: "--- element / vector" +ECHO: [3] +ECHO: [3, 3, 4] +ECHO: [true, 3, [4]] +ECHO: ["9", 1, 2, 3] +ECHO: [[6 : 2 : 9], 3, [4]] +ECHO: "--- vector / vector" +ECHO: [3, 4] +ECHO: [[], 3, 4] +ECHO: [[2, 4], 3, 4] +ECHO: [5, 6, "d", [3, 4]] +ECHO: [[1, 0, 0], [2, 0, 0], 3, 0, 0] +ECHO: [[1, 0, 0], [2, 0, 0], [3, 0, 0]] +ECHO: [[1, 0, 0], [2, 0, 0], [3, 0, 0], [4, 4, 4], [5, 5, 5]] +ECHO: "--- recursive function" +ECHO: [[1, 1], [2, 4], [3, 9], [4, 16], [5, 25], [6, 36], [7, 49], [8, 64], [9, 81], [10, 100]] -- cgit v0.10.1 From 72865e17dae7f51c60e0db54c1aa070f8125f273 Mon Sep 17 00:00:00 2001 From: Torsten Paul Date: Thu, 2 Jan 2014 23:27:52 +0100 Subject: Add feature registry. diff --git a/openscad.pro b/openscad.pro index 6d8282a..fa5c8b4 100644 --- a/openscad.pro +++ b/openscad.pro @@ -221,6 +221,7 @@ HEADERS += src/typedefs.h \ src/highlighter.h \ src/localscope.h \ src/module.h \ + src/feature.h \ src/node.h \ src/csgnode.h \ src/linearextrudenode.h \ @@ -279,6 +280,7 @@ SOURCES += src/version_check.cc \ src/func.cc \ src/localscope.cc \ src/module.cc \ + src/feature.cc \ src/node.cc \ src/context.cc \ src/modcontext.cc \ diff --git a/src/feature.cc b/src/feature.cc new file mode 100644 index 0000000..5beece1 --- /dev/null +++ b/src/feature.cc @@ -0,0 +1,84 @@ +#include +#include +#include +#include + +#include "feature.h" + +/** + * Feature registration map for later lookup. This must be initialized + * before the static feature instances as those register with this map. + */ +std::map Feature::feature_map; + +/* + * 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"); + +Feature::Feature(std::string name) : enabled_cmdline(false), enabled_options(false), name(name) +{ + feature_map[name] = this; +} + +Feature::~Feature() +{ +} + +const std::string& Feature::get_name() const +{ + return name; +} + +void Feature::set_enable_cmdline() +{ + enabled_cmdline = true; +} + +void Feature::set_enable_options(bool status) +{ + enabled_options = status; +} + +bool Feature::is_enabled() const +{ + if (enabled_cmdline) { + return true; + } + return enabled_options; +} + +bool operator ==(const Feature& lhs, const Feature& rhs) +{ + return lhs.get_name() == rhs.get_name(); +} + +bool operator !=(const Feature& lhs, const Feature& rhs) +{ + return !(lhs == rhs); +} + +void Feature::enable_feature(std::string feature_name) +{ + map_t::iterator it = feature_map.find(feature_name); + if (it != feature_map.end()) { + (*it).second->set_enable_cmdline(); + } +} + +void Feature::enable_feature(std::string feature_name, bool status) +{ + map_t::iterator it = feature_map.find(feature_name); + if (it != feature_map.end()) { + (*it).second->set_enable_options(status); + } +} + +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..3c97edc --- /dev/null +++ b/src/feature.h @@ -0,0 +1,47 @@ +#ifndef FEATURE_H_ +#define FEATURE_H_ + +#include +#include +#include +#include + +class Feature { +private: + /** + * Set to true in case the matching feature was given as commandline + * argument. + */ + bool enabled_cmdline; + /** + * Set from the GUI options. This will not be set in case the GUI is + * not started at all. + */ + bool enabled_options; + + std::string name; + + typedef std::map map_t; + static map_t feature_map; + + Feature(std::string name); + virtual ~Feature(); + virtual void set_enable_cmdline(); + virtual void set_enable_options(bool status); + +public: + static const Feature ExperimentalConcatFunction; + + const std::string& get_name() const; + + bool is_enabled() const; + + friend bool operator ==(const Feature& lhs, const Feature& rhs); + friend bool operator !=(const Feature& lhs, const Feature& rhs); + + static void dump_features(); + static void enable_feature(std::string feature_name); + static void enable_feature(std::string feature_name, bool status); +}; + +#endif diff --git a/src/openscad.cc b/src/openscad.cc index ab84235..574e87b 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" @@ -587,7 +588,8 @@ int main(int argc, char **argv) ("x,x", po::value(), "dxf-file") ("d,d", po::value(), "deps-file") ("m,m", po::value(), "makefile") - ("D,D", po::value >(), "var=val"); + ("D,D", po::value >(), "var=val") + ("enable-feature", po::value >(), "enable experimental features"); po::options_description hidden("Hidden options"); hidden.add_options() @@ -651,6 +653,11 @@ int main(int argc, char **argv) commandline_commands += ";\n"; } } + if (vm.count("enable-feature")) { + BOOST_FOREACH(const string &feature, vm["enable-feature"].as >()) { + Feature::enable_feature(feature); + } + } vector inputFiles; if (vm.count("input-file")) { inputFiles = vm["input-file"].as >(); -- cgit v0.10.1 From 7f3659907358f57e912f76291f8c9500b3032fbd Mon Sep 17 00:00:00 2001 From: Torsten Paul Date: Thu, 2 Jan 2014 22:41:21 +0100 Subject: Integrate feature registry with modules. In case a module is attached to a disabled feature, the module lookup will give a warning message and ignore that (builtin) module. diff --git a/src/modcontext.cc b/src/modcontext.cc index 7941cf5..7b8cb07 100644 --- a/src/modcontext.cc +++ b/src/modcontext.cc @@ -104,6 +104,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; -- cgit v0.10.1 From 09f8e80a7d49188ff18195e40e3c02a2d8768257 Mon Sep 17 00:00:00 2001 From: Torsten Paul Date: Thu, 2 Jan 2014 22:50:04 +0100 Subject: Integrate feature registry with functions. In case a function is attached to a disabled feature, the function lookup will give a warning message and ignore that (builtin) function. 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 #include + 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/modcontext.cc b/src/modcontext.cc index 7b8cb07..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; } -- cgit v0.10.1 From b7b2681c74d509109782c42ea0deb3ddcf1ad5e2 Mon Sep 17 00:00:00 2001 From: Torsten Paul Date: Thu, 2 Jan 2014 23:29:48 +0100 Subject: Add GUI Settings page to enable/disable features. diff --git a/icons/license.txt b/icons/license.txt new file mode 100644 index 0000000..97e6931 --- /dev/null +++ b/icons/license.txt @@ -0,0 +1,3 @@ +Taken from http://tango.freedesktop.org/Tango_Icon_Library, version 0.8.90 / public domain: + +- prefsFeatures.png (converted from preferences-system.svg) diff --git a/icons/prefsFeatures.png b/icons/prefsFeatures.png new file mode 100644 index 0000000..2fb0a55 Binary files /dev/null and b/icons/prefsFeatures.png differ diff --git a/openscad.qrc b/openscad.qrc index 28b6a72..6fd7e47 100644 --- a/openscad.qrc +++ b/openscad.qrc @@ -1,5 +1,6 @@ + icons/prefsFeatures.png icons/stopbutton.png icons/prefsAdvanced.png icons/prefs3DView.png diff --git a/src/Preferences.cc b/src/Preferences.cc index 0f3115e..6ec9c9d 100644 --- a/src/Preferences.cc +++ b/src/Preferences.cc @@ -35,6 +35,7 @@ #include "AutoUpdater.h" #ifdef ENABLE_CGAL #include "CGALCache.h" +#include "feature.h" #endif Preferences *Preferences::instance = NULL; @@ -86,13 +87,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 +141,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()]); @@ -151,20 +153,41 @@ Preferences::~Preferences() } void +Preferences::addPrefPage(QActionGroup *group, QAction *action, QWidget *widget) +{ + group->addAction(action); + prefPages[action] = widget; +} +void Preferences::actionTriggered(QAction *action) { - if (action == this->prefsAction3DView) { - this->stackedWidget->setCurrentWidget(this->page3DView); - } - else if (action == this->prefsActionEditor) { - this->stackedWidget->setCurrentWidget(this->pageEditor); - } - else if (action == this->prefsActionUpdate) { - this->stackedWidget->setCurrentWidget(this->pageUpdate); - } - else if (action == this->prefsActionAdvanced) { - this->stackedWidget->setCurrentWidget(this->pageAdvanced); + this->stackedWidget->setCurrentWidget(prefPages[action]); +} + +void +Preferences::setupFeaturesPage() +{ + int row = 0; + for (Feature::const_iterator it = Feature::begin();it != Feature::end();it++) { + gridLayoutExperimentalFeatures->addItem(new QSpacerItem(1, 8, QSizePolicy::Expanding, QSizePolicy::Fixed), row, 1, 1, 1, Qt::AlignCenter); + row++; + const Feature *feature = (*it); + QCheckBox *cb = new QCheckBox(feature->get_name().c_str(), pageFeatures); + QFont bold_font(cb->font()); + bold_font.setBold(true); + cb->setFont(bold_font); + gridLayoutExperimentalFeatures->addWidget(cb, row, 0, 1, 2, Qt::AlignLeading); + row++; + QLabel *l = new QLabel(feature->get_description().c_str(), 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() diff --git a/src/Preferences.h b/src/Preferences.h index 4656793..addb7e5 100644 --- a/src/Preferences.h +++ b/src/Preferences.h @@ -44,9 +44,12 @@ private: void keyPressEvent(QKeyEvent *e); void updateGUI(); void removeDefaultSettings(); + void setupFeaturesPage(); + void addPrefPage(QActionGroup *group, QAction *action, QWidget *widget); QSettings::SettingsMap defaultmap; QHash > colorschemes; + QHash prefPages; static Preferences *instance; }; diff --git a/src/Preferences.ui b/src/Preferences.ui index d67db6a..78a1cf6 100644 --- a/src/Preferences.ui +++ b/src/Preferences.ui @@ -6,8 +6,8 @@ 0 0 - 473 - 320 + 823 + 433 @@ -27,7 +27,7 @@ - 2 + 3 @@ -136,7 +136,7 @@ - Monaco + DejaVu Sans 12 @@ -323,6 +323,78 @@ + + + + 0 + + + + + + + + 75 + true + + + + Features + + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + Qt::ScrollBarAlwaysOff + + + true + + + + + 0 + 0 + 803 + 311 + + + + + + + 8 + + + + + + + Qt::Vertical + + + + 20 + 282 + + + + + + + + + + + + @@ -465,6 +537,7 @@ + @@ -515,6 +588,21 @@ Update + + + true + + + + :/icons/prefsFeatures.png:/icons/prefsFeatures.png + + + Features + + + Enable/Disable experimental features + + diff --git a/src/feature.cc b/src/feature.cc index 5beece1..7f36547 100644 --- a/src/feature.cc +++ b/src/feature.cc @@ -4,23 +4,26 @@ #include #include "feature.h" +#include "printutils.h" /** - * Feature registration map for later lookup. This must be initialized + * Feature registration map/list for later lookup. This must be initialized * before the static feature instances as those register with this map. */ -std::map Feature::feature_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"); +const Feature Feature::ExperimentalConcatFunction("experimental/concat-function", "Enable the concat() function."); -Feature::Feature(std::string name) : enabled_cmdline(false), enabled_options(false), name(name) +Feature::Feature(const std::string name, const std::string description) : enabled_cmdline(false), enabled_options(false), name(name), description(description) { feature_map[name] = this; + feature_list.push_back(this); } Feature::~Feature() @@ -32,6 +35,11 @@ const std::string& Feature::get_name() const return name; } +const std::string& Feature::get_description() const +{ + return description; +} + void Feature::set_enable_cmdline() { enabled_cmdline = true; @@ -49,15 +57,10 @@ bool Feature::is_enabled() const } return enabled_options; } - -bool operator ==(const Feature& lhs, const Feature& rhs) -{ - return lhs.get_name() == rhs.get_name(); -} -bool operator !=(const Feature& lhs, const Feature& rhs) +void Feature::enable(bool status) { - return !(lhs == rhs); + set_enable_options(status); } void Feature::enable_feature(std::string feature_name) @@ -65,6 +68,8 @@ void Feature::enable_feature(std::string feature_name) map_t::iterator it = feature_map.find(feature_name); if (it != feature_map.end()) { (*it).second->set_enable_cmdline(); + } else { + PRINTB("WARNING: Ignoring request to enable unknown feature '%s'.", feature_name); } } @@ -76,6 +81,16 @@ void Feature::enable_feature(std::string feature_name, bool status) } } +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++) { diff --git a/src/feature.h b/src/feature.h index 3c97edc..77e283c 100644 --- a/src/feature.h +++ b/src/feature.h @@ -5,8 +5,13 @@ #include #include #include +#include class Feature { +public: + typedef std::vector list_t; + typedef list_t::iterator iterator; + private: /** * Set to true in case the matching feature was given as commandline @@ -19,12 +24,14 @@ private: */ bool enabled_options; - std::string name; + const std::string name; + const std::string description; typedef std::map map_t; static map_t feature_map; - - Feature(std::string name); + static list_t feature_list; + + Feature(std::string name, std::string description); virtual ~Feature(); virtual void set_enable_cmdline(); virtual void set_enable_options(bool status); @@ -33,11 +40,13 @@ public: static const Feature ExperimentalConcatFunction; const std::string& get_name() const; + const std::string& get_description() const; bool is_enabled() const; - - friend bool operator ==(const Feature& lhs, const Feature& rhs); - friend bool operator !=(const Feature& lhs, const Feature& rhs); + void enable(bool status); + + static iterator begin(); + static iterator end(); static void dump_features(); static void enable_feature(std::string feature_name); -- cgit v0.10.1 From bef4ea305329e57984feed758ad11b48cfe94613 Mon Sep 17 00:00:00 2001 From: Torsten Paul Date: Thu, 2 Jan 2014 23:31:36 +0100 Subject: Handle synchronization between Qt settings / GUI and Feature objects. diff --git a/src/Preferences.cc b/src/Preferences.cc index 6ec9c9d..9eef43a 100644 --- a/src/Preferences.cc +++ b/src/Preferences.cc @@ -40,6 +40,9 @@ Preferences *Preferences::instance = NULL; +const char * Preferences::featurePropertyName = "FeatureProperty"; +Q_DECLARE_METATYPE(Feature *); + Preferences::Preferences(QWidget *parent) : QMainWindow(parent) { setupUi(this); @@ -152,32 +155,101 @@ 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) { 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; + } + QVariant v = sender->property(featurePropertyName); + if (!v.isValid()) { + return; + } + Feature *feature = v.value(); + feature->enable(state); + QSettings settings; + settings.setValue(feature->get_name().c_str(), state); +} + +/** + * Setup feature GUI and synchronize the Qt settings with the feature values. + * + * In case a feature was enabled on the commandline this will have precedence + * and cause the checkbox in the settings GUI to be not editable. + * Otherwise the value from the Qt settings is pushed into the feature state + * and the checkbox is initialized accordingly. + */ void Preferences::setupFeaturesPage() { int row = 0; - for (Feature::const_iterator it = Feature::begin();it != Feature::end();it++) { + for (Feature::iterator it = Feature::begin();it != Feature::end();it++) { + Feature * feature = (*it); + // setup default with the current value coming from commandline + this->defaultmap[feature->get_name().c_str()] = 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++; - const Feature *feature = (*it); - QCheckBox *cb = new QCheckBox(feature->get_name().c_str(), pageFeatures); + + std::string text(feature->get_name()); + QCheckBox *cb = new QCheckBox(text.c_str(), pageFeatures); QFont bold_font(cb->font()); bold_font.setBold(true); cb->setFont(bold_font); + if (feature->is_enabled()) { + // if enabled from command line, that has priority + cb->setChecked(true); + cb->setEnabled(false); + std::string text_cl = text + " (enabled on commandline)"; + cb->setText(text_cl.c_str()); + } else { + // synchronize Qt settings with the feature settings + bool value = getValue(feature->get_name().c_str()).toBool(); + feature->enable(value); + cb->setChecked(value); + } + cb->setProperty(featurePropertyName, QVariant::fromValue(feature)); + connect(cb, SIGNAL(toggled(bool)), this, SLOT(featuresCheckBoxToggled(bool))); gridLayoutExperimentalFeatures->addWidget(cb, row, 0, 1, 2, Qt::AlignLeading); row++; + QLabel *l = new QLabel(feature->get_description().c_str(), pageFeatures); l->setTextFormat(Qt::RichText); gridLayoutExperimentalFeatures->addWidget(l, row, 1, 1, 1, Qt::AlignLeading); @@ -332,7 +404,6 @@ QVariant Preferences::getValue(const QString &key) const void Preferences::updateGUI() { - QSettings settings; QList found = this->colorSchemeChooser->findItems(getValue("3dview/colorscheme").toString(), Qt::MatchExactly); diff --git a/src/Preferences.h b/src/Preferences.h index addb7e5..6310622 100644 --- a/src/Preferences.h +++ b/src/Preferences.h @@ -21,6 +21,7 @@ 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 &); @@ -52,6 +53,7 @@ private: QHash prefPages; static Preferences *instance; + static const char *featurePropertyName; }; #endif -- cgit v0.10.1 From 9caa2dcfa5fb6e56f88fdcf575e74c3e821d9c52 Mon Sep 17 00:00:00 2001 From: Torsten Paul Date: Thu, 2 Jan 2014 23:18:15 +0100 Subject: Add concat() function to list of experimental features. diff --git a/src/func.cc b/src/func.cc index 23292df..878bc1c 100644 --- a/src/func.cc +++ b/src/func.cc @@ -622,7 +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)); + 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)); -- cgit v0.10.1 From 3d5b844679871131e299fd335f998e994edae31f Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Fri, 3 Jan 2014 02:08:50 -0500 Subject: Keep cmd-line and GUI separate in terms of preferences, handle experimental tests separately, minor cleanups diff --git a/src/Preferences.cc b/src/Preferences.cc index 9eef43a..591ccf6 100644 --- a/src/Preferences.cc +++ b/src/Preferences.cc @@ -33,9 +33,9 @@ #include #include "PolySetCache.h" #include "AutoUpdater.h" +#include "feature.h" #ifdef ENABLE_CGAL #include "CGALCache.h" -#include "feature.h" #endif Preferences *Preferences::instance = NULL; @@ -204,7 +204,7 @@ void Preferences::featuresCheckBoxToggled(bool state) Feature *feature = v.value(); feature->enable(state); QSettings settings; - settings.setValue(feature->get_name().c_str(), state); + settings.setValue(QString("feature/%1").arg(QString::fromStdString(feature->get_name())), state); } /** @@ -220,37 +220,29 @@ Preferences::setupFeaturesPage() { int row = 0; for (Feature::iterator it = Feature::begin();it != Feature::end();it++) { - Feature * feature = (*it); - // setup default with the current value coming from commandline - this->defaultmap[feature->get_name().c_str()] = false; + 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++; - std::string text(feature->get_name()); - QCheckBox *cb = new QCheckBox(text.c_str(), pageFeatures); + QCheckBox *cb = new QCheckBox(QString::fromStdString(feature->get_name()), pageFeatures); QFont bold_font(cb->font()); bold_font.setBold(true); cb->setFont(bold_font); - if (feature->is_enabled()) { - // if enabled from command line, that has priority - cb->setChecked(true); - cb->setEnabled(false); - std::string text_cl = text + " (enabled on commandline)"; - cb->setText(text_cl.c_str()); - } else { - // synchronize Qt settings with the feature settings - bool value = getValue(feature->get_name().c_str()).toBool(); - feature->enable(value); - cb->setChecked(value); - } + // synchronize Qt settings with the feature settings + bool value = getValue(featurekey).toBool(); + feature->enable(value); + cb->setChecked(value); cb->setProperty(featurePropertyName, QVariant::fromValue(feature)); connect(cb, SIGNAL(toggled(bool)), this, SLOT(featuresCheckBoxToggled(bool))); gridLayoutExperimentalFeatures->addWidget(cb, row, 0, 1, 2, Qt::AlignLeading); row++; - QLabel *l = new QLabel(feature->get_description().c_str(), pageFeatures); + QLabel *l = new QLabel(QString::fromStdString(feature->get_description()), pageFeatures); l->setTextFormat(Qt::RichText); gridLayoutExperimentalFeatures->addWidget(l, row, 1, 1, 1, Qt::AlignLeading); row++; diff --git a/src/Preferences.h b/src/Preferences.h index 6310622..ef8835a 100644 --- a/src/Preferences.h +++ b/src/Preferences.h @@ -21,7 +21,7 @@ public: public slots: void actionTriggered(class QAction *); - void featuresCheckBoxToggled(bool); + void featuresCheckBoxToggled(bool); void on_colorSchemeChooser_itemSelectionChanged(); void on_fontChooser_activated(const QString &); void on_fontSize_editTextChanged(const QString &); @@ -45,15 +45,15 @@ private: void keyPressEvent(QKeyEvent *e); void updateGUI(); void removeDefaultSettings(); - void setupFeaturesPage(); - void addPrefPage(QActionGroup *group, QAction *action, QWidget *widget); + void setupFeaturesPage(); + void addPrefPage(QActionGroup *group, QAction *action, QWidget *widget); QSettings::SettingsMap defaultmap; QHash > colorschemes; - QHash prefPages; + QHash prefPages; static Preferences *instance; - static const char *featurePropertyName; + static const char *featurePropertyName; }; #endif diff --git a/src/feature.cc b/src/feature.cc index 7f36547..80d7887 100644 --- a/src/feature.cc +++ b/src/feature.cc @@ -18,11 +18,12 @@ Feature::list_t Feature::feature_list; * argument to enable the option and for saving the option value in GUI * context. */ -const Feature Feature::ExperimentalConcatFunction("experimental/concat-function", "Enable the concat() function."); +const Feature Feature::ExperimentalConcatFunction("concat", "Enable the concat() function."); -Feature::Feature(const std::string name, const std::string description) : enabled_cmdline(false), enabled_options(false), name(name), description(description) +Feature::Feature(const std::string &name, const std::string &description) + : enabled(false), name(name), description(description) { - feature_map[name] = this; + feature_map[name] = this; feature_list.push_back(this); } @@ -30,57 +31,36 @@ Feature::~Feature() { } -const std::string& Feature::get_name() const +const std::string &Feature::get_name() const { - return name; + return name; } -const std::string& Feature::get_description() const +const std::string &Feature::get_description() const { - return description; + return description; } -void Feature::set_enable_cmdline() -{ - enabled_cmdline = true; -} - -void Feature::set_enable_options(bool status) -{ - enabled_options = status; -} - bool Feature::is_enabled() const { - if (enabled_cmdline) { - return true; - } - return enabled_options; + return enabled; } void Feature::enable(bool status) { - set_enable_options(status); + enabled = status; } -void Feature::enable_feature(std::string feature_name) +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->set_enable_cmdline(); + it->second->enable(status); } else { PRINTB("WARNING: Ignoring request to enable unknown feature '%s'.", feature_name); } } -void Feature::enable_feature(std::string feature_name, bool status) -{ - map_t::iterator it = feature_map.find(feature_name); - if (it != feature_map.end()) { - (*it).second->set_enable_options(status); - } -} - Feature::iterator Feature::begin() { return feature_list.begin(); @@ -94,6 +74,6 @@ Feature::iterator Feature::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; + std::cout << "Feature('" << it->first << "') = " << (it->second->is_enabled() ? "enabled" : "disabled") << std::endl; } } diff --git a/src/feature.h b/src/feature.h index 77e283c..20b4f16 100644 --- a/src/feature.h +++ b/src/feature.h @@ -7,50 +7,38 @@ #include #include -class Feature { +class Feature +{ public: - typedef std::vector list_t; - typedef list_t::iterator iterator; + typedef std::vector list_t; + typedef list_t::iterator iterator; -private: - /** - * Set to true in case the matching feature was given as commandline - * argument. - */ - bool enabled_cmdline; - /** - * Set from the GUI options. This will not be set in case the GUI is - * not started at all. - */ - bool enabled_options; - - const std::string name; - const std::string description; + static const Feature ExperimentalConcatFunction; - typedef std::map map_t; - static map_t feature_map; - static list_t feature_list; + const std::string& get_name() const; + const std::string& get_description() const; - Feature(std::string name, std::string description); - virtual ~Feature(); - virtual void set_enable_cmdline(); - virtual void set_enable_options(bool status); + bool is_enabled() const; + void enable(bool status); -public: - static const Feature ExperimentalConcatFunction; + static iterator begin(); + static iterator end(); - const std::string& get_name() const; - const std::string& get_description() const; - - bool is_enabled() const; - void enable(bool status); + static void dump_features(); + static void enable_feature(const std::string &feature_name, bool status = true); - static iterator begin(); - static iterator end(); +private: + bool enabled; + + const std::string name; + const std::string description; + + typedef std::map map_t; + static map_t feature_map; + static list_t feature_list; - static void dump_features(); - static void enable_feature(std::string feature_name); - static void enable_feature(std::string feature_name, bool status); + Feature(const std::string &name, const std::string &description); + virtual ~Feature(); }; #endif diff --git a/src/openscad.cc b/src/openscad.cc index 574e87b..16a6e89 100644 --- a/src/openscad.cc +++ b/src/openscad.cc @@ -112,6 +112,7 @@ static void help(const char *progname) "%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= \\\n" "%2%filename\n", progname % (const char *)tabstr); exit(1); @@ -589,7 +590,7 @@ int main(int argc, char **argv) ("d,d", po::value(), "deps-file") ("m,m", po::value(), "makefile") ("D,D", po::value >(), "var=val") - ("enable-feature", po::value >(), "enable experimental features"); + ("enable", po::value >(), "enable experimental features"); po::options_description hidden("Hidden options"); hidden.add_options() @@ -653,8 +654,8 @@ int main(int argc, char **argv) commandline_commands += ";\n"; } } - if (vm.count("enable-feature")) { - BOOST_FOREACH(const string &feature, vm["enable-feature"].as >()) { + if (vm.count("enable")) { + BOOST_FOREACH(const string &feature, vm["enable"].as >()) { Feature::enable_feature(feature); } } diff --git a/testdata/scad/experimental/concat-tests.scad b/testdata/scad/experimental/concat-tests.scad new file mode 100644 index 0000000..0bcb903 --- /dev/null +++ b/testdata/scad/experimental/concat-tests.scad @@ -0,0 +1,53 @@ +u = undef; + +echo("--- empty"); +echo(concat()); +echo(concat([])); +echo(concat([], [])); +echo(concat([], [], [])); + +echo("--- single elements"); +echo(concat(u)); +echo(concat(true)); +echo(concat(3)); +echo(concat("abc")); +echo(concat([0:1:10])); + +echo("--- single vectors"); +echo(concat([1, 2, 3])); +echo(concat([[1, 2, 3]])); +echo(concat([[[1, 2, 3]]])); +echo(concat([[[1, 2, [3, 4], 5]]])); + +echo("--- multiple elements"); +echo(concat(3, 3)); +echo(concat(1, 2, 3)); +echo(concat(1, 2, 3, 4, 5)); +echo(concat(1, "text", false, [1:0.5:3])); + +echo("--- vector / element"); +echo(concat([3, 4], u)); +echo(concat([3, 4, 5], 6)); +echo(concat([3, 4, 5, 6], true)); +echo(concat([3, 4, "5", 6], "test")); +echo(concat([3, 4, true, 6], [4:1:3])); + +echo("--- element / vector"); +echo(concat(3, [])); +echo(concat(3, [3, 4])); +echo(concat(true, [3, [4]])); +echo(concat("9", [1, 2, 3])); +echo(concat([6:2:9], [3, [4]])); + +echo("--- vector / vector"); +echo(concat([], [3, 4])); +echo(concat([[]], [3, 4])); +echo(concat([[2, 4]], [3, 4])); +echo(concat([5, 6], ["d", [3, 4]])); +echo(concat([[1, 0, 0], [2, 0, 0]], [3, 0, 0])); +echo(concat([[1, 0, 0], [2, 0, 0]], [[3, 0, 0]])); +echo(concat([[1, 0, 0], [2, 0, 0], [3, 0, 0]], [[4, 4, 4], [5, 5, 5]])); + +echo("--- recursive function"); +function r(i) = i > 0 ? concat(r(i - 1), [[i, i * i]]) : []; +echo(r(10)); diff --git a/testdata/scad/functions/concat-tests.scad b/testdata/scad/functions/concat-tests.scad deleted file mode 100644 index 0bcb903..0000000 --- a/testdata/scad/functions/concat-tests.scad +++ /dev/null @@ -1,53 +0,0 @@ -u = undef; - -echo("--- empty"); -echo(concat()); -echo(concat([])); -echo(concat([], [])); -echo(concat([], [], [])); - -echo("--- single elements"); -echo(concat(u)); -echo(concat(true)); -echo(concat(3)); -echo(concat("abc")); -echo(concat([0:1:10])); - -echo("--- single vectors"); -echo(concat([1, 2, 3])); -echo(concat([[1, 2, 3]])); -echo(concat([[[1, 2, 3]]])); -echo(concat([[[1, 2, [3, 4], 5]]])); - -echo("--- multiple elements"); -echo(concat(3, 3)); -echo(concat(1, 2, 3)); -echo(concat(1, 2, 3, 4, 5)); -echo(concat(1, "text", false, [1:0.5:3])); - -echo("--- vector / element"); -echo(concat([3, 4], u)); -echo(concat([3, 4, 5], 6)); -echo(concat([3, 4, 5, 6], true)); -echo(concat([3, 4, "5", 6], "test")); -echo(concat([3, 4, true, 6], [4:1:3])); - -echo("--- element / vector"); -echo(concat(3, [])); -echo(concat(3, [3, 4])); -echo(concat(true, [3, [4]])); -echo(concat("9", [1, 2, 3])); -echo(concat([6:2:9], [3, [4]])); - -echo("--- vector / vector"); -echo(concat([], [3, 4])); -echo(concat([[]], [3, 4])); -echo(concat([[2, 4]], [3, 4])); -echo(concat([5, 6], ["d", [3, 4]])); -echo(concat([[1, 0, 0], [2, 0, 0]], [3, 0, 0])); -echo(concat([[1, 0, 0], [2, 0, 0]], [[3, 0, 0]])); -echo(concat([[1, 0, 0], [2, 0, 0], [3, 0, 0]], [[4, 4, 4], [5, 5, 5]])); - -echo("--- recursive function"); -function r(i) = i > 0 ? concat(r(i - 1), [[i, i * i]]) : []; -echo(r(10)); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 70e56c6..d2d9e68 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -516,6 +516,7 @@ set(CORE_SOURCES ../src/context.cc ../src/modcontext.cc ../src/evalcontext.cc + ../src/feature.cc ../src/csgterm.cc ../src/csgtermnormalizer.cc ../src/polyset.cc @@ -974,6 +975,11 @@ add_cmdline_test(throwntogethertest EXE ${OPENSCAD_BINPATH} ARGS --preview=throw # with anything. It's self-contained and returns != 0 on error add_cmdline_test(cgalstlsanitytest EXE ${CMAKE_SOURCE_DIR}/cgalstlsanitytest SUFFIX txt ARGS ${OPENSCAD_BINPATH} FILES ${CGALSTLSANITYTEST_FILES}) +# Add experimental tests + +add_cmdline_test(echotest EXE ${OPENSCAD_BINPATH} ARGS --enable=concat -o SUFFIX echo FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/experimental/concat-tests.scad) + + # Tests using the actual OpenSCAD binary # non-ASCII filenames -- cgit v0.10.1