Use ModelWipeTower instead of config for wipe tower pos and rot (3MF part) - SPE-2520

This commit is contained in:
Lukas Matena 2024-10-15 22:57:22 +02:00
parent c41290bc07
commit 9237a68ba9
11 changed files with 191 additions and 49 deletions

View File

@ -90,6 +90,7 @@ const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/Prusa_Slicer_layer_config
const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt";
const std::string SLA_DRAIN_HOLES_FILE = "Metadata/Slic3r_PE_sla_drain_holes.txt";
const std::string CUSTOM_GCODE_PER_PRINT_Z_FILE = "Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml";
const std::string WIPE_TOWER_INFORMATION_FILE = "Metadata/Prusa_Slicer_wipe_tower_information.xml";
const std::string CUT_INFORMATION_FILE = "Metadata/Prusa_Slicer_cut_information.xml";
static constexpr const char *RELATIONSHIP_TAG = "Relationship";
@ -554,6 +555,8 @@ namespace Slic3r {
void _extract_sla_drain_holes_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_custom_gcode_per_print_z_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_wipe_tower_information_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model);
void _extract_wipe_tower_information_from_archive_legacy(::mz_zip_archive &archive, const mz_zip_archive_file_stat &stat, Model& model);
void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, ConfigSubstitutionContext& subs_context, const std::string& archive_filename);
bool _extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model);
@ -766,6 +769,9 @@ namespace Slic3r {
}
}
// Initialize the wipe tower position (see the end of this function):
model.wipe_tower.position.x() = std::numeric_limits<double>::max();
// Read root model file
if (start_part_stat.m_file_index < num_entries) {
try {
@ -822,6 +828,10 @@ namespace Slic3r {
// extract slic3r layer config ranges file
_extract_custom_gcode_per_print_z_from_archive(archive, stat);
}
else if (boost::algorithm::iequals(name, WIPE_TOWER_INFORMATION_FILE)) {
// extract wipe tower information file
_extract_wipe_tower_information_from_archive(archive, stat, model);
}
else if (boost::algorithm::iequals(name, MODEL_CONFIG_FILE)) {
// extract slic3r model config file
if (!_extract_model_config_from_archive(archive, stat, model)) {
@ -836,6 +846,28 @@ namespace Slic3r {
}
}
if (model.wipe_tower.position.x() == std::numeric_limits<double>::max()) {
// This is apparently an old project from before PS 2.9.0, which saved wipe tower pos and rotation
// into config, not into Model. Try to load it from the config file.
// First set default in case we do not find it (these were the default values of the config options).
model.wipe_tower.position.x() = 180;
model.wipe_tower.position.y() = 140;
model.wipe_tower.rotation = 0.;
for (mz_uint i = 0; i < num_entries; ++i) {
if (mz_zip_reader_file_stat(&archive, i, &stat)) {
std::string name(stat.m_filename);
std::replace(name.begin(), name.end(), '\\', '/');
if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE)) {
_extract_wipe_tower_information_from_archive_legacy(archive, stat, model);
break;
}
}
}
}
close_zip_reader(&archive);
if (m_version == 0) {
@ -1615,6 +1647,77 @@ namespace Slic3r {
}
}
void _3MF_Importer::_extract_wipe_tower_information_from_archive(::mz_zip_archive &archive, const mz_zip_archive_file_stat &stat, Model& model)
{
if (stat.m_uncomp_size > 0) {
std::string buffer((size_t)stat.m_uncomp_size, 0);
mz_bool res = mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
if (res == 0) {
add_error("Error while reading wipe tower information data to buffer");
return;
}
std::istringstream iss(buffer); // wrap returned xml to istringstream
pt::ptree main_tree;
pt::read_xml(iss, main_tree);
try {
auto& node = main_tree.get_child("wipe_tower_information");
double pos_x = node.get<double>("<xmlattr>.position_x");
double pos_y = node.get<double>("<xmlattr>.position_y");
double rot_deg = node.get<double>("<xmlattr>.rotation_deg");
model.wipe_tower.position = Vec2d(pos_x, pos_y);
model.wipe_tower.rotation = rot_deg;
} catch (const boost::property_tree::ptree_bad_path&) {
// Handles missing node or attribute.
add_error("Error while reading wipe tower information.");
return;
}
}
}
void _3MF_Importer::_extract_wipe_tower_information_from_archive_legacy(::mz_zip_archive &archive, const mz_zip_archive_file_stat &stat, Model& model)
{
if (stat.m_uncomp_size > 0) {
std::string buffer((size_t)stat.m_uncomp_size, 0);
mz_bool res = mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
if (res == 0) {
add_error("Error while reading config data to buffer");
return;
}
// Try to find wipe tower data in the config, where pre-2.9.0 slicers saved them.
// Do not load the config as usual, it no longer knows those values.
std::istringstream iss(buffer);
std::string line;
while (iss) {
std::getline(iss, line);
boost::algorithm::trim_left_if(line, [](char ch) { return std::isspace(ch) || ch == ';'; });
if (boost::starts_with(line, "wipe_tower_x") || boost::starts_with(line, "wipe_tower_y") || boost::starts_with(line, "wipe_tower_rotation_angle")) {
std::string value_str;
try {
value_str = line.substr(line.find("=") + 1, std::string::npos);
} catch (const std::out_of_range&) {
continue;
}
double val = 0.;
std::istringstream value_ss(value_str);
value_ss >> val;
if (! value_ss.fail()) {
if (boost::starts_with(line, "wipe_tower_x"))
model.wipe_tower.position.x() = val;
else if (boost::starts_with(line, "wipe_tower_y"))
model.wipe_tower.position.y() = val;
else
model.wipe_tower.rotation = val;
}
}
}
}
}
void XMLCALL _3MF_Importer::_handle_start_relationships_element(void *userData, const char *name, const char **attributes)
{
_3MF_Importer *importer = (_3MF_Importer *) userData;
@ -2645,9 +2748,10 @@ namespace Slic3r {
bool _add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model);
bool _add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model);
bool _add_sla_drain_holes_file_to_archive(mz_zip_archive& archive, Model& model);
bool _add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config);
bool _add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config, const Model& model);
bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, const IdToObjectDataMap &objects_data);
bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config);
bool _add_wipe_tower_information_file_to_archive( mz_zip_archive& archive, Model& model);
};
bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64)
@ -2754,10 +2858,19 @@ namespace Slic3r {
return false;
}
// Adds wipe tower information ("Metadata/Prusa_Slicer_wipe_tower_information.xml").
if (!_add_wipe_tower_information_file_to_archive(archive, model)) {
close_zip_writer(&archive);
boost::filesystem::remove(filename);
return false;
}
// Adds slic3r print config file ("Metadata/Slic3r_PE.config").
// This file contains the content of FullPrintConfing / SLAFullPrintConfig.
if (config != nullptr) {
if (!_add_print_config_file_to_archive(archive, *config)) {
if (!_add_print_config_file_to_archive(archive, *config, model)) {
close_zip_writer(&archive);
boost::filesystem::remove(filename);
return false;
@ -3456,16 +3569,39 @@ namespace Slic3r {
return true;
}
bool _3MF_Exporter::_add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config)
bool _3MF_Exporter::_add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config, const Model& model)
{
assert(is_decimal_separator_point());
char buffer[1024];
sprintf(buffer, "; %s\n\n", header_slic3r_generated().c_str());
std::string out = buffer;
for (const std::string &key : config.keys())
if (key != "compatible_printers")
out += "; " + key + " = " + config.opt_serialize(key) + "\n";
t_config_option_keys keys = config.keys();
// Wipe tower values were historically stored in the config, but they were moved into
// Model in PS 2.9.0. Keep saving the old values to maintain forward compatibility.
for (const std::string s : {"wipe_tower_x", "wipe_tower_y", "wipe_tower_rotation_angle"})
if (! config.has(s))
keys.emplace_back(s);
sort_remove_duplicates(keys);
for (const std::string& key : keys) {
if (key == "compatible_printers")
continue;
std::string opt_serialized;
if (key == "wipe_tower_x")
opt_serialized = float_to_string_decimal_point(model.wipe_tower.position.x());
else if (key == "wipe_tower_y")
opt_serialized = float_to_string_decimal_point(model.wipe_tower.position.y());
else if (key == "wipe_tower_rotation_angle")
opt_serialized = float_to_string_decimal_point(model.wipe_tower.rotation);
else
opt_serialized = config.opt_serialize(key);
out += "; " + key + " = " + opt_serialized + "\n";
}
if (!out.empty()) {
if (!mz_zip_writer_add_mem(&archive, PRINT_CONFIG_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) {
@ -3663,6 +3799,34 @@ bool _3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archiv
return true;
}
bool _3MF_Exporter::_add_wipe_tower_information_file_to_archive( mz_zip_archive& archive, Model& model)
{
std::string out = "";
pt::ptree tree;
pt::ptree& main_tree = tree.add("wipe_tower_information", "");
main_tree.put("<xmlattr>.position_x", model.wipe_tower.position.x());
main_tree.put("<xmlattr>.position_y", model.wipe_tower.position.y());
main_tree.put("<xmlattr>.rotation_deg", model.wipe_tower.rotation);
std::ostringstream oss;
boost::property_tree::write_xml(oss, tree);
out = oss.str();
// Post processing("beautification") of the output string
boost::replace_all(out, "><", ">\n<");
if (!out.empty()) {
if (!mz_zip_writer_add_mem(&archive, WIPE_TOWER_INFORMATION_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) {
add_error("Unable to add wipe tower information file to archive");
return false;
}
}
return true;
}
// Perform conversions based on the config values available.
static void handle_legacy_project_loaded(unsigned int version_project_file, DynamicPrintConfig& config, const boost::optional<Semver>& prusaslicer_generator_version)
{

View File

@ -69,6 +69,7 @@ Model& Model::assign_copy(const Model &rhs)
// copy custom code per height
this->custom_gcode_per_print_z = rhs.custom_gcode_per_print_z;
this->wipe_tower = rhs.wipe_tower;
return *this;
}
@ -90,6 +91,7 @@ Model& Model::assign_copy(Model &&rhs)
// copy custom code per height
this->custom_gcode_per_print_z = std::move(rhs.custom_gcode_per_print_z);
this->wipe_tower = rhs.wipe_tower;
return *this;
}

View File

@ -1190,6 +1190,8 @@ private:
};
// Note: The following class does not have to inherit from ObjectID, it is currently
// only used for arrangement. It might be good to refactor this in future.
class ModelWipeTower final : public ObjectBase
{
public:
@ -1199,6 +1201,9 @@ public:
bool operator==(const ModelWipeTower& other) const { return position == other.position && rotation == other.rotation; }
bool operator!=(const ModelWipeTower& other) const { return !((*this) == other); }
// Assignment operator does not touch the ID!
ModelWipeTower& operator=(const ModelWipeTower& rhs) { position = rhs.position; rotation = rhs.rotation; return *this; }
private:
friend class cereal::access;
@ -1214,9 +1219,8 @@ private:
// Copy constructor copies the ID.
explicit ModelWipeTower(const ModelWipeTower &cfg) = default;
// Disabled methods.
ModelWipeTower(ModelWipeTower &&rhs) = delete;
ModelWipeTower& operator=(const ModelWipeTower &rhs) = delete;
// Disabled methods.
ModelWipeTower(ModelWipeTower &&rhs) = delete;
ModelWipeTower& operator=(ModelWipeTower &&rhs) = delete;
// For serialization / deserialization of ModelWipeTower composed into another class into the Undo / Redo stack as a separate object.

View File

@ -485,8 +485,8 @@ static std::vector<std::string> s_Preset_print_options {
"perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width",
"top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "infill_anchor", "infill_anchor_max", "bridge_flow_ratio",
"elefant_foot_compensation", "xy_size_compensation", "resolution", "gcode_resolution", "arc_fitting",
"wipe_tower", "wipe_tower_x", "wipe_tower_y",
"wipe_tower_width", "wipe_tower_cone_angle", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width",
"wipe_tower",
"wipe_tower_width", "wipe_tower_cone_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width",
"mmu_segmented_region_interlocking_depth", "wipe_tower_extruder", "wipe_tower_no_sparse_layers", "wipe_tower_extra_flow", "wipe_tower_extra_spacing", "compatible_printers", "compatible_printers_condition", "inherits",
"perimeter_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle",
"wall_distribution_count", "min_feature_size", "min_bead_width",

View File

@ -199,10 +199,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|| opt_key == "draft_shield"
|| opt_key == "skirt_distance"
|| opt_key == "min_skirt_length"
|| opt_key == "ooze_prevention"
|| opt_key == "wipe_tower_x"
|| opt_key == "wipe_tower_y"
|| opt_key == "wipe_tower_rotation_angle") {
|| opt_key == "ooze_prevention") {
steps.emplace_back(psSkirtBrim);
} else if (
opt_key == "first_layer_height"

View File

@ -1070,8 +1070,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
// Check the position and rotation of the wipe tower.
if (model.wipe_tower != m_model.wipe_tower)
update_apply_status(this->invalidate_step(psSkirtBrim));
m_model.wipe_tower.position = model.wipe_tower.position;
m_model.wipe_tower.rotation = model.wipe_tower.rotation;
m_model.wipe_tower = model.wipe_tower;
ModelObjectStatusDB model_object_status_db;

View File

@ -3547,20 +3547,6 @@ void PrintConfigDef::init_fff_params()
def->tooltip = "";
def->set_default_value(new ConfigOptionBool{ false });
def = this->add("wipe_tower_x", coFloat);
def->label = L("Position X");
def->tooltip = L("X coordinate of the left front corner of a wipe tower");
def->sidetext = L("mm");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(180.));
def = this->add("wipe_tower_y", coFloat);
def->label = L("Position Y");
def->tooltip = L("Y coordinate of the left front corner of a wipe tower");
def->sidetext = L("mm");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(140.));
def = this->add("wipe_tower_width", coFloat);
def->label = L("Width");
def->tooltip = L("Width of a wipe tower");
@ -3568,13 +3554,6 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(60.));
def = this->add("wipe_tower_rotation_angle", coFloat);
def->label = L("Wipe tower rotation angle");
def->tooltip = L("Wipe tower rotation angle with respect to x-axis.");
def->sidetext = L("°");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0.));
def = this->add("wipe_tower_brim_width", coFloat);
def->label = L("Wipe tower brim width");
def->tooltip = L("Wipe tower brim width");
@ -4821,7 +4800,8 @@ static std::set<std::string> PrintConfigDef_ignore = {
// Disabled in 2.6.0-alpha6, this option is problematic
"infill_only_where_needed",
"gcode_binary", // Introduced in 2.7.0-alpha1, removed in 2.7.1 (replaced by binary_gcode).
"wiping_volumes_extruders" // Removed in 2.7.3-alpha1.
"wiping_volumes_extruders", // Removed in 2.7.3-alpha1.
"wipe_tower_x", "wipe_tower_y", "wipe_tower_rotation_angle" // Removed in 2.9.0
};
void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value)

View File

@ -965,11 +965,8 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
((ConfigOptionBools, wipe))
((ConfigOptionBool, wipe_tower))
((ConfigOptionFloat, wipe_tower_acceleration))
((ConfigOptionFloat, wipe_tower_x))
((ConfigOptionFloat, wipe_tower_y))
((ConfigOptionFloat, wipe_tower_width))
((ConfigOptionFloat, wipe_tower_per_color_wipe))
((ConfigOptionFloat, wipe_tower_rotation_angle))
((ConfigOptionFloat, wipe_tower_brim_width))
((ConfigOptionFloat, wipe_tower_cone_angle))
((ConfigOptionPercent, wipe_tower_extra_spacing))

View File

@ -324,7 +324,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
toggle_field("standby_temperature_delta", have_ooze_prevention);
bool have_wipe_tower = config->opt_bool("wipe_tower");
for (auto el : { "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_cone_angle",
for (auto el : { "wipe_tower_width", "wipe_tower_brim_width", "wipe_tower_cone_angle",
"wipe_tower_extra_spacing", "wipe_tower_extra_flow", "wipe_tower_bridging", "wipe_tower_no_sparse_layers", "single_extruder_multi_material_priming" })
toggle_field(el, have_wipe_tower);

View File

@ -614,7 +614,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
, config(Slic3r::DynamicPrintConfig::new_from_defaults_keys({
"bed_shape", "bed_custom_texture", "bed_custom_model", "complete_objects", "duplicate_distance", "extruder_clearance_radius", "skirts", "skirt_distance",
"brim_width", "brim_separation", "brim_type", "variable_layer_height", "nozzle_diameter", "single_extruder_multi_material",
"wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_cone_angle", "wipe_tower_extra_spacing", "wipe_tower_extra_flow", "wipe_tower_extruder",
"wipe_tower", "wipe_tower_width", "wipe_tower_brim_width", "wipe_tower_cone_angle", "wipe_tower_extra_spacing", "wipe_tower_extra_flow", "wipe_tower_extruder",
"extruder_colour", "filament_colour", "material_colour", "max_print_height", "printer_model", "printer_notes", "printer_technology",
// These values are necessary to construct SlicingParameters by the Canvas3D variable layer height editor.
"layer_height", "first_layer_height", "min_layer_height", "max_layer_height",
@ -1300,8 +1300,10 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
if (! config_substitutions.empty())
show_substitutions_info(config_substitutions.substitutions, filename.string());
if (load_config)
if (load_config) {
this->model.custom_gcode_per_print_z = model.custom_gcode_per_print_z;
this->model.wipe_tower = model.wipe_tower;
}
}
if (load_config) {

View File

@ -1635,10 +1635,7 @@ void TabPrint::build()
optgroup = page->new_optgroup(L("Wipe tower"));
optgroup->append_single_option_line("wipe_tower");
optgroup->append_single_option_line("wipe_tower_x");
optgroup->append_single_option_line("wipe_tower_y");
optgroup->append_single_option_line("wipe_tower_width");
optgroup->append_single_option_line("wipe_tower_rotation_angle");
optgroup->append_single_option_line("wipe_tower_width");
optgroup->append_single_option_line("wipe_tower_brim_width");
optgroup->append_single_option_line("wipe_tower_bridging");
optgroup->append_single_option_line("wipe_tower_cone_angle");