diff --git a/src/admesh/shared.cpp b/src/admesh/shared.cpp index 91bb82e006..c8c17ccd59 100644 --- a/src/admesh/shared.cpp +++ b/src/admesh/shared.cpp @@ -143,7 +143,7 @@ stl_generate_shared_vertices(stl_file *stl) { } void -stl_write_off(stl_file *stl, char *file) { +stl_write_off(stl_file *stl, const char *file) { int i; FILE *fp; char *error_msg; @@ -179,7 +179,7 @@ stl_write_off(stl_file *stl, char *file) { } void -stl_write_vrml(stl_file *stl, char *file) { +stl_write_vrml(stl_file *stl, const char *file) { int i; FILE *fp; char *error_msg; @@ -236,7 +236,7 @@ stl_write_vrml(stl_file *stl, char *file) { fclose(fp); } -void stl_write_obj (stl_file *stl, char *file) { +void stl_write_obj (stl_file *stl, const char *file) { int i; FILE* fp; diff --git a/src/admesh/stl.h b/src/admesh/stl.h index 9c71f00f66..afff3deac1 100644 --- a/src/admesh/stl.h +++ b/src/admesh/stl.h @@ -177,10 +177,10 @@ extern void stl_transform(stl_file *stl, const Eigen::Transformvertex[1] - facet->vertex[0]).cross(facet->vertex[2] - facet->vertex[0]); } diff --git a/src/admesh/stl_io.cpp b/src/admesh/stl_io.cpp index 81e29d2860..71e434cbcd 100644 --- a/src/admesh/stl_io.cpp +++ b/src/admesh/stl_io.cpp @@ -365,7 +365,7 @@ stl_write_quad_object(stl_file *stl, char *file) { } void -stl_write_dxf(stl_file *stl, char *file, char *label) { +stl_write_dxf(stl_file *stl, const char *file, char *label) { int i; FILE *fp; char *error_msg; diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index 6f22ba89e8..e7984e6cc2 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -190,6 +190,110 @@ bool unescape_strings_cstyle(const std::string &str, std::vector &o } } +std::vector ConfigOptionDef::cli_args() const +{ + std::string cli = this->cli.substr(0, this->cli.find("=")); + boost::trim_right_if(cli, boost::is_any_of("!")); + std::vector args; + boost::split(args, cli, boost::is_any_of("|")); + return args; +} + +std::ostream& ConfigDef::print_cli_help(std::ostream& out, bool show_defaults) const +{ + // prepare a function for wrapping text + auto wrap = [](std::string text, size_t line_length) -> std::string { + std::istringstream words(text); + std::ostringstream wrapped; + std::string word; + + if (words >> word) { + wrapped << word; + size_t space_left = line_length - word.length(); + while (words >> word) { + if (space_left < word.length() + 1) { + wrapped << '\n' << word; + space_left = line_length - word.length(); + } else { + wrapped << ' ' << word; + space_left -= word.length() + 1; + } + } + } + return wrapped.str(); + }; + + // get the unique categories + std::set categories; + for (const auto& opt : this->options) { + const ConfigOptionDef& def = opt.second; + categories.insert(def.category); + } + + for (auto category : categories) { + if (category != "") { + out << category << ":" << std::endl; + } else if (categories.size() > 1) { + out << "Misc options:" << std::endl; + } + + for (const auto& opt : this->options) { + const ConfigOptionDef& def = opt.second; + if (def.category != category) continue; + + if (!def.cli.empty()) { + // get all possible variations: --foo, --foobar, -f... + auto cli_args = def.cli_args(); + for (auto& arg : cli_args) { + arg.insert(0, (arg.size() == 1) ? "-" : "--"); + if (def.type == coFloat || def.type == coInt || def.type == coFloatOrPercent + || def.type == coFloats || def.type == coInts) { + arg += " N"; + } else if (def.type == coPoint) { + arg += " X,Y"; + } else if (def.type == coPoint3) { + arg += " X,Y,Z"; + } else if (def.type == coString || def.type == coStrings) { + arg += " ABCD"; + } + } + + // left: command line options + const std::string cli = boost::algorithm::join(cli_args, ", "); + out << " " << std::left << std::setw(20) << cli; + + // right: option description + std::string descr = def.tooltip; + if (show_defaults && def.default_value != nullptr && def.type != coBool + && (def.type != coString || !def.default_value->serialize().empty())) { + descr += " ("; + if (!def.sidetext.empty()) { + descr += def.sidetext + ", "; + } else if (!def.enum_values.empty()) { + descr += boost::algorithm::join(def.enum_values, ", ") + "; "; + } + descr += "default: " + def.default_value->serialize() + ")"; + } + + // wrap lines of description + descr = wrap(descr, 80); + std::vector lines; + boost::split(lines, descr, boost::is_any_of("\n")); + + // if command line options are too long, print description in new line + for (size_t i = 0; i < lines.size(); ++i) { + if (i == 0 && cli.size() > 19) + out << std::endl; + if (i > 0 || cli.size() > 19) + out << std::string(21, ' '); + out << lines[i] << std::endl; + } + } + } + } + return out; +} + void ConfigBase::apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent) { // loop through options and apply them @@ -508,50 +612,53 @@ ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool cre // Let the parent decide what to do if the opt_key is not defined by this->def(). return nullptr; ConfigOption *opt = nullptr; - switch (optdef->type) { - case coFloat: opt = new ConfigOptionFloat(); break; - case coFloats: opt = new ConfigOptionFloats(); break; - case coInt: opt = new ConfigOptionInt(); break; - case coInts: opt = new ConfigOptionInts(); break; - case coString: opt = new ConfigOptionString(); break; - case coStrings: opt = new ConfigOptionStrings(); break; - case coPercent: opt = new ConfigOptionPercent(); break; - case coPercents: opt = new ConfigOptionPercents(); break; - case coFloatOrPercent: opt = new ConfigOptionFloatOrPercent(); break; - case coPoint: opt = new ConfigOptionPoint(); break; - case coPoints: opt = new ConfigOptionPoints(); break; - case coBool: opt = new ConfigOptionBool(); break; - case coBools: opt = new ConfigOptionBools(); break; - case coEnum: opt = new ConfigOptionEnumGeneric(optdef->enum_keys_map); break; - default: throw std::runtime_error(std::string("Unknown option type for option ") + opt_key); + if (optdef->default_value != nullptr) { + opt = (optdef->default_value->type() == coEnum) ? + // Special case: For a DynamicConfig, convert a templated enum to a generic enum. + new ConfigOptionEnumGeneric(optdef->enum_keys_map, optdef->default_value->getInt()) : + optdef->default_value->clone(); + } else { + switch (optdef->type) { + case coFloat: opt = new ConfigOptionFloat(); break; + case coFloats: opt = new ConfigOptionFloats(); break; + case coInt: opt = new ConfigOptionInt(); break; + case coInts: opt = new ConfigOptionInts(); break; + case coString: opt = new ConfigOptionString(); break; + case coStrings: opt = new ConfigOptionStrings(); break; + case coPercent: opt = new ConfigOptionPercent(); break; + case coPercents: opt = new ConfigOptionPercents(); break; + case coFloatOrPercent: opt = new ConfigOptionFloatOrPercent(); break; + case coPoint: opt = new ConfigOptionPoint(); break; + case coPoints: opt = new ConfigOptionPoints(); break; + case coPoint3: opt = new ConfigOptionPoint3(); break; + // case coPoint3s: opt = new ConfigOptionPoint3s(); break; + case coBool: opt = new ConfigOptionBool(); break; + case coBools: opt = new ConfigOptionBools(); break; + case coEnum: opt = new ConfigOptionEnumGeneric(optdef->enum_keys_map); break; + default: throw std::runtime_error(std::string("Unknown option type for option ") + opt_key); + } } this->options[opt_key] = opt; return opt; } -void DynamicConfig::read_cli(const std::vector &tokens, t_config_option_keys* extra) +void DynamicConfig::read_cli(const std::vector &tokens, t_config_option_keys* extra, t_config_option_keys* keys) { std::vector args; // push a bogus executable name (argv[0]) args.emplace_back(const_cast("")); for (size_t i = 0; i < tokens.size(); ++ i) args.emplace_back(const_cast(tokens[i].c_str())); - this->read_cli(int(args.size()), &args[0], extra); + this->read_cli(int(args.size()), &args[0], extra, keys); } -bool DynamicConfig::read_cli(int argc, char** argv, t_config_option_keys* extra) +bool DynamicConfig::read_cli(int argc, char** argv, t_config_option_keys* extra, t_config_option_keys* keys) { // cache the CLI option => opt_key mapping std::map opts; - for (const auto &oit : this->def()->options) { - std::string cli = oit.second.cli; - cli = cli.substr(0, cli.find("=")); - boost::trim_right_if(cli, boost::is_any_of("!")); - std::vector tokens; - boost::split(tokens, cli, boost::is_any_of("|")); - for (const std::string &t : tokens) + for (const auto &oit : this->def()->options) + for (auto t : oit.second.cli_args()) opts[t] = oit.first; - } bool parse_options = true; for (int i = 1; i < argc; ++ i) { @@ -611,6 +718,10 @@ bool DynamicConfig::read_cli(int argc, char** argv, t_config_option_keys* extra) } // Store the option value. const bool existing = this->has(opt_key); + if (keys != nullptr && !existing) { + // Save the order of detected keys. + keys->push_back(opt_key); + } ConfigOption *opt_base = this->option(opt_key, true); ConfigOptionVectorBase *opt_vector = opt_base->is_vector() ? static_cast(opt_base) : nullptr; if (opt_vector) { diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index c521f1cbb9..6fb8cb0c12 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -27,6 +27,24 @@ extern std::string escape_strings_cstyle(const std::vector &strs); extern bool unescape_string_cstyle(const std::string &str, std::string &out); extern bool unescape_strings_cstyle(const std::string &str, std::vector &out); +/// Specialization of std::exception to indicate that an unknown config option has been encountered. +class UnknownOptionException : public std::runtime_error { +public: + UnknownOptionException() : + std::runtime_error("Unknown option exception") {} + UnknownOptionException(const std::string &opt_key) : + std::runtime_error(std::string("Unknown option exception: ") + opt_key) {} +}; + +/// Indicate that the ConfigBase derived class does not provide config definition (the method def() returns null). +class NoDefinitionException : public std::runtime_error +{ +public: + NoDefinitionException() : + std::runtime_error("No definition exception") {} + NoDefinitionException(const std::string &opt_key) : + std::runtime_error(std::string("No definition exception: ") + opt_key) {} +}; // Type of a configuration value. enum ConfigOptionType { @@ -54,12 +72,14 @@ enum ConfigOptionType { coPoint = 6, // vector of 2d points (Point2f). Currently used for the definition of the print bed and for the extruder offsets. coPoints = coPoint + coVectorType, + coPoint3 = 7, +// coPoint3s = coPoint3 + coVectorType, // single boolean value - coBool = 7, + coBool = 8, // vector of boolean values coBools = coBool + coVectorType, // a generic enum - coEnum = 8, + coEnum = 9, }; enum ConfigOptionMode { @@ -718,6 +738,39 @@ public: } }; + +class ConfigOptionPoint3 : public ConfigOptionSingle +{ +public: + ConfigOptionPoint3() : ConfigOptionSingle(Vec3d(0,0,0)) {} + explicit ConfigOptionPoint3(const Vec3d &value) : ConfigOptionSingle(value) {} + + static ConfigOptionType static_type() { return coPoint3; } + ConfigOptionType type() const override { return static_type(); } + ConfigOption* clone() const override { return new ConfigOptionPoint3(*this); } + ConfigOptionPoint3& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + bool operator==(const ConfigOptionPoint3 &rhs) const { return this->value == rhs.value; } + + std::string serialize() const override + { + std::ostringstream ss; + ss << this->value(0); + ss << ","; + ss << this->value(1); + ss << ","; + ss << this->value(2); + return ss.str(); + } + + bool deserialize(const std::string &str, bool append = false) override + { + UNUSED(append); + char dummy; + return sscanf(str.data(), " %lf , %lf , %lf %c", &this->value(0), &this->value(1), &this->value(2), &dummy) == 2 || + sscanf(str.data(), " %lf x %lf x %lf %c", &this->value(0), &this->value(1), &this->value(2), &dummy) == 2; + } +}; + class ConfigOptionBool : public ConfigOptionSingle { public: @@ -893,6 +946,7 @@ class ConfigOptionEnumGeneric : public ConfigOptionInt { public: ConfigOptionEnumGeneric(const t_config_enum_values* keys_map = nullptr) : keys_map(keys_map) {} + explicit ConfigOptionEnumGeneric(const t_config_enum_values* keys_map, int value) : ConfigOptionInt(value), keys_map(keys_map) {} const t_config_enum_values* keys_map; @@ -1010,6 +1064,9 @@ public: return true; return false; } + + /// Returns the alternative CLI arguments for the given option. + std::vector cli_args() const; }; // Map from a config option name to its definition. @@ -1044,6 +1101,9 @@ public: return out; } + /// Iterate through all of the CLI options and write them to a stream. + std::ostream& print_cli_help(std::ostream& out, bool show_defaults) const; + protected: ConfigOptionDef* add(const t_config_option_key &opt_key, ConfigOptionType type) { ConfigOptionDef* opt = &this->options[opt_key]; @@ -1089,12 +1149,24 @@ public: TYPE* option(const t_config_option_key &opt_key, bool create = false) { ConfigOption *opt = this->optptr(opt_key, create); -// assert(opt == nullptr || opt->type() == TYPE::static_type()); return (opt == nullptr || opt->type() != TYPE::static_type()) ? nullptr : static_cast(opt); } template const TYPE* option(const t_config_option_key &opt_key) const { return const_cast(this)->option(opt_key, false); } + template + TYPE* option_throw(const t_config_option_key &opt_key, bool create = false) + { + ConfigOption *opt = this->optptr(opt_key, create); + if (opt == nullptr) + throw UnknownOptionException(opt_key); + if (opt->type() != TYPE::static_type()) + throw std::runtime_error("Conversion to a wrong type"); + return static_cast(opt); + } + template + const TYPE* option_throw(const t_config_option_key &opt_key) const + { return const_cast(this)->option_throw(opt_key, false); } // Apply all keys of other ConfigBase defined by this->def() to this ConfigBase. // An UnknownOptionException is thrown in case some option keys of other are not defined by this->def(), // or this ConfigBase is of a StaticConfig type and it does not support some of the keys, and ignore_nonexistent is not set. @@ -1276,8 +1348,8 @@ public: bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option(opt_key)->get_at(idx) != 0; } // Command line processing - void read_cli(const std::vector &tokens, t_config_option_keys* extra); - bool read_cli(int argc, char** argv, t_config_option_keys* extra); + void read_cli(const std::vector &tokens, t_config_option_keys* extra, t_config_option_keys* keys = nullptr); + bool read_cli(int argc, char** argv, t_config_option_keys* extra, t_config_option_keys* keys = nullptr); typedef std::map t_options_map; t_options_map::const_iterator cbegin() const { return options.cbegin(); } @@ -1303,25 +1375,6 @@ protected: void set_defaults(); }; -/// Specialization of std::exception to indicate that an unknown config option has been encountered. -class UnknownOptionException : public std::runtime_error { -public: - UnknownOptionException() : - std::runtime_error("Unknown option exception") {} - UnknownOptionException(const std::string &opt_key) : - std::runtime_error(std::string("Unknown option exception: ") + opt_key) {} -}; - -/// Indicate that the ConfigBase derived class does not provide config definition (the method def() returns null). -class NoDefinitionException : public std::runtime_error -{ -public: - NoDefinitionException() : - std::runtime_error("No definition exception") {} - NoDefinitionException(const std::string &opt_key) : - std::runtime_error(std::string("No definition exception: ") + opt_key) {} -}; - } #endif diff --git a/src/libslic3r/Format/OBJ.cpp b/src/libslic3r/Format/OBJ.cpp index ee5756083d..85ca73551c 100644 --- a/src/libslic3r/Format/OBJ.cpp +++ b/src/libslic3r/Format/OBJ.cpp @@ -115,4 +115,23 @@ bool load_obj(const char *path, Model *model, const char *object_name_in) return true; } +bool store_obj(const char *path, TriangleMesh *mesh) +{ + //FIXME returning false even if write failed. + mesh->WriteOBJFile(path); + return true; +} + +bool store_obj(const char *path, ModelObject *model_object) +{ + TriangleMesh mesh = model_object->mesh(); + return store_obj(path, &mesh); +} + +bool store_obj(const char *path, Model *model) +{ + TriangleMesh mesh = model->mesh(); + return store_obj(path, &mesh); +} + }; // namespace Slic3r diff --git a/src/libslic3r/Format/OBJ.hpp b/src/libslic3r/Format/OBJ.hpp index 9a8790bfff..36aa179512 100644 --- a/src/libslic3r/Format/OBJ.hpp +++ b/src/libslic3r/Format/OBJ.hpp @@ -9,6 +9,10 @@ class Model; // Load an OBJ file into a provided model. extern bool load_obj(const char *path, Model *model, const char *object_name = nullptr); +extern bool store_obj(const char *path, TriangleMesh *mesh); +extern bool store_obj(const char *path, ModelObject *model); +extern bool store_obj(const char *path, Model *model); + }; // namespace Slic3r #endif /* slic3r_Format_OBJ_hpp_ */ diff --git a/src/libslic3r/Format/STL.cpp b/src/libslic3r/Format/STL.cpp index 99e2ff1935..b00623d1d6 100644 --- a/src/libslic3r/Format/STL.cpp +++ b/src/libslic3r/Format/STL.cpp @@ -55,4 +55,10 @@ bool store_stl(const char *path, ModelObject *model_object, bool binary) return store_stl(path, &mesh, binary); } +bool store_stl(const char *path, Model *model, bool binary) +{ + TriangleMesh mesh = model->mesh(); + return store_stl(path, &mesh, binary); +} + }; // namespace Slic3r diff --git a/src/libslic3r/Format/STL.hpp b/src/libslic3r/Format/STL.hpp index 2fd32324c8..cff7dc0869 100644 --- a/src/libslic3r/Format/STL.hpp +++ b/src/libslic3r/Format/STL.hpp @@ -11,6 +11,7 @@ extern bool load_stl(const char *path, Model *model, const char *object_name = n extern bool store_stl(const char *path, TriangleMesh *mesh, bool binary); extern bool store_stl(const char *path, ModelObject *model_object, bool binary); +extern bool store_stl(const char *path, Model *model, bool binary); }; // namespace Slic3r diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 9c5f8c826a..4734645986 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -577,6 +577,11 @@ end: return input_file; } +std::string Model::propose_export_file_name_and_path(const std::string &new_extension) const +{ + return boost::filesystem::path(this->propose_export_file_name_and_path()).replace_extension(new_extension).string(); +} + ModelObject::~ModelObject() { this->clear_volumes(); @@ -1568,6 +1573,22 @@ void ModelVolume::scale(const Vec3d& scaling_factors) set_scaling_factor(get_scaling_factor().cwiseProduct(scaling_factors)); } +void ModelObject::scale_to_fit(const Vec3d &size) +{ +/* + BoundingBoxf3 instance_bounding_box(size_t instance_idx, bool dont_translate = false) const; + Vec3d orig_size = this->bounding_box().size(); + float factor = fminf( + size.x / orig_size.x, + fminf( + size.y / orig_size.y, + size.z / orig_size.z + ) + ); + this->scale(factor); +*/ +} + void ModelVolume::rotate(double angle, Axis axis) { switch (axis) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index a4b32d93f8..3b3bb51d97 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -245,6 +245,10 @@ public: void scale(const Vec3d &versor); void scale(const double s) { this->scale(Vec3d(s, s, s)); } void scale(double x, double y, double z) { this->scale(Vec3d(x, y, z)); } + /// Scale the current ModelObject to fit by altering the scaling factor of ModelInstances. + /// It operates on the total size by duplicating the object according to all the instances. + /// \param size Sizef3 the size vector + void scale_to_fit(const Vec3d &size); void rotate(double angle, Axis axis); void rotate(double angle, const Vec3d& axis); void mirror(Axis axis); @@ -619,6 +623,8 @@ public: // Propose an output file name & path based on the first printable object's name and source input file's path. std::string propose_export_file_name_and_path() const; + // Propose an output path, replace extension. The new_extension shall contain the initial dot. + std::string propose_export_file_name_and_path(const std::string &new_extension) const; private: MODELBASE_DERIVED_PRIVATE_COPY_MOVE(Model) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 8ed70fde62..bd7772f809 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1509,7 +1509,7 @@ void Print::process() // The export_gcode may die for various reasons (fails to process output_filename_format, // write error into the G-code, cannot execute post-processing scripts). // It is up to the caller to show an error message. -void Print::export_gcode(const std::string &path_template, GCodePreviewData *preview_data) +std::string Print::export_gcode(const std::string &path_template, GCodePreviewData *preview_data) { // output everything to a G-code file // The following call may die if the output_filename_format template substitution fails. @@ -1525,6 +1525,7 @@ void Print::export_gcode(const std::string &path_template, GCodePreviewData *pre // The following line may die for multiple reasons. GCode gcode; gcode.do_export(this, path.c_str(), preview_data); + return path.c_str(); } void Print::_make_skirt() @@ -1693,8 +1694,10 @@ void Print::_make_brim() } polygons_append(loops, offset(islands, -0.5f * float(flow.scaled_spacing()))); } - + loops = union_pt_chained(loops, false); + // The function above produces ordering well suited for concentric infill (from outside to inside). + // For Brim, the ordering should be reversed (from inside to outside). std::reverse(loops.begin(), loops.end()); extrusion_entities_append_loops(m_brim.entities, std::move(loops), erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(this->skirt_first_layer_height())); } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 0eea71046d..9e97ab20cb 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -297,7 +297,9 @@ public: bool apply_config(DynamicPrintConfig config); void process() override; - void export_gcode(const std::string &path_template, GCodePreviewData *preview_data); + // Exports G-code into a file name based on the path_template, returns the file path of the generated G-code file. + // If preview_data is not null, the preview_data is filled in for the G-code visualization (not used by the command line Slic3r). + std::string export_gcode(const std::string &path_template, GCodePreviewData *preview_data); // methods for handling state bool is_step_done(PrintStep step) const { return Inherited::is_step_done(step); } diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 1c78a3e310..df30761c89 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2955,7 +2955,7 @@ std::string FullPrintConfig::validate() return "--use-firmware-retraction is only supported by Marlin, Smoothie, Repetier and Machinekit firmware"; if (this->use_firmware_retraction.value) - for (bool wipe : this->wipe.values) + for (unsigned char wipe : this->wipe.values) if (wipe) return "--use-firmware-retraction is not compatible with --wipe"; @@ -2999,7 +2999,7 @@ std::string FullPrintConfig::validate() return "Invalid value for --extruder-clearance-height"; // --extrusion-multiplier - for (float em : this->extrusion_multiplier.values) + for (double em : this->extrusion_multiplier.values) if (em <= 0) return "Invalid value for --extrusion-multiplier"; @@ -3100,55 +3100,65 @@ StaticPrintConfig::StaticCache SLAPrintObje StaticPrintConfig::StaticCache SLAPrinterConfig::s_cache_SLAPrinterConfig; StaticPrintConfig::StaticCache SLAFullPrintConfig::s_cache_SLAFullPrintConfig; -CLIConfigDef::CLIConfigDef() +CLIActionsConfigDef::CLIActionsConfigDef() { - ConfigOptionDef *def; + ConfigOptionDef* def; - def = this->add("cut", coFloat); - def->label = L("Cut"); - def->tooltip = L("Cut model at the given Z."); - def->cli = "cut"; - def->default_value = new ConfigOptionFloat(0); - - def = this->add("dont_arrange", coBool); - def->label = L("Dont arrange"); - def->tooltip = L("Don't arrange the objects on the build plate. The model coordinates " - "define the absolute positions on the build plate. " - "The option --center will be ignored."); - def->cli = "dont-arrange"; + // Actions: + def = this->add("export_obj", coBool); + def->label = L("Export SVG"); + def->tooltip = L("Export the model(s) as OBJ."); + def->cli = "export-obj"; + def->default_value = new ConfigOptionBool(false); + +/* + def = this->add("export_svg", coBool); + def->label = L("Export SVG"); + def->tooltip = L("Slice the model and export solid slices as SVG."); + def->cli = "export-svg"; + def->default_value = new ConfigOptionBool(false); +*/ + + def = this->add("export_sla", coBool); + def->label = L("Export SLA"); + def->tooltip = L("Slice the model and export SLA printing layers as PNG."); + def->cli = "export-sla|sla"; def->default_value = new ConfigOptionBool(false); - - def = this->add("datadir", coString); - def->label = L("User data directory"); - def->tooltip = L("Load and store settings at the given directory. " - "This is useful for maintaining different profiles or including " - "configurations from a network storage."); - def->cli = "datadir"; - def->default_value = new ConfigOptionString(); def = this->add("export_3mf", coBool); def->label = L("Export 3MF"); - def->tooltip = L("Slice the model and export slices as 3MF."); + def->tooltip = L("Export the model(s) as 3MF."); def->cli = "export-3mf"; def->default_value = new ConfigOptionBool(false); - def = this->add("slice", coBool); - def->label = L("Slice"); - def->tooltip = L("Slice the model and export gcode."); - def->cli = "slice"; + def = this->add("export_amf", coBool); + def->label = L("Export AMF"); + def->tooltip = L("Export the model(s) as AMF."); + def->cli = "export-amf"; + def->default_value = new ConfigOptionBool(false); + + def = this->add("export_stl", coBool); + def->label = L("Export STL"); + def->tooltip = L("Export the model(s) as STL."); + def->cli = "export-stl"; + def->default_value = new ConfigOptionBool(false); + + def = this->add("export_gcode", coBool); + def->label = L("Export G-code"); + def->tooltip = L("Slice the model and export toolpaths as G-code."); + def->cli = "export-gcode|gcode|g"; def->default_value = new ConfigOptionBool(false); def = this->add("help", coBool); def->label = L("Help"); def->tooltip = L("Show this help."); - def->cli = "help"; + def->cli = "help|h"; def->default_value = new ConfigOptionBool(false); - def = this->add("gui", coBool); - def->label = L("Use GUI"); - def->tooltip = L("Forces the GUI launch instead of command line slicing " - "(if you supply a model file, it will be loaded into the plater)"); - def->cli = "gui"; + def = this->add("help_options", coBool); + def->label = L("Help (options)"); + def->tooltip = L("Show the full list of print/G-code configuration options."); + def->cli = "help-options"; def->default_value = new ConfigOptionBool(false); def = this->add("info", coBool); @@ -3157,107 +3167,169 @@ CLIConfigDef::CLIConfigDef() def->cli = "info"; def->default_value = new ConfigOptionBool(false); - def = this->add("load", coStrings); - def->label = L("Load config file"); - def->tooltip = L("Load configuration from the specified file. It can be used more than once to load options from multiple files."); - def->cli = "load"; - def->default_value = new ConfigOptionStrings(); - - def = this->add("no_gui", coBool); - def->label = L("Do not use GUI"); - def->tooltip = L("Forces the command line slicing instead of gui. This takes precedence over --gui if both are present."); - def->cli = "no-gui"; - def->default_value = new ConfigOptionBool(false); - - def = this->add("output", coString); - def->label = L("Output File"); - def->tooltip = L("The file where the output will be written (if not specified, it will be based on the input file)."); - def->cli = "output"; - def->default_value = new ConfigOptionString(""); - - def = this->add("rotate", coFloat); - def->label = L("Rotate"); - def->tooltip = L("Rotation angle around the Z axis in degrees (0-360, default: 0)."); - def->cli = "rotate"; - def->default_value = new ConfigOptionFloat(0); - - def = this->add("rotate_x", coFloat); - def->label = L("Rotate around X"); - def->tooltip = L("Rotation angle around the X axis in degrees (0-360, default: 0)."); - def->cli = "rotate-x"; - def->default_value = new ConfigOptionFloat(0); - - def = this->add("rotate_y", coFloat); - def->label = L("Rotate around Y"); - def->tooltip = L("Rotation angle around the Y axis in degrees (0-360, default: 0)."); - def->cli = "rotate-y"; - def->default_value = new ConfigOptionFloat(0); - def = this->add("save", coString); def->label = L("Save config file"); def->tooltip = L("Save configuration to the specified file."); def->cli = "save"; def->default_value = new ConfigOptionString(); - - def = this->add("scale", coFloat); - def->label = L("Scale"); - def->tooltip = L("Scaling factor (default: 1)."); - def->cli = "scale"; - def->default_value = new ConfigOptionFloat(1); +} -/* +CLITransformConfigDef::CLITransformConfigDef() +{ + ConfigOptionDef* def; + + // Transform options: + def = this->add("align_xy", coPoint); + def->label = L("Align XY"); + def->tooltip = L("Align the model to the given point."); + def->cli = "align-xy"; + def->default_value = new ConfigOptionPoint(Vec2d(100,100)); + + def = this->add("cut", coFloat); + def->label = L("Cut"); + def->tooltip = L("Cut model at the given Z."); + def->cli = "cut"; + def->default_value = new ConfigOptionFloat(0); + +/* + def = this->add("cut_grid", coFloat); + def->label = L("Cut"); + def->tooltip = L("Cut model in the XY plane into tiles of the specified max size."); + def->cli = "cut-grid"; + def->default_value = new ConfigOptionPoint(); + + def = this->add("cut_x", coFloat); + def->label = L("Cut"); + def->tooltip = L("Cut model at the given X."); + def->cli = "cut-x"; + def->default_value = new ConfigOptionFloat(0); + + def = this->add("cut_y", coFloat); + def->label = L("Cut"); + def->tooltip = L("Cut model at the given Y."); + def->cli = "cut-y"; + def->default_value = new ConfigOptionFloat(0); +*/ + + def = this->add("center", coPoint); + def->label = L("Center"); + def->tooltip = L("Center the print around the given center."); + def->cli = "center"; + def->default_value = new ConfigOptionPoint(Vec2d(100,100)); + + def = this->add("dont_arrange", coBool); + def->label = L("Don't arrange"); + def->tooltip = L("Do not rearrange the given models before merging and keep their original XY coordinates."); + def->cli = "dont-arrange"; + + def = this->add("duplicate", coInt); + def->label = L("Duplicate"); + def->tooltip =L("Multiply copies by this factor."); + def->cli = "duplicate=i"; + def->min = 1; + + def = this->add("duplicate_grid", coPoint); + def->label = L("Duplicate by grid"); + def->tooltip = L("Multiply copies by creating a grid."); + def->cli = "duplicate-grid"; + + def = this->add("merge", coBool); + def->label = L("Merge"); + def->tooltip = L("Arrange the supplied models in a plate and merge them in a single model in order to perform actions once."); + def->cli = "merge|m"; + + def = this->add("repair", coBool); + def->label = L("Repair"); + def->tooltip = L("Try to repair any non-manifold meshes (this option is implicitly added whenever we need to slice the model to perform the requested action)."); + def->cli = "repair"; + + def = this->add("rotate", coFloat); + def->label = L("Rotate"); + def->tooltip = L("Rotation angle around the Z axis in degrees."); + def->cli = "rotate"; + def->default_value = new ConfigOptionFloat(0); + + def = this->add("rotate_x", coFloat); + def->label = L("Rotate around X"); + def->tooltip = L("Rotation angle around the X axis in degrees."); + def->cli = "rotate-x"; + def->default_value = new ConfigOptionFloat(0); + + def = this->add("rotate_y", coFloat); + def->label = L("Rotate around Y"); + def->tooltip = L("Rotation angle around the Y axis in degrees."); + def->cli = "rotate-y"; + def->default_value = new ConfigOptionFloat(0); + + def = this->add("scale", coFloatOrPercent); + def->label = L("Scale"); + def->tooltip = L("Scaling factor or percentage."); + def->cli = "scale"; + def->default_value = new ConfigOptionFloatOrPercent(1, false); + + def = this->add("split", coBool); + def->label = L("Split"); + def->tooltip = L("Detect unconnected parts in the given model(s) and split them into separate objects."); + def->cli = "split"; + def = this->add("scale_to_fit", coPoint3); def->label = L("Scale to Fit"); def->tooltip = L("Scale to fit the given volume."); def->cli = "scale-to-fit"; - def->default_value = new ConfigOptionPoint3(Pointf3(0,0,0)); -*/ - - def = this->add("print_center", coPoint); - def->label = L("Print center"); - def->tooltip = L("Center the print around the given center (default: 100, 100)."); - def->cli = "print-center"; - def->default_value = new ConfigOptionPoint(Vec2d(100,100)); + def->default_value = new ConfigOptionPoint3(Vec3d(0,0,0)); } -const CLIConfigDef cli_config_def; +CLIMiscConfigDef::CLIMiscConfigDef() +{ + ConfigOptionDef* def; + + def = this->add("ignore_nonexistent_config", coBool); + def->label = L("Ignore non-existent config files"); + def->tooltip = L("Do not fail if a file supplied to --load does not exist."); + def->cli = "ignore-nonexistent-config"; + + def = this->add("load", coStrings); + def->label = L("Load config file"); + def->tooltip = L("Load configuration from the specified file. It can be used more than once to load options from multiple files."); + def->cli = "load"; + + def = this->add("output", coString); + def->label = L("Output File"); + def->tooltip = L("The file where the output will be written (if not specified, it will be based on the input file)."); + def->cli = "output|o"; + +/* + def = this->add("autosave", coString); + def->label = L("Autosave"); + def->tooltip = L("Automatically export current configuration to the specified file."); + def->cli = "autosave"; +*/ + + def = this->add("datadir", coString); + def->label = L("Data directory"); + def->tooltip = L("Load and store settings at the given directory. This is useful for maintaining different profiles or including configurations from a network storage."); + def->cli = "datadir"; + + def = this->add("loglevel", coInt); + def->label = L("Logging level"); + def->tooltip = L("Messages with severity lower or eqal to the loglevel will be printed out. 0:trace, 1:debug, 2:info, 3:warning, 4:error, 5:fatal"); + def->cli = "loglevel"; + def->min = 0; +} + +const CLIActionsConfigDef cli_actions_config_def; +const CLITransformConfigDef cli_transform_config_def; +const CLIMiscConfigDef cli_misc_config_def; + DynamicPrintAndCLIConfig::PrintAndCLIConfigDef DynamicPrintAndCLIConfig::s_def; void DynamicPrintAndCLIConfig::handle_legacy(t_config_option_key &opt_key, std::string &value) const { - if (cli_config_def.options.find(opt_key) == cli_config_def.options.end()) { - PrintConfigDef::handle_legacy(opt_key, value); - } -} - -std::ostream& print_cli_options(std::ostream& out) -{ - for (const auto& opt : cli_config_def.options) { - if (opt.second.cli.size() != 0) { - out << "\t" << std::left << std::setw(40) << std::string("--") + opt.second.cli; - out << "\t" << opt.second.tooltip << "\n"; - if (opt.second.default_value != nullptr) - out << "\t" << std::setw(40) << " " << "\t" << " (default: " << opt.second.default_value->serialize() << ")"; - out << "\n"; - } - } - std::cerr << std::endl; - return out; -} - -std::ostream& print_print_options(std::ostream& out) -{ - for (const auto& opt : print_config_def.options) { - if (opt.second.cli.size() != 0) { - out << "\t" << std::left << std::setw(40) << std::string("--") + opt.second.cli; - out << "\t" << opt.second.tooltip << "\n"; - if (opt.second.default_value != nullptr) - out << "\t" << std::setw(40) << " " << "\t" << " (default: " << opt.second.default_value->serialize() << ")"; - out << "\n"; - } - } - std::cerr << std::endl; - return out; + if (cli_actions_config_def .options.find(opt_key) == cli_actions_config_def .options.end() && + cli_transform_config_def.options.find(opt_key) == cli_transform_config_def.options.end() && + cli_misc_config_def .options.find(opt_key) == cli_misc_config_def .options.end()) { + PrintConfigDef::handle_legacy(opt_key, value); + } } } diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index f4d9053a6b..ecbfd0e62f 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -30,6 +30,8 @@ enum PrinterTechnology ptFFF, // Stereolitography ptSLA, + // Unknown, useful for command line processing + ptUnknown, }; enum GCodeFlavor { @@ -1145,72 +1147,32 @@ protected: #undef STATIC_PRINT_CONFIG_CACHE_DERIVED #undef OPT_PTR -class CLIConfigDef : public ConfigDef +class CLIActionsConfigDef : public ConfigDef { public: - CLIConfigDef(); + CLIActionsConfigDef(); }; -extern const CLIConfigDef cli_config_def; - -#define OPT_PTR(KEY) if (opt_key == #KEY) return &this->KEY - -class CLIConfig : public virtual ConfigBase, public StaticConfig +class CLITransformConfigDef : public ConfigDef { public: - ConfigOptionFloat cut; - ConfigOptionString datadir; - ConfigOptionBool dont_arrange; - ConfigOptionBool export_3mf; - ConfigOptionBool gui; - ConfigOptionBool info; - ConfigOptionBool help; - ConfigOptionStrings load; - ConfigOptionBool no_gui; - ConfigOptionString output; - ConfigOptionPoint print_center; - ConfigOptionFloat rotate; - ConfigOptionFloat rotate_x; - ConfigOptionFloat rotate_y; - ConfigOptionString save; - ConfigOptionFloat scale; -// ConfigOptionPoint3 scale_to_fit; - ConfigOptionBool slice; - - CLIConfig() : ConfigBase(), StaticConfig() - { - this->set_defaults(); - }; - - // Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here. - const ConfigDef* def() const override { return &cli_config_def; } - t_config_option_keys keys() const override { return cli_config_def.keys(); } - - ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) override - { - OPT_PTR(cut); - OPT_PTR(datadir); - OPT_PTR(dont_arrange); - OPT_PTR(export_3mf); - OPT_PTR(gui); - OPT_PTR(help); - OPT_PTR(info); - OPT_PTR(load); - OPT_PTR(no_gui); - OPT_PTR(output); - OPT_PTR(print_center); - OPT_PTR(rotate); - OPT_PTR(rotate_x); - OPT_PTR(rotate_y); - OPT_PTR(save); - OPT_PTR(scale); -// OPT_PTR(scale_to_fit); - OPT_PTR(slice); - return NULL; - } + CLITransformConfigDef(); }; -#undef OPT_PTR +class CLIMiscConfigDef : public ConfigDef +{ +public: + CLIMiscConfigDef(); +}; + +// This class defines the command line options representing actions. +extern const CLIActionsConfigDef cli_actions_config_def; + +// This class defines the command line options representing transforms. +extern const CLITransformConfigDef cli_transform_config_def; + +// This class defines all command line options that are not actions or transforms. +extern const CLIMiscConfigDef cli_misc_config_def; class DynamicPrintAndCLIConfig : public DynamicPrintConfig { @@ -1233,19 +1195,16 @@ private: public: PrintAndCLIConfigDef() { this->options.insert(print_config_def.options.begin(), print_config_def.options.end()); - this->options.insert(cli_config_def.options.begin(), cli_config_def.options.end()); + this->options.insert(cli_actions_config_def.options.begin(), cli_actions_config_def.options.end()); + this->options.insert(cli_transform_config_def.options.begin(), cli_transform_config_def.options.end()); + this->options.insert(cli_misc_config_def.options.begin(), cli_misc_config_def.options.end()); } - // Do not release the default values, they are handled by print_config_def & cli_config_def. + // Do not release the default values, they are handled by print_config_def & cli_actions_config_def / cli_transform_config_def / cli_misc_config_def. ~PrintAndCLIConfigDef() { this->options.clear(); } }; static PrintAndCLIConfigDef s_def; }; -/// Iterate through all of the print options and write them to a stream. -std::ostream& print_print_options(std::ostream& out); -/// Iterate through all of the CLI options and write them to a stream. -std::ostream& print_cli_options(std::ostream& out); - } // namespace Slic3r #endif diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 23880cfa96..e6696b27f2 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -300,81 +300,82 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf auto it_status = model_object_status.find(ModelObjectStatus(model_object.id())); assert(it_status != model_object_status.end()); assert(it_status->status != ModelObjectStatus::Deleted); - if (it_status->status == ModelObjectStatus::New) - // PrintObject instances will be added in the next loop. - continue; - // Update the ModelObject instance, possibly invalidate the linked PrintObjects. - assert(it_status->status == ModelObjectStatus::Old || it_status->status == ModelObjectStatus::Moved); - const ModelObject &model_object_new = *model.objects[idx_model_object]; - auto it_print_object_status = print_object_status.lower_bound(PrintObjectStatus(model_object.id())); - if (it_print_object_status != print_object_status.end() && it_print_object_status->id != model_object.id()) - it_print_object_status = print_object_status.end(); - // Check whether a model part volume was added or removed, their transformations or order changed. - bool model_parts_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::MODEL_PART); - bool sla_trafo_differs = model_object.instances.empty() != model_object_new.instances.empty() || - (! model_object.instances.empty() && ! sla_trafo(model_object).isApprox(sla_trafo(model_object_new))); - if (model_parts_differ || sla_trafo_differs) { - // The very first step (the slicing step) is invalidated. One may freely remove all associated PrintObjects. - if (it_print_object_status != print_object_status.end()) { - update_apply_status(it_print_object_status->print_object->invalidate_all_steps()); - const_cast(*it_print_object_status).status = PrintObjectStatus::Deleted; - } - // Copy content of the ModelObject including its ID, do not change the parent. - model_object.assign_copy(model_object_new); - } else { - // Synchronize Object's config. - bool object_config_changed = model_object.config != model_object_new.config; - if (object_config_changed) - model_object.config = model_object_new.config; - if (! object_diff.empty() || object_config_changed) { - SLAPrintObjectConfig new_config = m_default_object_config; - normalize_and_apply_config(new_config, model_object.config); - if (it_print_object_status != print_object_status.end()) { - t_config_option_keys diff = it_print_object_status->print_object->config().diff(new_config); - if (! diff.empty()) { - update_apply_status(it_print_object_status->print_object->invalidate_state_by_config_options(diff)); - it_print_object_status->print_object->config_apply_only(new_config, diff, true); - } - } - } - /*if (model_object.sla_support_points != model_object_new.sla_support_points) { - model_object.sla_support_points = model_object_new.sla_support_points; - if (it_print_object_status != print_object_status.end()) - update_apply_status(it_print_object_status->print_object->invalidate_step(slaposSupportPoints)); - } - if (model_object.sla_points_status != model_object_new.sla_points_status) { - // Change of this status should invalidate support points. The points themselves are not enough, there are none - // in case that nothing was generated OR that points were autogenerated already and not copied to the front-end. - // These cases can only be differentiated by checking the status change. However, changing from 'Generating' should NOT - // invalidate - that would keep stopping the background processing without a reason. - if (model_object.sla_points_status != sla::PointsStatus::Generating) - if (it_print_object_status != print_object_status.end()) - update_apply_status(it_print_object_status->print_object->invalidate_step(slaposSupportPoints)); - model_object.sla_points_status = model_object_new.sla_points_status; - }*/ + // PrintObject for this ModelObject, if it exists. + auto it_print_object_status = print_object_status.end(); + if (it_status->status != ModelObjectStatus::New) { + // Update the ModelObject instance, possibly invalidate the linked PrintObjects. + assert(it_status->status == ModelObjectStatus::Old || it_status->status == ModelObjectStatus::Moved); + const ModelObject &model_object_new = *model.objects[idx_model_object]; + auto it_print_object_status = print_object_status.lower_bound(PrintObjectStatus(model_object.id())); + if (it_print_object_status != print_object_status.end() && it_print_object_status->id != model_object.id()) + it_print_object_status = print_object_status.end(); + // Check whether a model part volume was added or removed, their transformations or order changed. + bool model_parts_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::MODEL_PART); + bool sla_trafo_differs = model_object.instances.empty() != model_object_new.instances.empty() || + (! model_object.instances.empty() && ! sla_trafo(model_object).isApprox(sla_trafo(model_object_new))); + if (model_parts_differ || sla_trafo_differs) { + // The very first step (the slicing step) is invalidated. One may freely remove all associated PrintObjects. + if (it_print_object_status != print_object_status.end()) { + update_apply_status(it_print_object_status->print_object->invalidate_all_steps()); + const_cast(*it_print_object_status).status = PrintObjectStatus::Deleted; + } + // Copy content of the ModelObject including its ID, do not change the parent. + model_object.assign_copy(model_object_new); + } else { + // Synchronize Object's config. + bool object_config_changed = model_object.config != model_object_new.config; + if (object_config_changed) + model_object.config = model_object_new.config; + if (! object_diff.empty() || object_config_changed) { + SLAPrintObjectConfig new_config = m_default_object_config; + normalize_and_apply_config(new_config, model_object.config); + if (it_print_object_status != print_object_status.end()) { + t_config_option_keys diff = it_print_object_status->print_object->config().diff(new_config); + if (! diff.empty()) { + update_apply_status(it_print_object_status->print_object->invalidate_state_by_config_options(diff)); + it_print_object_status->print_object->config_apply_only(new_config, diff, true); + } + } + } + /*if (model_object.sla_support_points != model_object_new.sla_support_points) { + model_object.sla_support_points = model_object_new.sla_support_points; + if (it_print_object_status != print_object_status.end()) + update_apply_status(it_print_object_status->print_object->invalidate_step(slaposSupportPoints)); + } + if (model_object.sla_points_status != model_object_new.sla_points_status) { + // Change of this status should invalidate support points. The points themselves are not enough, there are none + // in case that nothing was generated OR that points were autogenerated already and not copied to the front-end. + // These cases can only be differentiated by checking the status change. However, changing from 'Generating' should NOT + // invalidate - that would keep stopping the background processing without a reason. + if (model_object.sla_points_status != sla::PointsStatus::Generating) + if (it_print_object_status != print_object_status.end()) + update_apply_status(it_print_object_status->print_object->invalidate_step(slaposSupportPoints)); + model_object.sla_points_status = model_object_new.sla_points_status; + }*/ - bool old_user_modified = model_object.sla_points_status == sla::PointsStatus::UserModified; - bool new_user_modified = model_object_new.sla_points_status == sla::PointsStatus::UserModified; - if ((old_user_modified && ! new_user_modified) || // switching to automatic supports from manual supports - (! old_user_modified && new_user_modified) || // switching to manual supports from automatic supports - (new_user_modified && model_object.sla_support_points != model_object_new.sla_support_points)) { - if (it_print_object_status != print_object_status.end()) - update_apply_status(it_print_object_status->print_object->invalidate_step(slaposSupportPoints)); + bool old_user_modified = model_object.sla_points_status == sla::PointsStatus::UserModified; + bool new_user_modified = model_object_new.sla_points_status == sla::PointsStatus::UserModified; + if ((old_user_modified && ! new_user_modified) || // switching to automatic supports from manual supports + (! old_user_modified && new_user_modified) || // switching to manual supports from automatic supports + (new_user_modified && model_object.sla_support_points != model_object_new.sla_support_points)) { + if (it_print_object_status != print_object_status.end()) + update_apply_status(it_print_object_status->print_object->invalidate_step(slaposSupportPoints)); - model_object.sla_points_status = model_object_new.sla_points_status; - model_object.sla_support_points = model_object_new.sla_support_points; - } + model_object.sla_points_status = model_object_new.sla_points_status; + model_object.sla_support_points = model_object_new.sla_support_points; + } - // Copy the ModelObject name, input_file and instances. The instances will compared against PrintObject instances in the next step. - model_object.name = model_object_new.name; - model_object.input_file = model_object_new.input_file; - model_object.clear_instances(); - model_object.instances.reserve(model_object_new.instances.size()); - for (const ModelInstance *model_instance : model_object_new.instances) { - model_object.instances.emplace_back(new ModelInstance(*model_instance)); - model_object.instances.back()->set_model_object(&model_object); - } - } + // Copy the ModelObject name, input_file and instances. The instances will compared against PrintObject instances in the next step. + model_object.name = model_object_new.name; + model_object.input_file = model_object_new.input_file; + model_object.clear_instances(); + model_object.instances.reserve(model_object_new.instances.size()); + for (const ModelInstance *model_instance : model_object_new.instances) { + model_object.instances.emplace_back(new ModelInstance(*model_instance)); + model_object.instances.back()->set_model_object(&model_object); + } + } + } std::vector new_instances = sla_instances(model_object); if (it_print_object_status != print_object_status.end() && it_print_object_status->status != PrintObjectStatus::Deleted) { diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 855fe76443..eaa11b7381 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -247,7 +247,7 @@ bool TriangleMesh::needed_repair() const || this->stl.stats.backwards_edges > 0; } -void TriangleMesh::WriteOBJFile(char* output_file) +void TriangleMesh::WriteOBJFile(const char* output_file) { stl_generate_shared_vertices(&stl); stl_write_obj(&stl, output_file); @@ -1499,9 +1499,16 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo // Try to close gaps. // Do it in two rounds, first try to connect in the same direction only, // then try to connect the open polylines in reversed order as well. +#if 0 + for (double max_gap : { EPSILON, 0.001, 0.1, 1., 2. }) { + chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, false); + chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, true); + } +#else const double max_gap = 2.; //mm chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, false); chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, true); +#endif #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 04c13e876e..a4387e5c1e 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -36,7 +36,7 @@ public: float volume(); void check_topology(); bool is_manifold() const { return this->stl.stats.connected_facets_3_edge == (int)this->stl.stats.number_of_facets; } - void WriteOBJFile(char* output_file); + void WriteOBJFile(const char* output_file); void scale(float factor); void scale(const Vec3d &versor); void translate(float x, float y, float z); diff --git a/src/slic3r.cpp b/src/slic3r.cpp index 9a124c73c7..b6409fb1de 100644 --- a/src/slic3r.cpp +++ b/src/slic3r.cpp @@ -31,22 +31,452 @@ #include "libslic3r/Print.hpp" #include "libslic3r/SLAPrint.hpp" #include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/Format/AMF.hpp" #include "libslic3r/Format/3mf.hpp" +#include "libslic3r/Format/STL.hpp" +#include "libslic3r/Format/OBJ.hpp" #include "libslic3r/Utils.hpp" +#include "slic3r.hpp" #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/GUI_App.hpp" using namespace Slic3r; -/// utility function for displaying CLI usage -void printUsage(); +PrinterTechnology get_printer_technology(const DynamicConfig &config) +{ + const ConfigOptionEnum *opt = config.option>("printer_technology"); + return (opt == nullptr) ? ptUnknown : opt->value; +} -#ifdef _MSC_VER -int slic3r_main_(int argc, char **argv) +int CLI::run(int argc, char **argv) +{ + if (! this->setup(argc, argv)) + return 1; + + m_extra_config.apply(m_config, true); + m_extra_config.normalize(); + + bool start_gui = m_actions.empty() && + // cutting transformations are setting an "export" action. + std::find(m_transforms.begin(), m_transforms.end(), "cut") == m_transforms.end() && + std::find(m_transforms.begin(), m_transforms.end(), "cut_x") == m_transforms.end() && + std::find(m_transforms.begin(), m_transforms.end(), "cut_y") == m_transforms.end(); + PrinterTechnology printer_technology = get_printer_technology(m_extra_config); + const std::vector &load_configs = m_config.option("load", true)->values; + + // load config files supplied via --load + for (auto const &file : load_configs) { + if (! boost::filesystem::exists(file)) { + if (m_config.opt_bool("ignore_nonexistent_file")) { + continue; + } else { + boost::nowide::cerr << "No such file: " << file << std::endl; + return 1; + } + } + DynamicPrintConfig config; + try { + config.load(file); + } catch (std::exception &ex) { + boost::nowide::cerr << "Error while reading config file: " << ex.what() << std::endl; + return 1; + } + config.normalize(); + PrinterTechnology other_printer_technology = get_printer_technology(config); + if (printer_technology == ptUnknown) { + printer_technology = other_printer_technology; + } else if (printer_technology != other_printer_technology) { + boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl; + return 1; + } + m_print_config.apply(config); + } + + // Read input file(s) if any. + for (const std::string &file : m_input_files) { + if (! boost::filesystem::exists(file)) { + boost::nowide::cerr << "No such file: " << file << std::endl; + exit(1); + } + Model model; + try { + // When loading an AMF or 3MF, config is imported as well, including the printer technology. + model = Model::read_from_file(file, &m_print_config, true); + PrinterTechnology other_printer_technology = get_printer_technology(m_print_config); + if (printer_technology == ptUnknown) { + printer_technology = other_printer_technology; + } else if (printer_technology != other_printer_technology) { + boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl; + return 1; + } + } catch (std::exception &e) { + boost::nowide::cerr << file << ": " << e.what() << std::endl; + return 1; + } + if (model.objects.empty()) { + boost::nowide::cerr << "Error: file is empty: " << file << std::endl; + continue; + } + m_models.push_back(model); + } + + // Apply command line options to a more specific DynamicPrintConfig which provides normalize() + // (command line options override --load files) + m_print_config.apply(m_extra_config, true); + // Normalizing after importing the 3MFs / AMFs + m_print_config.normalize(); + + if (printer_technology == ptUnknown) + printer_technology = std::find(m_actions.begin(), m_actions.end(), "export_sla") == m_actions.end() ? ptFFF : ptSLA; + + // Initialize full print configs for both the FFF and SLA technologies. + FullPrintConfig fff_print_config; + SLAFullPrintConfig sla_print_config; + fff_print_config.apply(m_print_config); + sla_print_config.apply(m_print_config); + + // Loop through transform options. + for (auto const &opt_key : m_transforms) { + if (opt_key == "merge") { + Model m; + for (auto &model : m_models) + for (ModelObject *o : model.objects) + m.add_object(*o); + // Rearrange instances unless --dont-arrange is supplied + if (! m_config.opt_bool("dont_arrange")) { + m.add_default_instances(); + const BoundingBoxf &bb = fff_print_config.bed_shape.values; + m.arrange_objects( + fff_print_config.min_object_distance(), + // If we are going to use the merged model for printing, honor + // the configured print bed for arranging, otherwise do it freely. + this->has_print_action() ? &bb : nullptr + ); + } + m_models.clear(); + m_models.emplace_back(std::move(m)); + } else if (opt_key == "duplicate") { + const BoundingBoxf &bb = fff_print_config.bed_shape.values; + for (auto &model : m_models) { + const bool all_objects_have_instances = std::none_of( + model.objects.begin(), model.objects.end(), + [](ModelObject* o){ return o->instances.empty(); } + ); + if (all_objects_have_instances) { + // if all input objects have defined position(s) apply duplication to the whole model + model.duplicate(m_config.opt_int("duplicate"), fff_print_config.min_object_distance(), &bb); + } else { + model.add_default_instances(); + model.duplicate_objects(m_config.opt_int("duplicate"), fff_print_config.min_object_distance(), &bb); + } + } + } else if (opt_key == "duplicate_grid") { + std::vector &ints = m_config.option("duplicate_grid")->values; + const int x = ints.size() > 0 ? ints.at(0) : 1; + const int y = ints.size() > 1 ? ints.at(1) : 1; + const double distance = fff_print_config.duplicate_distance.value; + for (auto &model : m_models) + model.duplicate_objects_grid(x, y, (distance > 0) ? distance : 6); // TODO: this is not the right place for setting a default + } else if (opt_key == "center") { + for (auto &model : m_models) { + model.add_default_instances(); + // this affects instances: + model.center_instances_around_point(m_config.option("center")->value); + // this affects volumes: + //FIXME Vojtech: Who knows why the complete model should be aligned with Z as a single rigid body? + //model.align_to_ground(); + BoundingBoxf3 bbox; + for (ModelObject *model_object : model.objects) + // We are interested into the Z span only, therefore it is sufficient to measure the bounding box of the 1st instance only. + bbox.merge(model_object->instance_bounding_box(0, false)); + for (ModelObject *model_object : model.objects) + for (ModelInstance *model_instance : model_object->instances) + model_instance->set_offset(Z, model_instance->get_offset(Z) - bbox.min.z()); + } + } else if (opt_key == "align_xy") { + const Vec2d &p = m_config.option("align_xy")->value; + for (auto &model : m_models) { + BoundingBoxf3 bb = model.bounding_box(); + // this affects volumes: + model.translate(-(bb.min.x() - p.x()), -(bb.min.y() - p.y()), -bb.min.z()); + } + } else if (opt_key == "dont_arrange") { + // do nothing - this option alters other transform options + } else if (opt_key == "rotate") { + for (auto &model : m_models) + for (auto &o : model.objects) + // this affects volumes: + o->rotate(Geometry::deg2rad(m_config.opt_float(opt_key)), Z); + } else if (opt_key == "rotate_x") { + for (auto &model : m_models) + for (auto &o : model.objects) + // this affects volumes: + o->rotate(Geometry::deg2rad(m_config.opt_float(opt_key)), X); + } else if (opt_key == "rotate_y") { + for (auto &model : m_models) + for (auto &o : model.objects) + // this affects volumes: + o->rotate(Geometry::deg2rad(m_config.opt_float(opt_key)), Y); + } else if (opt_key == "scale") { + for (auto &model : m_models) + for (auto &o : model.objects) + // this affects volumes: + o->scale(m_config.get_abs_value(opt_key, 1)); + } else if (opt_key == "scale_to_fit") { + const Vec3d &opt = m_config.opt(opt_key)->value; + if (opt.x() <= 0 || opt.y() <= 0 || opt.z() <= 0) { + boost::nowide::cerr << "--scale-to-fit requires a positive volume" << std::endl; + return 1; + } + for (auto &model : m_models) + for (auto &o : model.objects) + // this affects volumes: + o->scale_to_fit(opt); + } else if (opt_key == "cut" || opt_key == "cut_x" || opt_key == "cut_y") { + std::vector new_models; + for (auto &model : m_models) { + model.repair(); + model.translate(0, 0, -model.bounding_box().min.z()); // align to z = 0 + size_t num_objects = model.objects.size(); + for (size_t i = 0; i < num_objects; ++ i) { + +#if 0 + if (opt_key == "cut_x") { + o->cut(X, m_config.opt_float("cut_x"), &out); + } else if (opt_key == "cut_y") { + o->cut(Y, m_config.opt_float("cut_y"), &out); + } else if (opt_key == "cut") { + o->cut(Z, m_config.opt_float("cut"), &out); + } #else -int main(int argc, char **argv) + model.objects.front()->cut(0, m_config.opt_float("cut"), true, true, true); #endif + model.delete_object(size_t(0)); + } + } + + // TODO: copy less stuff around using pointers + m_models = new_models; + + if (m_actions.empty()) + m_actions.push_back("export_stl"); + } +#if 0 + else if (opt_key == "cut_grid") { + std::vector new_models; + for (auto &model : m_models) { + TriangleMesh mesh = model.mesh(); + mesh.repair(); + + TriangleMeshPtrs meshes = mesh.cut_by_grid(m_config.option("cut_grid")->value); + size_t i = 0; + for (TriangleMesh* m : meshes) { + Model out; + auto o = out.add_object(); + o->add_volume(*m); + o->input_file += "_" + std::to_string(i++); + delete m; + } + } + + // TODO: copy less stuff around using pointers + m_models = new_models; + + if (m_actions.empty()) + m_actions.push_back("export_stl"); + } +#endif + else if (opt_key == "split") { + for (Model &model : m_models) { + size_t num_objects = model.objects.size(); + for (size_t i = 0; i < num_objects; ++ i) { + model.objects.front()->split(nullptr); + model.delete_object(size_t(0)); + } + } + } else if (opt_key == "repair") { + for (auto &model : m_models) + model.repair(); + } else { + boost::nowide::cerr << "error: option not implemented yet: " << opt_key << std::endl; + return 1; + } + } + + // loop through action options + for (auto const &opt_key : m_actions) { + if (opt_key == "help") { + this->print_help(); + } else if (opt_key == "help_options") { + this->print_help(true); + } else if (opt_key == "save") { + //FIXME check for mixing the FFF / SLA parameters. + // or better save fff_print_config vs. sla_print_config + m_print_config.save(m_config.opt_string("save")); + } else if (opt_key == "info") { + // --info works on unrepaired model + for (Model &model : m_models) { + model.add_default_instances(); + model.print_info(); + } + } else if (opt_key == "export_stl") { + for (auto &model : m_models) + model.add_default_instances(); + if (! this->export_models(IO::STL)) + return 1; + } else if (opt_key == "export_obj") { + for (auto &model : m_models) + model.add_default_instances(); + if (! this->export_models(IO::OBJ)) + return 1; + } else if (opt_key == "export_amf") { + if (! this->export_models(IO::AMF)) + return 1; + } else if (opt_key == "export_3mf") { + if (! this->export_models(IO::TMF)) + return 1; + } else if (opt_key == "export_gcode" || opt_key == "export_sla" || opt_key == "slice") { + if (opt_key == "export_gcode" && printer_technology == ptSLA) { + boost::nowide::cerr << "error: cannot export G-code for an FFF configuration" << std::endl; + return 1; + } else if (opt_key == "export_sla" && printer_technology == ptFFF) { + boost::nowide::cerr << "error: cannot export SLA slices for a SLA configuration" << std::endl; + return 1; + } + // Make a copy of the model if the current action is not the last action, as the model may be + // modified by the centering and such. + Model model_copy; + bool make_copy = &opt_key != &m_actions.back(); + for (Model &model_in : m_models) { + if (make_copy) + model_copy = model_in; + Model &model = make_copy ? model_copy : model_in; + // If all objects have defined instances, their relative positions will be + // honored when printing (they will be only centered, unless --dont-arrange + // is supplied); if any object has no instances, it will get a default one + // and all instances will be rearranged (unless --dont-arrange is supplied). + std::string outfile = m_config.opt_string("output"); + Print fff_print; + SLAPrint sla_print; + PrintBase *print = (printer_technology == ptFFF) ? static_cast(&fff_print) : static_cast(&sla_print); + if (! m_config.opt_bool("dont_arrange")) { + //FIXME make the min_object_distance configurable. + model.arrange_objects(fff_print.config().min_object_distance()); + model.center_instances_around_point(m_config.option("center")->value); + } + if (printer_technology == ptFFF) { + for (auto* mo : model.objects) + fff_print.auto_assign_extruders(mo); + } + print->apply(model, m_print_config); + std::string err = print->validate(); + if (err.empty()) { + try { + std::string outfile_final; + print->process(); + if (printer_technology == ptFFF) { + // The outfile is processed by a PlaceholderParser. + outfile = fff_print.export_gcode(outfile, nullptr); + outfile_final = fff_print.print_statistics().finalize_output_path(outfile); + } else { + outfile = sla_print.output_filepath(outfile); + //FIXME Tamas, please port it to miniz + // sla_print.export_raster(outfile); + outfile_final = sla_print.print_statistics().finalize_output_path(outfile); + } + if (outfile != outfile_final && Slic3r::rename_file(outfile, outfile_final) != 0) { + boost::nowide::cerr << "Renaming file " << outfile << " to " << outfile_final << " failed" << std::endl; + return 1; + } + } catch (const std::exception &ex) { + boost::nowide::cerr << ex.what() << std::endl; + return 1; + } + } else { + boost::nowide::cerr << err << std::endl; + return 1; + } + +/* + print.center = ! m_config.has("center") + && ! m_config.has("align_xy") + && ! m_config.opt_bool("dont_arrange"); + print.set_model(model); + + // start chronometer + typedef std::chrono::high_resolution_clock clock_; + typedef std::chrono::duration > second_; + std::chrono::time_point t0{ clock_::now() }; + + const std::string outfile = this->output_filepath(model, IO::Gcode); + try { + print.export_gcode(outfile); + } catch (std::runtime_error &e) { + boost::nowide::cerr << e.what() << std::endl; + return 1; + } + boost::nowide::cout << "G-code exported to " << outfile << std::endl; + + // output some statistics + double duration { std::chrono::duration_cast(clock_::now() - t0).count() }; + boost::nowide::cout << std::fixed << std::setprecision(0) + << "Done. Process took " << (duration/60) << " minutes and " + << std::setprecision(3) + << std::fmod(duration, 60.0) << " seconds." << std::endl + << std::setprecision(2) + << "Filament required: " << print.total_used_filament() << "mm" + << " (" << print.total_extruded_volume()/1000 << "cm3)" << std::endl; +*/ + } + } else { + boost::nowide::cerr << "error: option not supported yet: " << opt_key << std::endl; + return 1; + } + } + + if (start_gui) { +#if 1 +// #ifdef USE_WX + GUI::GUI_App *gui = new GUI::GUI_App(); +// gui->autosave = m_config.opt_string("autosave"); + GUI::GUI_App::SetInstance(gui); + gui->CallAfter([gui, this, &load_configs] { + if (!gui->initialized()) { + return; + } +#if 0 + // Load the cummulative config over the currently active profiles. + //FIXME if multiple configs are loaded, only the last one will have an effect. + // We need to decide what to do about loading of separate presets (just print preset, just filament preset etc). + // As of now only the full configs are supported here. + if (!m_print_config.empty()) + gui->mainframe->load_config(m_print_config); +#endif + if (! load_configs.empty()) + // Load the last config to give it a name at the UI. The name of the preset may be later + // changed by loading an AMF or 3MF. + //FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config. + gui->mainframe->load_config_file(load_configs.back()); + // If loading a 3MF file, the config is loaded from the last one. + if (! m_input_files.empty()) + gui->plater()->load_files(m_input_files, true, true); + if (! m_extra_config.empty()) + gui->mainframe->load_config(m_extra_config); + }); + return wxEntry(argc, argv); +#else + // No GUI support. Just print out a help. + this->print_help(false); + // If started without a parameter, consider it to be OK, otherwise report an error code (no action etc). + return (argc == 0) ? 0 : 1; +#endif + } + + return 0; +} + +bool CLI::setup(int argc, char **argv) { { const char *loglevel = boost::nowide::getenv("SLIC3R_LOGLEVEL"); @@ -58,15 +488,6 @@ int main(int argc, char **argv) } } - // parse all command line options into a DynamicConfig - DynamicPrintAndCLIConfig all_config; - t_config_option_keys input_files; - // if any option is unsupported, print usage and abort immediately - if (! all_config.read_cli(argc, argv, &input_files)) { - printUsage(); - return 0; - } - boost::filesystem::path path_to_binary = boost::filesystem::system_complete(argv[0]); // Path from the Slic3r binary to its resources. @@ -94,207 +515,111 @@ int main(int argc, char **argv) set_var_dir((path_resources / "icons").string()); set_local_dir((path_resources / "localization").string()); - // apply command line options to a more handy CLIConfig - CLIConfig cli_config; -#ifdef __APPLE__ - // Enable the GUI mode by default, to support drag & drop. - cli_config.gui.value = true; -#endif /* __APPLE__ */ + // Parse all command line options into a DynamicConfig. + // If any option is unsupported, print usage and abort immediately. + t_config_option_keys opt_order; + if (! m_config.read_cli(argc, argv, &m_input_files, &opt_order)) { + this->print_help(); + return false; + } + // Parse actions and transform options. + for (auto const &opt_key : opt_order) { + if (cli_actions_config_def.has(opt_key)) + m_actions.emplace_back(opt_key); + if (cli_transform_config_def.has(opt_key)) + m_transforms.emplace_back(opt_key); + } - cli_config.apply(all_config, true); - set_data_dir(cli_config.datadir.value); - - // Load the extra config values. - DynamicPrintConfig extra_config; - extra_config.apply(all_config, true); - - // load config files supplied via --load - DynamicPrintConfig print_config; - for (const std::string &file : cli_config.load.values) { - if (! boost::filesystem::exists(file)) { - boost::nowide::cout << "No such file: " << file << std::endl; - exit(1); - } - DynamicPrintConfig c; - try { - c.load(file); - } catch (std::exception &e) { - boost::nowide::cout << "Error while reading config file: " << e.what() << std::endl; - exit(1); - } - c.normalize(); - print_config.apply(c); + { + const ConfigOptionInt *opt_loglevel = m_config.opt("loglevel"); + if (opt_loglevel != 0) + set_logging_level(opt_loglevel->value); } - if ((input_files.empty() || cli_config.gui.value) && ! cli_config.no_gui.value && ! cli_config.help.value && cli_config.save.value.empty()) { -#if 1 - GUI::GUI_App *gui = new GUI::GUI_App(); - GUI::GUI_App::SetInstance(gui); - gui->CallAfter([gui, &input_files, &cli_config, &extra_config, &print_config] { - if (! gui->initialized()) { - return; - } -#if 0 - // Load the cummulative config over the currently active profiles. - //FIXME if multiple configs are loaded, only the last one will have an effect. - // We need to decide what to do about loading of separate presets (just print preset, just filament preset etc). - // As of now only the full configs are supported here. - if (! print_config.empty()) - gui->mainframe->load_config(print_config); -#endif - if (! cli_config.load.values.empty()) - // Load the last config to give it a name at the UI. The name of the preset may be later - // changed by loading an AMF or 3MF. - //FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config. - gui->mainframe->load_config_file(cli_config.load.values.back()); - // If loading a 3MF file, the config is loaded from the last one. - gui->plater()->load_files(input_files, true, true); - if (! extra_config.empty()) - gui->mainframe->load_config(extra_config); - }); - return wxEntry(argc, argv); -#else - std::cout << "GUI support has not been built." << "\n"; - return -1; -#endif - } + // Initialize with defaults. + for (const t_optiondef_map *options : { &cli_actions_config_def.options, &cli_transform_config_def.options, &cli_misc_config_def.options }) + for (const std::pair &optdef : *options) + m_config.optptr(optdef.first, true); - // apply command line options to a more specific DynamicPrintConfig which provides normalize() - // (command line options override --load files) - print_config.apply(extra_config, true); - - // write config if requested - if (! cli_config.save.value.empty()) { - print_config.normalize(); - print_config.save(cli_config.save.value); - } + set_data_dir(m_config.opt_string("datadir")); - if (cli_config.help) { - printUsage(); - return 0; - } - - // read input file(s) if any - std::vector models; - for (const t_config_option_key &file : input_files) { - if (! boost::filesystem::exists(file)) { - boost::nowide::cerr << "No such file: " << file << std::endl; - exit(1); - } - Model model; - try { - model = Model::read_from_file(file, &print_config, true); - } catch (std::exception &e) { - boost::nowide::cerr << file << ": " << e.what() << std::endl; - exit(1); - } - if (model.objects.empty()) { - boost::nowide::cerr << "Error: file is empty: " << file << std::endl; - continue; - } - model.add_default_instances(); - // apply command line transform options - for (ModelObject* o : model.objects) { -/* - if (cli_config.scale_to_fit.is_positive_volume()) - o->scale_to_fit(cli_config.scale_to_fit.value); -*/ - // TODO: honor option order? - o->scale(cli_config.scale.value); - o->rotate(Geometry::deg2rad(cli_config.rotate_x.value), X); - o->rotate(Geometry::deg2rad(cli_config.rotate_y.value), Y); - o->rotate(Geometry::deg2rad(cli_config.rotate.value), Z); - } - // TODO: handle --merge - models.push_back(model); - } - - for (Model &model : models) { - if (cli_config.info) { - // --info works on unrepaired model - model.print_info(); - } else if (cli_config.export_3mf) { - std::string outfile = cli_config.output.value; - if (outfile.empty()) outfile = model.objects.front()->input_file; - // Check if the file is already a 3mf. - if(outfile.substr(outfile.find_last_of('.'), outfile.length()) == ".3mf") - outfile = outfile.substr(0, outfile.find_last_of('.')) + "_2" + ".3mf"; - else - // Remove the previous extension and add .3mf extention. - outfile = outfile.substr(0, outfile.find_last_of('.')) + ".3mf"; - store_3mf(outfile.c_str(), &model, nullptr); - boost::nowide::cout << "File file exported to " << outfile << std::endl; - } else if (cli_config.cut > 0) { - model.repair(); - model.translate(0, 0, - model.bounding_box().min(2)); - if (! model.objects.empty()) { - // XXX - // Model out; - // model.objects.front()->cut(cli_config.cut, &out); - // ModelObject &upper = *out.objects[0]; - // ModelObject &lower = *out.objects[1]; - // // Use the input name and trim off the extension. - // std::string outfile = cli_config.output.value; - // if (outfile.empty()) - // outfile = model.objects.front()->input_file; - // outfile = outfile.substr(0, outfile.find_last_of('.')); - // std::cerr << outfile << "\n"; - // if (upper.facets_count() > 0) - // upper.mesh().write_binary((outfile + "_upper.stl").c_str()); - // if (lower.facets_count() > 0) - // lower.mesh().write_binary((outfile + "_lower.stl").c_str()); - } - } else if (cli_config.slice) { - PrinterTechnology printer_technology = print_config.option>("printer_technology", true)->value; - std::string outfile = cli_config.output.value; - Print fff_print; - SLAPrint sla_print; - PrintBase *print = (printer_technology == ptFFF) ? static_cast(&fff_print) : static_cast(&sla_print); - if (! cli_config.dont_arrange) { - //FIXME make the min_object_distance configurable. - model.arrange_objects(fff_print.config().min_object_distance()); - model.center_instances_around_point(cli_config.print_center); - } - if (printer_technology == ptFFF) { - for (auto* mo : model.objects) - fff_print.auto_assign_extruders(mo); - } - print_config.normalize(); - print->apply(model, print_config); - std::string err = print->validate(); - if (err.empty()) { - if (printer_technology == ptFFF) { - // The outfile is processed by a PlaceholderParser. - fff_print.export_gcode(outfile, nullptr); - } else { - assert(printer_technology == ptSLA); - //FIXME add the output here - } - } else - std::cerr << err << "\n"; - } else { - boost::nowide::cerr << "error: command not supported" << std::endl; - return 1; - } - } - - return 0; + return true; } -void printUsage() +void CLI::print_help(bool include_print_options) const { - std::cout << "Slic3r " << SLIC3R_VERSION << " is a STL-to-GCODE translator for RepRap 3D printers" << "\n" - << "written by Alessandro Ranellucci - http://slic3r.org/ - https://github.com/slic3r/Slic3r" << "\n" -// << "Git Version " << BUILD_COMMIT << "\n\n" - << "Usage: ./slic3r [ OPTIONS ] [ file.stl ] [ file2.stl ] ..." << "\n"; - // CLI Options - std::cout << "** CLI OPTIONS **\n"; - print_cli_options(boost::nowide::cout); - std::cout << "****\n"; - // Print options - std::cout << "** PRINT OPTIONS **\n"; - print_print_options(boost::nowide::cout); - std::cout << "****\n"; + boost::nowide::cout + << "Slic3r Prusa Edition " << SLIC3R_BUILD << std::endl + << "https://github.com/prusa3d/Slic3r" << std::endl << std::endl + << "Usage: slic3r [ ACTIONS ] [ TRANSFORM ] [ OPTIONS ] [ file.stl ... ]" << std::endl + << std::endl + << "Actions:" << std::endl; + cli_actions_config_def.print_cli_help(boost::nowide::cout, false); + + boost::nowide::cout + << std::endl + << "Transform options:" << std::endl; + cli_transform_config_def.print_cli_help(boost::nowide::cout, false); + + boost::nowide::cout + << std::endl + << "Other options:" << std::endl; + cli_misc_config_def.print_cli_help(boost::nowide::cout, false); + + if (include_print_options) { + boost::nowide::cout << std::endl; + print_config_def.print_cli_help(boost::nowide::cout, true); + } else { + boost::nowide::cout + << std::endl + << "Run --help-options to see the full listing of print/G-code options." << std::endl; + } +} + +bool CLI::export_models(IO::ExportFormat format) +{ + for (Model &model : m_models) { + const std::string path = this->output_filepath(model, format); + bool success = false; + switch (format) { + case IO::AMF: success = Slic3r::store_amf(path.c_str(), &model, nullptr); break; + case IO::OBJ: success = Slic3r::store_obj(path.c_str(), &model); break; + case IO::STL: success = Slic3r::store_stl(path.c_str(), &model, true); break; + case IO::TMF: success = Slic3r::store_3mf(path.c_str(), &model, nullptr); break; + default: assert(false); break; + } + if (success) + std::cout << "File exported to " << path << std::endl; + else { + std::cerr << "File export to " << path << " failed" << std::endl; + return false; + } + } + return true; +} + +std::string CLI::output_filepath(const Model &model, IO::ExportFormat format) const +{ + std::string ext; + switch (format) { + case IO::AMF: ext = ".amf"; break; + case IO::OBJ: ext = ".obj"; break; + case IO::STL: ext = ".stl"; break; + case IO::TMF: ext = ".3mf"; break; + default: assert(false); break; + }; + auto proposed_path = boost::filesystem::path(model.propose_export_file_name_and_path(ext)); + // use --output when available + std::string cmdline_param = m_config.opt_string("output", false); + if (! cmdline_param.empty()) { + // if we were supplied a directory, use it and append our automatically generated filename + boost::filesystem::path cmdline_path(cmdline_param); + if (boost::filesystem::is_directory(cmdline_path)) + proposed_path = cmdline_path / proposed_path.filename(); + else + proposed_path = cmdline_path; + } + return proposed_path.string(); } #ifdef _MSC_VER @@ -309,7 +634,12 @@ extern "C" { for (size_t i = 0; i < argc; ++ i) argv_ptrs[i] = const_cast(argv_narrow[i].data()); // Call the UTF8 main. - return slic3r_main_(argc, argv_ptrs.data()); + return CLI().run(argc, argv_ptrs.data()); } } +#else /* _MSC_VER */ +int main(int argc, char **argv) +{ + return CLI().run(argc, argv); +} #endif /* _MSC_VER */ diff --git a/src/slic3r/GUI/PresetBundle.cpp b/src/slic3r/GUI/PresetBundle.cpp index 0ee7a5c6c2..f0bb4de01e 100644 --- a/src/slic3r/GUI/PresetBundle.cpp +++ b/src/slic3r/GUI/PresetBundle.cpp @@ -1289,7 +1289,7 @@ void PresetBundle::update_compatible(bool select_other_if_incompatible) { const Preset &printer_preset = this->printers.get_edited_preset(); - switch (printers.get_edited_preset().printer_technology()) { + switch (printer_preset.printer_technology()) { case ptFFF: { assert(printer_preset.config.has("default_print_profile")); diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index bcf84b9571..da1d7abbf8 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -115,6 +115,8 @@ SV* ConfigOption_to_SV(const ConfigOption &opt, const ConfigOptionDef &def) } case coPoint: return perl_to_SV_clone_ref(static_cast(&opt)->value); + case coPoint3: + return perl_to_SV_clone_ref(static_cast(&opt)->value); case coPoints: { auto optv = static_cast(&opt); @@ -248,6 +250,8 @@ bool ConfigBase__set(ConfigBase* THIS, const t_config_option_key &opt_key, SV* v } case coPoint: return from_SV_check(value, &static_cast(opt)->value); + case coPoint3: + return from_SV_check(value, &static_cast(&opt)->value); case coPoints: { std::vector &values = static_cast(opt)->values; diff --git a/xs/xsp/Config.xsp b/xs/xsp/Config.xsp index d5d2958397..017c8dad43 100644 --- a/xs/xsp/Config.xsp +++ b/xs/xsp/Config.xsp @@ -159,6 +159,8 @@ print_config_def() opt_type = "s@"; } else if (optdef->type == coPoint || optdef->type == coPoints) { opt_type = "point"; + } else if (optdef.type == coPoint3) { + opt_type = "point3"; } else if (optdef->type == coBool || optdef->type == coBools) { opt_type = "bool"; } else if (optdef->type == coEnum) {