diff --git a/xs/MANIFEST b/xs/MANIFEST index b3f0d64f8e..754b9f025e 100644 --- a/xs/MANIFEST +++ b/xs/MANIFEST @@ -60,6 +60,7 @@ t/11_clipper.t t/12_extrusionpathcollection.t t/13_polylinecollection.t t/14_geometry.t +t/15_config.t xsp/Clipper.xsp xsp/Config.xsp xsp/ExPolygon.xsp diff --git a/xs/src/Config.cpp b/xs/src/Config.cpp index 7646440124..16bd56cf4e 100644 --- a/xs/src/Config.cpp +++ b/xs/src/Config.cpp @@ -5,6 +5,13 @@ namespace Slic3r { t_optiondef_map Options = _build_optiondef_map(); FullConfig DefaultConfig = _build_default_config(); +ConfigOptionDef* +get_config_option_def(const t_config_option_key opt_key) { + t_optiondef_map::iterator it = Options.find(opt_key); + if (it == Options.end()) return NULL; + return &it->second; +} + void ConfigBase::apply(ConfigBase &other, bool ignore_nonexistent) { // get list of option keys to apply @@ -19,25 +26,112 @@ ConfigBase::apply(ConfigBase &other, bool ignore_nonexistent) { } } +std::string +ConfigBase::serialize(const t_config_option_key opt_key) { + ConfigOption* opt = this->option(opt_key); + assert(opt != NULL); + return opt->serialize(); +} + +void +ConfigBase::set_deserialize(const t_config_option_key opt_key, std::string str) { + ConfigOption* opt = this->option(opt_key); + assert(opt != NULL); + opt->deserialize(str); +} + +float +ConfigBase::get_abs_value(const t_config_option_key opt_key) { + // get option definition + ConfigOptionDef* def = get_config_option_def(opt_key); + assert(def != NULL); + assert(def->type == coFloatOrPercent); + + // get stored option value + ConfigOptionFloatOrPercent* opt = dynamic_cast(this->option(opt_key)); + assert(opt != NULL); + + // compute absolute value + if (opt->percent) { + ConfigOptionFloat* optbase = dynamic_cast(this->option(def->ratio_over)); + assert(optbase != NULL); + return optbase->value * opt->value / 100; + } else { + return opt->value; + } +} + #ifdef SLIC3RXS SV* ConfigBase::get(t_config_option_key opt_key) { ConfigOption* opt = this->option(opt_key); if (opt == NULL) return &PL_sv_undef; - if (ConfigOptionFloat* v = dynamic_cast(opt)) { - return newSVnv(v->value); - } else if (ConfigOptionInt* v = dynamic_cast(opt)) { - return newSViv(v->value); + if (ConfigOptionFloat* optv = dynamic_cast(opt)) { + return newSVnv(optv->value); + } else if (ConfigOptionInt* optv = dynamic_cast(opt)) { + return newSViv(optv->value); + } else if (ConfigOptionString* optv = dynamic_cast(opt)) { + // we don't serialize() because that would escape newlines + return newSVpvn(optv->value.c_str(), optv->value.length()); + } else if (ConfigOptionPoint* optv = dynamic_cast(opt)) { + return optv->point.to_SV_pureperl(); } else { - throw "Unknown option value type"; + std::string serialized = opt->serialize(); + return newSVpvn(serialized.c_str(), serialized.length()); + } +} + +void +ConfigBase::set(t_config_option_key opt_key, SV* value) { + ConfigOption* opt = this->option(opt_key, true); + assert(opt != NULL); + + if (ConfigOptionFloat* optv = dynamic_cast(opt)) { + optv->value = SvNV(value); + } else if (ConfigOptionInt* optv = dynamic_cast(opt)) { + optv->value = SvIV(value); + } else if (ConfigOptionString* optv = dynamic_cast(opt)) { + optv->value = std::string(SvPV_nolen(value), SvCUR(value)); + } else if (ConfigOptionPoint* optv = dynamic_cast(opt)) { + optv->point.from_SV(value); + } else { + opt->deserialize( std::string(SvPV_nolen(value)) ); } } #endif +DynamicConfig::~DynamicConfig () { + for (t_options_map::iterator it = this->options.begin(); it != this->options.end(); ++it) { + ConfigOption* opt = it->second; + if (opt != NULL) delete opt; + } +} + ConfigOption* -DynamicConfig::option(const t_config_option_key opt_key) { +DynamicConfig::option(const t_config_option_key opt_key, bool create) { t_options_map::iterator it = this->options.find(opt_key); - if (it == this->options.end()) return NULL; + if (it == this->options.end()) { + if (create) { + ConfigOption* opt; + if (Options[opt_key].type == coFloat) { + opt = new ConfigOptionFloat (); + } else if (Options[opt_key].type == coInt) { + opt = new ConfigOptionInt (); + } else if (Options[opt_key].type == coString) { + opt = new ConfigOptionString (); + } else if (Options[opt_key].type == coFloatOrPercent) { + opt = new ConfigOptionFloatOrPercent (); + } else if (Options[opt_key].type == coPoint) { + opt = new ConfigOptionPoint (); + } else { + throw "Unknown option type"; + } + this->options[opt_key] = opt; + return opt; + } else { + return NULL; + } + } return it->second; } diff --git a/xs/src/Config.hpp b/xs/src/Config.hpp index 275a33a3ef..0bb483dc69 100644 --- a/xs/src/Config.hpp +++ b/xs/src/Config.hpp @@ -3,8 +3,13 @@ #include #include +#include +#include +#include +#include ; #include #include +#include "Point.hpp" namespace Slic3r { @@ -14,27 +19,79 @@ typedef std::vector t_config_option_keys; class ConfigOption { public: virtual ~ConfigOption() {}; + virtual std::string serialize() = 0; + virtual void deserialize(std::string str) = 0; }; class ConfigOptionFloat : public ConfigOption { public: float value; + ConfigOptionFloat() : value(0) {}; + operator float() const { return this->value; }; + + std::string serialize() { + std::ostringstream ss; + ss << this->value; + return ss.str(); + }; + + void deserialize(std::string str) { + this->value = ::atof(str.c_str()); + }; }; class ConfigOptionInt : public ConfigOption { public: int value; + ConfigOptionInt() : value(0) {}; + operator int() const { return this->value; }; + + std::string serialize() { + std::ostringstream ss; + ss << this->value; + return ss.str(); + }; + + void deserialize(std::string str) { + this->value = ::atoi(str.c_str()); + }; }; class ConfigOptionString : public ConfigOption { public: std::string value; + ConfigOptionString() : value("") {}; + operator std::string() const { return this->value; }; + + std::string serialize() { + std::string str = this->value; + + // s/\R/\\n/g + size_t pos = 0; + while ((pos = str.find("\n", pos)) != std::string::npos || (pos = str.find("\r", pos)) != std::string::npos) { + str.replace(pos, 1, "\\n"); + pos += 2; // length of "\\n" + } + + return str; + }; + + void deserialize(std::string str) { + // s/\\n/\n/g + size_t pos = 0; + while ((pos = str.find("\\n", pos)) != std::string::npos) { + str.replace(pos, 2, "\n"); + pos += 1; // length of "\n" + } + + this->value = str; + }; }; class ConfigOptionFloatOrPercent : public ConfigOption @@ -42,6 +99,44 @@ class ConfigOptionFloatOrPercent : public ConfigOption public: float value; bool percent; + ConfigOptionFloatOrPercent() : value(0), percent(false) {}; + + std::string serialize() { + std::ostringstream ss; + ss << this->value; + std::string s(ss.str()); + if (this->percent) s += "%"; + return s; + }; + + void deserialize(std::string str) { + if (str.find_first_of("%") != std::string::npos) { + sscanf(str.c_str(), "%f%%", &this->value); + this->percent = true; + } else { + this->value = ::atof(str.c_str()); + this->percent = false; + } + }; +}; + +class ConfigOptionPoint : public ConfigOption +{ + public: + Pointf point; + ConfigOptionPoint() : point(Pointf(0,0)) {}; + + std::string serialize() { + std::ostringstream ss; + ss << this->point.x; + ss << ","; + ss << this->point.y; + return ss.str(); + }; + + void deserialize(std::string str) { + sscanf(str.c_str(), "%f%*1[,x]%f", &this->point.x, &this->point.y); + }; }; enum ConfigOptionType { @@ -49,6 +144,7 @@ enum ConfigOptionType { coInt, coString, coFloatOrPercent, + coPoint, }; class ConfigOptionDef @@ -57,30 +153,41 @@ class ConfigOptionDef ConfigOptionType type; std::string label; std::string tooltip; + std::string ratio_over; }; typedef std::map t_optiondef_map; +ConfigOptionDef* get_config_option_def(const t_config_option_key opt_key); + class ConfigBase { public: - virtual ConfigOption* option(const t_config_option_key opt_key) = 0; + virtual ConfigOption* option(const t_config_option_key opt_key, bool create = false) = 0; virtual void keys(t_config_option_keys *keys) = 0; void apply(ConfigBase &other, bool ignore_nonexistent = false); + std::string serialize(const t_config_option_key opt_key); + void set_deserialize(const t_config_option_key opt_key, std::string str); + float get_abs_value(const t_config_option_key opt_key); #ifdef SLIC3RXS SV* get(t_config_option_key opt_key); + void set(t_config_option_key opt_key, SV* value); #endif }; class DynamicConfig : public ConfigBase { public: - ConfigOption* option(const t_config_option_key opt_key); + DynamicConfig() {}; + ~DynamicConfig(); + ConfigOption* option(const t_config_option_key opt_key, bool create = false); void keys(t_config_option_keys *keys); bool has(const t_config_option_key opt_key) const; private: + DynamicConfig(const DynamicConfig& other); // we disable this by making it private and unimplemented + DynamicConfig& operator= (const DynamicConfig& other); // we disable this by making it private and unimplemented typedef std::map t_options_map; t_options_map options; }; @@ -96,10 +203,19 @@ class FullConfig : public StaticConfig public: ConfigOptionFloat layer_height; ConfigOptionFloatOrPercent first_layer_height; + ConfigOptionInt perimeters; + ConfigOptionString extrusion_axis; + ConfigOptionPoint print_center; + ConfigOptionString notes; - ConfigOption* option(const t_config_option_key opt_key) { + ConfigOption* option(const t_config_option_key opt_key, bool create = false) { + assert(!create); // can't create options in StaticConfig if (opt_key == "layer_height") return &this->layer_height; if (opt_key == "first_layer_height") return &this->first_layer_height; + if (opt_key == "perimeters") return &this->perimeters; + if (opt_key == "extrusion_axis") return &this->extrusion_axis; + if (opt_key == "print_center") return &this->print_center; + if (opt_key == "notes") return &this->notes; return NULL; }; }; @@ -111,6 +227,18 @@ static t_optiondef_map _build_optiondef_map () { Options["layer_height"].tooltip = "This setting controls the height (and thus the total number) of the slices/layers. Thinner layers give better accuracy but take more time to print."; Options["first_layer_height"].type = coFloatOrPercent; + Options["first_layer_height"].ratio_over = "layer_height"; + + Options["perimeters"].type = coInt; + Options["perimeters"].label = "Perimeters (minimum)"; + Options["perimeters"].tooltip = "This option sets the number of perimeters to generate for each layer. Note that Slic3r may increase this number automatically when it detects sloping surfaces which benefit from a higher number of perimeters if the Extra Perimeters option is enabled."; + + Options["extrusion_axis"].type = coString; + + Options["print_center"].type = coPoint; + + Options["notes"].type = coString; + return Options; } @@ -120,6 +248,10 @@ static FullConfig _build_default_config () { defconf.layer_height.value = 0.4; defconf.first_layer_height.value = 0.35; defconf.first_layer_height.percent = false; + defconf.perimeters.value = 3; + defconf.extrusion_axis.value = "E"; + defconf.print_center.point = Pointf(100,100); + defconf.notes.value = ""; return defconf; } diff --git a/xs/src/Point.cpp b/xs/src/Point.cpp index 13e7d23617..e816925fd1 100644 --- a/xs/src/Point.cpp +++ b/xs/src/Point.cpp @@ -171,6 +171,23 @@ Point::from_SV_check(SV* point_sv) this->from_SV(point_sv); } } + +SV* +Pointf::to_SV_pureperl() const { + AV* av = newAV(); + av_fill(av, 1); + av_store(av, 0, newSVnv(this->x)); + av_store(av, 1, newSVnv(this->y)); + return newRV_noinc((SV*)av); +} + +void +Pointf::from_SV(SV* point_sv) +{ + AV* point_av = (AV*)SvRV(point_sv); + this->x = SvNV(*av_fetch(point_av, 0, 0)); + this->y = SvNV(*av_fetch(point_av, 1, 0)); +} #endif } diff --git a/xs/src/Point.hpp b/xs/src/Point.hpp index d89a12cb8a..202ec557d7 100644 --- a/xs/src/Point.hpp +++ b/xs/src/Point.hpp @@ -42,6 +42,19 @@ class Point #endif }; +class Pointf +{ + public: + float x; + float y; + explicit Pointf(float _x = 0, float _y = 0): x(_x), y(_y) {}; + + #ifdef SLIC3RXS + void from_SV(SV* point_sv); + SV* to_SV_pureperl() const; + #endif +}; + } #endif diff --git a/xs/t/15_config.t b/xs/t/15_config.t new file mode 100644 index 0000000000..9ff4a6dac0 --- /dev/null +++ b/xs/t/15_config.t @@ -0,0 +1,45 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Slic3r::XS; +use Test::More tests => 16; + +{ + my $config = Slic3r::Config->new; + + $config->set('layer_height', 0.3); + ok abs($config->get('layer_height') - 0.3) < 1e-4, 'set/get float'; + is $config->serialize('layer_height'), '0.3', 'serialize float'; + + $config->set('perimeters', 2); + is $config->get('perimeters'), 2, 'set/get int'; + is $config->serialize('perimeters'), '2', 'serialize int'; + + $config->set('extrusion_axis', 'A'); + is $config->get('extrusion_axis'), 'A', 'set/get string'; + is $config->serialize('extrusion_axis'), 'A', 'serialize string'; + + $config->set('notes', "foo\nbar"); + is $config->get('notes'), "foo\nbar", 'set/get string with newline'; + is $config->serialize('notes'), 'foo\nbar', 'serialize string with newline'; + $config->set_deserialize('notes', 'bar\nbaz'); + is $config->get('notes'), "bar\nbaz", 'deserialize string with newline'; + + $config->set('first_layer_height', 0.3); + ok abs($config->get('first_layer_height') - 0.3) < 1e-4, 'set/get absolute floatOrPercent'; + is $config->serialize('first_layer_height'), '0.3', 'serialize absolute floatOrPercent'; + + $config->set('first_layer_height', '50%'); + ok abs($config->get_abs_value('first_layer_height') - 0.15) < 1e-4, 'set/get relative floatOrPercent'; + is $config->serialize('first_layer_height'), '50%', 'serialize relative floatOrPercent'; + + $config->set('print_center', [50,80]); + is_deeply $config->get('print_center'), [50,80], 'set/get point'; + is $config->serialize('print_center'), '50,80', 'serialize point'; + $config->set_deserialize('print_center', '20,10'); + is_deeply $config->get('print_center'), [20,10], 'deserialize point'; +} + +__END__ diff --git a/xs/xsp/Config.xsp b/xs/xsp/Config.xsp index 1bd9d9db68..b0e4595309 100644 --- a/xs/xsp/Config.xsp +++ b/xs/xsp/Config.xsp @@ -9,6 +9,10 @@ DynamicConfig(); ~DynamicConfig(); SV* get(t_config_option_key opt_key); + void set(t_config_option_key opt_key, SV* value); + void set_deserialize(t_config_option_key opt_key, std::string str); + std::string serialize(t_config_option_key opt_key); + float get_abs_value(t_config_option_key opt_key); %{ %}