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

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

View File

@ -177,10 +177,10 @@ extern void stl_transform(stl_file *stl, const Eigen::Transform<double, 3, Eigen
extern void stl_open_merge(stl_file *stl, char *file);
extern void stl_invalidate_shared_vertices(stl_file *stl);
extern void stl_generate_shared_vertices(stl_file *stl);
extern void stl_write_obj(stl_file *stl, char *file);
extern void stl_write_off(stl_file *stl, char *file);
extern void stl_write_dxf(stl_file *stl, char *file, char *label);
extern void stl_write_vrml(stl_file *stl, char *file);
extern void stl_write_obj(stl_file *stl, const char *file);
extern void stl_write_off(stl_file *stl, const char *file);
extern void stl_write_dxf(stl_file *stl, const char *file, char *label);
extern void stl_write_vrml(stl_file *stl, const char *file);
inline void stl_calculate_normal(stl_normal &normal, stl_facet *facet) {
normal = (facet->vertex[1] - facet->vertex[0]).cross(facet->vertex[2] - facet->vertex[0]);
}

View File

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

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

View File

@ -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<PrinterTechnology> *opt = config.option<ConfigOptionEnum<PrinterTechnology>>("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<std::string> &load_configs = m_config.option<ConfigOptionStrings>("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<int> &ints = m_config.option<ConfigOptionInts>("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<ConfigOptionPoint>("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<ConfigOptionPoint>("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<ConfigOptionPoint3>(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<Model> 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<Model> new_models;
for (auto &model : m_models) {
TriangleMesh mesh = model.mesh();
mesh.repair();
TriangleMeshPtrs meshes = mesh.cut_by_grid(m_config.option<ConfigOptionPoint>("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<PrintBase*>(&fff_print) : static_cast<PrintBase*>(&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<ConfigOptionPoint>("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<SLAZipFmt>(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<double, std::ratio<1> > second_;
std::chrono::time_point<clock_> 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<second_>(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<ConfigOptionInt>("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<t_config_option_key, ConfigOptionDef> &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<Model> 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<ConfigOptionEnum<PrinterTechnology>>("printer_technology", true)->value;
std::string outfile = cli_config.output.value;
Print fff_print;
SLAPrint sla_print;
PrintBase *print = (printer_technology == ptFFF) ? static_cast<PrintBase*>(&fff_print) : static_cast<PrintBase*>(&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 <aar@cpan.org> - 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<char*>(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 */

View File

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

View File

@ -115,6 +115,8 @@ SV* ConfigOption_to_SV(const ConfigOption &opt, const ConfigOptionDef &def)
}
case coPoint:
return perl_to_SV_clone_ref(static_cast<const ConfigOptionPoint*>(&opt)->value);
case coPoint3:
return perl_to_SV_clone_ref(static_cast<const ConfigOptionPoint3*>(&opt)->value);
case coPoints:
{
auto optv = static_cast<const ConfigOptionPoints*>(&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<ConfigOptionPoint*>(opt)->value);
case coPoint3:
return from_SV_check(value, &static_cast<const ConfigOptionPoint3*>(&opt)->value);
case coPoints:
{
std::vector<Vec2d> &values = static_cast<ConfigOptionPoints*>(opt)->values;

View File

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