Reworked the command line interface based on the current state

of the upstream.
Thanks @alexrj, @lordofhyphens for the original code of slic3r.cpp
This commit is contained in:
bubnikv 2019-03-13 15:44:50 +01:00
parent 75cf1cde92
commit 18025cc669
22 changed files with 1131 additions and 530 deletions

View file

@ -190,6 +190,110 @@ bool unescape_strings_cstyle(const std::string &str, std::vector<std::string> &o
}
}
std::vector<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> &tokens, t_config_option_keys* extra)
void DynamicConfig::read_cli(const std::vector<std::string> &tokens, t_config_option_keys* extra, t_config_option_keys* keys)
{
std::vector<char*> args;
// push a bogus executable name (argv[0])
args.emplace_back(const_cast<char*>(""));
for (size_t i = 0; i < tokens.size(); ++ i)
args.emplace_back(const_cast<char *>(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<std::string,std::string> 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<std::string> 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<ConfigOptionVectorBase*>(opt_base) : nullptr;
if (opt_vector) {

View file

@ -27,6 +27,24 @@ extern std::string escape_strings_cstyle(const std::vector<std::string> &strs);
extern bool unescape_string_cstyle(const std::string &str, std::string &out);
extern bool unescape_strings_cstyle(const std::string &str, std::vector<std::string> &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<Vec3d>
{
public:
ConfigOptionPoint3() : ConfigOptionSingle<Vec3d>(Vec3d(0,0,0)) {}
explicit ConfigOptionPoint3(const Vec3d &value) : ConfigOptionSingle<Vec3d>(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<bool>
{
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<std::string> 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<TYPE*>(opt);
}
template<typename TYPE>
const TYPE* option(const t_config_option_key &opt_key) const
{ return const_cast<ConfigBase*>(this)->option<TYPE>(opt_key, false); }
template<typename TYPE>
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<TYPE*>(opt);
}
template<typename TYPE>
const TYPE* option_throw(const t_config_option_key &opt_key) const
{ return const_cast<ConfigBase*>(this)->option_throw<TYPE>(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<ConfigOptionBools>(opt_key)->get_at(idx) != 0; }
// Command line processing
void read_cli(const std::vector<std::string> &tokens, t_config_option_keys* extra);
bool read_cli(int argc, char** argv, t_config_option_keys* extra);
void read_cli(const std::vector<std::string> &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_config_option_key,ConfigOption*> 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

View file

@ -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

View file

@ -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_ */

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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()));
}

View file

@ -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); }

View file

@ -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<class Slic3r::SLAPrintObjectConfig> SLAPrintObje
StaticPrintConfig::StaticCache<class Slic3r::SLAPrinterConfig> SLAPrinterConfig::s_cache_SLAPrinterConfig;
StaticPrintConfig::StaticCache<class Slic3r::SLAFullPrintConfig> 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);
}
}
}

View file

@ -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

View file

@ -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<PrintObjectStatus&>(*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<PrintObjectStatus&>(*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<SLAPrintObject::Instance> new_instances = sla_instances(model_object);
if (it_print_object_status != print_object_status.end() && it_print_object_status->status != PrintObjectStatus::Deleted) {

View file

@ -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<IntersectionLine> &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
{

View file

@ -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);