Vectorization of the wipe tower

This commit is contained in:
Lukas Matena 2024-10-19 22:55:33 +02:00
parent da13a0a80e
commit fae06e0773
17 changed files with 117 additions and 97 deletions

View File

@ -567,7 +567,7 @@ void ArrangeableSlicerModel::for_each_arrangeable_(Self &&self, Fn &&fn)
template<class Self, class Fn>
void ArrangeableSlicerModel::visit_arrangeable_(Self &&self, const ObjectID &id, Fn &&fn)
{
if (id == self.m_model->wipe_tower.id()) {
if (id == wipe_tower_instance_id(0)) {
self.m_wth->visit(fn);
return;

View File

@ -47,6 +47,8 @@ namespace pt = boost::property_tree;
#include "libslic3r/NSVGUtils.hpp"
#include "libslic3r/MultipleBeds.hpp"
#include <fast_float.h>
// Slightly faster than sprintf("%.9g"), but there is an issue with the karma floating point formatter,
@ -773,7 +775,7 @@ namespace Slic3r {
}
// Initialize the wipe tower position (see the end of this function):
model.wipe_tower.position.x() = std::numeric_limits<double>::max();
model.get_wipe_tower_vector().front().position.x() = std::numeric_limits<double>::max();
// Read root model file
if (start_part_stat.m_file_index < num_entries) {
@ -850,13 +852,13 @@ namespace Slic3r {
}
if (model.wipe_tower.position.x() == std::numeric_limits<double>::max()) {
if (model.get_wipe_tower_vector().front().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.;
model.get_wipe_tower_vector().front().position.x() = 180;
model.get_wipe_tower_vector().front().position.y() = 140;
model.get_wipe_tower_vector().front().rotation = 0.;
for (mz_uint i = 0; i < num_entries; ++i) {
if (mz_zip_reader_file_stat(&archive, i, &stat)) {
@ -1679,17 +1681,29 @@ namespace Slic3r {
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;
for (const auto& bed_block : main_tree) {
if (bed_block.first != "wipe_tower_information")
continue;
try {
int bed_idx = 0;
try {
bed_idx = bed_block.second.get<int>("<xmlattr>.bed_idx");
} catch (const boost::property_tree::ptree_bad_path&) {
// Probably an old project with no bed_idx info - pretend that we saw 0.
}
if (bed_idx >= int(m_model->get_wipe_tower_vector().size()))
continue;
double pos_x = bed_block.second.get<double>("<xmlattr>.position_x");
double pos_y = bed_block.second.get<double>("<xmlattr>.position_y");
double rot_deg = bed_block.second.get<double>("<xmlattr>.rotation_deg");
model.get_wipe_tower_vector()[bed_idx].position = Vec2d(pos_x, pos_y);
model.get_wipe_tower_vector()[bed_idx].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;
}
}
}
@ -1725,11 +1739,11 @@ namespace Slic3r {
value_ss >> val;
if (! value_ss.fail()) {
if (boost::starts_with(line, "wipe_tower_x"))
model.wipe_tower.position.x() = val;
model.get_wipe_tower_vector().front().position.x() = val;
else if (boost::starts_with(line, "wipe_tower_y"))
model.wipe_tower.position.y() = val;
model.get_wipe_tower_vector().front().position.y() = val;
else
model.wipe_tower.rotation = val;
model.get_wipe_tower_vector().front().rotation = val;
}
}
}
@ -3621,11 +3635,11 @@ namespace Slic3r {
std::string opt_serialized;
if (key == "wipe_tower_x")
opt_serialized = float_to_string_decimal_point(model.wipe_tower.position.x());
opt_serialized = float_to_string_decimal_point(model.get_wipe_tower_vector().front().position.x());
else if (key == "wipe_tower_y")
opt_serialized = float_to_string_decimal_point(model.wipe_tower.position.y());
opt_serialized = float_to_string_decimal_point(model.get_wipe_tower_vector().front().position.y());
else if (key == "wipe_tower_rotation_angle")
opt_serialized = float_to_string_decimal_point(model.wipe_tower.rotation);
opt_serialized = float_to_string_decimal_point(model.get_wipe_tower_vector().front().rotation);
else
opt_serialized = config.opt_serialize(key);
@ -3842,11 +3856,19 @@ bool _3MF_Exporter::_add_wipe_tower_information_file_to_archive( mz_zip_archive&
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);
size_t bed_idx = 0;
for (const ModelWipeTower& wipe_tower : model.get_wipe_tower_vector()) {
pt::ptree& main_tree = tree.add("wipe_tower_information", "");
main_tree.put("<xmlattr>.bed_idx", bed_idx);
main_tree.put("<xmlattr>.position_x", wipe_tower.position.x());
main_tree.put("<xmlattr>.position_y", wipe_tower.position.y());
main_tree.put("<xmlattr>.rotation_deg", wipe_tower.rotation);
++bed_idx;
if (bed_idx >= s_multiple_beds.get_number_of_beds())
break;
}
std::ostringstream oss;
boost::property_tree::write_xml(oss, tree);

View File

@ -1314,7 +1314,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
std::vector<std::pair<coordf_t, ObjectsLayerToPrint>> layers_to_print = collect_layers_to_print(print);
// Prusa Multi-Material wipe tower.
if (has_wipe_tower && ! layers_to_print.empty()) {
m_wipe_tower = std::make_unique<GCode::WipeTowerIntegration>(print.model().wipe_tower.position.cast<float>(), print.model().wipe_tower.rotation, print.config(), *print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get());
m_wipe_tower = std::make_unique<GCode::WipeTowerIntegration>(print.model().wipe_tower().position.cast<float>(), print.model().wipe_tower().rotation, print.config(), *print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get());
// Set position for wipe tower generation.
Vec3d new_position = this->writer().get_position();

View File

@ -151,8 +151,8 @@ BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_
// Wipe tower extrusions are saved as if the tower was at the origin with no rotation
// We need to get position and angle of the wipe tower to transform them to actual position.
Transform2d trafo =
Eigen::Translation2d(print.model().wipe_tower.position.x(), print.model().wipe_tower.position.y()) *
Eigen::Rotation2Dd(Geometry::deg2rad(print.model().wipe_tower.rotation));
Eigen::Translation2d(print.model().wipe_tower().position.x(), print.model().wipe_tower().position.y()) *
Eigen::Rotation2Dd(Geometry::deg2rad(print.model().wipe_tower().rotation));
BoundingBoxf bbox;
for (const std::vector<WipeTower::ToolChangeResult> &tool_changes : print.wipe_tower_data().tool_changes) {

View File

@ -70,7 +70,7 @@ Model& Model::assign_copy(const Model &rhs)
// copy custom code per height
this->custom_gcode_per_print_z_vector = rhs.custom_gcode_per_print_z_vector;
this->wipe_tower = rhs.wipe_tower;
this->wipe_tower_vector = rhs.wipe_tower_vector;
return *this;
}
@ -93,7 +93,7 @@ Model& Model::assign_copy(Model &&rhs)
// copy custom code per height
this->custom_gcode_per_print_z_vector = std::move(rhs.custom_gcode_per_print_z_vector);
this->wipe_tower = rhs.wipe_tower;
this->wipe_tower_vector = rhs.wipe_tower_vector;
return *this;
}
@ -120,6 +120,16 @@ void Model::update_links_bottom_up_recursive()
}
}
ModelWipeTower& Model::wipe_tower()
{
return const_cast<ModelWipeTower&>(const_cast<const Model*>(this)->wipe_tower());
}
const ModelWipeTower& Model::wipe_tower() const
{
return wipe_tower_vector[s_multiple_beds.get_active_bed()];
}
CustomGCode::Info& Model::custom_gcode_per_print_z()
{
return const_cast<CustomGCode::Info&>(const_cast<const Model*>(this)->custom_gcode_per_print_z());

View File

@ -1253,7 +1253,7 @@ 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
class ModelWipeTower
{
public:
Vec2d position = Vec2d(180., 140.);
@ -1265,25 +1265,9 @@ public:
// 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;
friend class UndoRedo::StackImpl;
friend class Model;
// Constructors to be only called by derived classes.
// Default constructor to assign a unique ID.
explicit ModelWipeTower() {}
// Constructor with ignored int parameter to assign an invalid ID, to be replaced
// by an existing ID copied from elsewhere.
explicit ModelWipeTower(int) : ObjectBase(-1) {}
// Copy constructor copies the ID.
explicit ModelWipeTower(const ModelWipeTower &cfg) = default;
// 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.
template<typename Archive> void serialize(Archive &ar) { ar(position, rotation); }
};
@ -1301,14 +1285,21 @@ public:
ModelMaterialMap materials;
// Objects are owned by a model. Each model may have multiple instances, each instance having its own transformation (shift, scale, rotation).
ModelObjectPtrs objects;
// Wipe tower object.
ModelWipeTower wipe_tower;
ModelWipeTower& wipe_tower();
const ModelWipeTower& wipe_tower() const;
std::vector<ModelWipeTower>& get_wipe_tower_vector() { return wipe_tower_vector; }
const std::vector<ModelWipeTower>& get_wipe_tower_vector() const { return wipe_tower_vector; }
CustomGCode::Info& custom_gcode_per_print_z();
const CustomGCode::Info& custom_gcode_per_print_z() const;
std::vector<CustomGCode::Info>& get_custom_gcode_per_print_z_vector() { return custom_gcode_per_print_z_vector; }
private:
// Wipe tower object.
std::vector<ModelWipeTower> wipe_tower_vector = std::vector<ModelWipeTower>(MAX_NUMBER_OF_BEDS);
// Extensions for color print
std::vector<CustomGCode::Info> custom_gcode_per_print_z_vector = std::vector<CustomGCode::Info>(MAX_NUMBER_OF_BEDS);
@ -1413,8 +1404,7 @@ private:
friend class cereal::access;
friend class UndoRedo::StackImpl;
template<class Archive> void serialize(Archive &ar) {
Internal::StaticSerializationWrapper<ModelWipeTower> wipe_tower_wrapper(wipe_tower);
ar(materials, objects, wipe_tower_wrapper);
ar(materials, objects, wipe_tower_vector);
}
};

View File

@ -8,17 +8,19 @@ namespace Slic3r {
size_t ObjectBase::s_last_id = 0;
// Unique object / instance ID for the wipe tower.
ObjectID wipe_tower_object_id()
{
static ObjectBase mine;
return mine.id();
}
struct WipeTowerId : public ObjectBase {
// Need to inherit because ObjectBase
// destructor is protected.
using ObjectBase::ObjectBase;
};
ObjectID wipe_tower_instance_id()
ObjectID wipe_tower_instance_id(size_t bed_idx)
{
static ObjectBase mine;
return mine.id();
static std::vector<WipeTowerId> mine;
if (bed_idx >= mine.size()) {
mine.resize(bed_idx + 1);
}
return mine[bed_idx].id();
}
ObjectWithTimestamp::Timestamp ObjectWithTimestamp::s_last_timestamp = 1;

View File

@ -86,9 +86,6 @@ private:
static inline ObjectID generate_new_id() { return ObjectID(++ s_last_id); }
static size_t s_last_id;
friend ObjectID wipe_tower_object_id();
friend ObjectID wipe_tower_instance_id();
friend class cereal::access;
friend class Slic3r::UndoRedo::StackImpl;
@ -135,8 +132,7 @@ private:
};
// Unique object / instance ID for the wipe tower.
extern ObjectID wipe_tower_object_id();
extern ObjectID wipe_tower_instance_id();
ObjectID wipe_tower_instance_id(size_t bed_idx);
} // namespace Slic3r

View File

@ -1048,8 +1048,8 @@ void Print::process()
if (this->has_wipe_tower()) {
// These values have to be updated here, not during wipe tower generation.
// When the wipe tower is moved/rotated, it is not regenerated.
m_wipe_tower_data.position = model().wipe_tower.position;
m_wipe_tower_data.rotation_angle = model().wipe_tower.rotation;
m_wipe_tower_data.position = model().wipe_tower().position;
m_wipe_tower_data.rotation_angle = model().wipe_tower().rotation;
}
auto conflictRes = ConflictChecker::find_inter_of_lines_in_diff_objs(objects(), m_wipe_tower_data);
@ -1278,8 +1278,8 @@ Points Print::first_layer_wipe_tower_corners() const
pts.emplace_back(center + r*Vec2d(std::cos(alpha)/cone_x_scale, std::sin(alpha)));
for (Vec2d& pt : pts) {
pt = Eigen::Rotation2Dd(Geometry::deg2rad(model().wipe_tower.rotation)) * pt;
pt += model().wipe_tower.position;
pt = Eigen::Rotation2Dd(Geometry::deg2rad(model().wipe_tower().rotation)) * pt;
pt += model().wipe_tower().position;
pts_scaled.emplace_back(Point(scale_(pt.x()), scale_(pt.y())));
}
}
@ -1553,7 +1553,7 @@ void Print::_make_wipe_tower()
this->throw_if_canceled();
// Initialize the wipe tower.
WipeTower wipe_tower(model().wipe_tower.position.cast<float>(), model().wipe_tower.rotation, m_config, m_default_region_config, wipe_volumes, m_wipe_tower_data.tool_ordering.first_extruder());
WipeTower wipe_tower(model().wipe_tower().position.cast<float>(), model().wipe_tower().rotation, m_config, m_default_region_config, wipe_volumes, m_wipe_tower_data.tool_ordering.first_extruder());
// Set the extruder & material properties at the wipe tower object.
for (size_t i = 0; i < m_config.nozzle_diameter.size(); ++ i)

View File

@ -1123,9 +1123,9 @@ 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)
if (model.wipe_tower() != m_model.wipe_tower())
update_apply_status(this->invalidate_step(psSkirtBrim));
m_model.wipe_tower = model.wipe_tower;
m_model.wipe_tower() = model.wipe_tower();
ModelObjectStatusDB model_object_status_db;

View File

@ -606,7 +606,7 @@ int GLVolumeCollection::load_wipe_tower_preview(
v.set_volume_rotation(Vec3d(0., 0., (M_PI / 180.) * rotation_angle));
v.composite_id = GLVolume::CompositeID(INT_MAX, 0, 0);
v.geometry_id.first = 0;
v.geometry_id.second = wipe_tower_instance_id().id;
v.geometry_id.second = wipe_tower_instance_id(0).id;
v.is_wipe_tower = true;
v.shader_outside_printer_detection_enabled = !size_unknown;
return int(volumes.size() - 1);

View File

@ -1698,8 +1698,8 @@ void GCodeViewer::load_wipetower_shell(const Print& print)
const std::vector<std::pair<float, float>> z_and_depth_pairs = print.wipe_tower_data(extruders_count).z_and_depth_pairs;
const float brim_width = wipe_tower_data.brim_width;
if (depth != 0.) {
m_shells.volumes.load_wipe_tower_preview(wxGetApp().plater()->model().wipe_tower.position.x(), wxGetApp().plater()->model().wipe_tower.position.y(), config.wipe_tower_width, depth, z_and_depth_pairs,
max_z, config.wipe_tower_cone_angle, wxGetApp().plater()->model().wipe_tower.rotation, false, brim_width);
m_shells.volumes.load_wipe_tower_preview(wxGetApp().plater()->model().wipe_tower().position.x(), wxGetApp().plater()->model().wipe_tower().position.y(), config.wipe_tower_width, depth, z_and_depth_pairs,
max_z, config.wipe_tower_cone_angle, wxGetApp().plater()->model().wipe_tower().rotation, false, brim_width);
GLVolume* volume = m_shells.volumes.volumes.back();
volume->color.a(0.25f);
volume->force_native_color = true;

View File

@ -2454,10 +2454,10 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
if (extruders_count > 1 && wt && !co) {
const float x = m_model->wipe_tower.position.x();
const float y = m_model->wipe_tower.position.y();
const float x = m_model->wipe_tower().position.x();
const float y = m_model->wipe_tower().position.y();
const float w = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_width"))->value;
const float a = m_model->wipe_tower.rotation;
const float a = m_model->wipe_tower().rotation;
const float bw = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_brim_width"))->value;
const float ca = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_cone_angle"))->value;
@ -4042,7 +4042,7 @@ void GLCanvas3D::do_move(const std::string& snapshot_type)
post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_MOVED));
if (wipe_tower_origin != Vec3d::Zero()) {
m_model->wipe_tower.position = Vec2d(wipe_tower_origin[0], wipe_tower_origin[1]);
m_model->wipe_tower().position = Vec2d(wipe_tower_origin[0], wipe_tower_origin[1]);
post_event(SimpleEvent(EVT_GLCANVAS_WIPETOWER_TOUCHED));
}
@ -4088,8 +4088,8 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type)
const Vec3d offset = v->get_volume_offset();
Vec3d rot_unit_x = v->get_volume_transformation().get_matrix().linear() * Vec3d::UnitX();
double z_rot = std::atan2(rot_unit_x.y(), rot_unit_x.x());
m_model->wipe_tower.position = Vec2d(offset.x(), offset.y());
m_model->wipe_tower.rotation = (180./M_PI) * z_rot;
m_model->wipe_tower().position = Vec2d(offset.x(), offset.y());
m_model->wipe_tower().rotation = (180. / M_PI) * z_rot;
}
const int object_idx = v->object_idx();
if (object_idx < 0 || (int)m_model->objects.size() <= object_idx)
@ -4406,9 +4406,9 @@ GLCanvas3D::WipeTowerInfo GLCanvas3D::get_wipe_tower_info() const
for (const GLVolume* vol : m_volumes.volumes) {
if (vol->is_wipe_tower) {
wti.m_pos = Vec2d(m_model->wipe_tower.position.x(),
m_model->wipe_tower.position.y());
wti.m_rotation = (M_PI/180.) * m_model->wipe_tower.rotation;
wti.m_pos = Vec2d(m_model->wipe_tower().position.x(),
m_model->wipe_tower().position.y());
wti.m_rotation = (M_PI/180.) * m_model->wipe_tower().rotation;
const BoundingBoxf3& bb = vol->bounding_box();
wti.m_bb = BoundingBoxf{to_2d(bb.min), to_2d(bb.max)};
break;
@ -7000,8 +7000,8 @@ const SLAPrint* GLCanvas3D::sla_print() const
void GLCanvas3D::WipeTowerInfo::apply_wipe_tower(Vec2d pos, double rot)
{
wxGetApp().plater()->model().wipe_tower.position = pos;
wxGetApp().plater()->model().wipe_tower.rotation = (180./M_PI) * rot;
wxGetApp().plater()->model().wipe_tower().position = pos;
wxGetApp().plater()->model().wipe_tower().rotation = (180. / M_PI) * rot;
}
void GLCanvas3D::RenderTimer::Notify()

View File

@ -180,7 +180,7 @@ arr2::SceneBuilder build_scene(Plater &plater, ArrangeSelectionMode mode)
AnyPtr<WTH> wth;
if (wti) {
wth = std::make_unique<WTH>(plater.model().wipe_tower.id(), wti);
wth = std::make_unique<WTH>(wipe_tower_instance_id(0), wti);
}
if (plater.config()) {

View File

@ -427,8 +427,8 @@ public:
if (wipe_tower_data.final_purge)
m_final.emplace_back(*wipe_tower_data.final_purge.get());
m_angle = print.model().wipe_tower.rotation / 180.0f * PI;
m_position = print.model().wipe_tower.position.cast<float>();
m_angle = print.model().wipe_tower().rotation / 180.0f * PI;
m_position = print.model().wipe_tower().position.cast<float>();
m_layers_count = wipe_tower_data.tool_changes.size() + (m_priming.empty() ? 0 : 1);
}

View File

@ -1330,7 +1330,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
if (load_config) {
this->model.get_custom_gcode_per_print_z_vector() = model.get_custom_gcode_per_print_z_vector();
this->model.wipe_tower = model.wipe_tower;
this->model.get_wipe_tower_vector() = model.get_wipe_tower_vector();
}
}
@ -3388,13 +3388,13 @@ void Plater::priv::on_right_click(RBtnEvent& evt)
void Plater::priv::on_wipetower_moved(Vec3dEvent &evt)
{
model.wipe_tower.position = Vec2d(evt.data[0], evt.data[1]);
model.wipe_tower().position = Vec2d(evt.data[0], evt.data[1]);
}
void Plater::priv::on_wipetower_rotated(Vec3dEvent& evt)
{
model.wipe_tower.position = Vec2d(evt.data[0], evt.data[1]);
model.wipe_tower.rotation = Geometry::rad2deg(evt.data(2));
model.wipe_tower().position = Vec2d(evt.data[0], evt.data[1]);
model.wipe_tower().rotation = Geometry::rad2deg(evt.data(2));
}
void Plater::priv::on_update_geometry(Vec3dsEvent<2>&)

View File

@ -1001,7 +1001,7 @@ TEST_CASE("Test SceneBuilder", "[arrange2][integration]")
arr2::SceneBuilder bld;
Model mdl;
bld.set_model(mdl);
bld.set_wipe_tower_handler(std::make_unique<arr2::MocWTH>(mdl.wipe_tower.id()));
bld.set_wipe_tower_handler(std::make_unique<arr2::MocWTH>(wipe_tower_instance_id(0)));
WHEN("the selection mask is initialized as a fallback default in the created scene")
{
@ -1014,7 +1014,7 @@ TEST_CASE("Test SceneBuilder", "[arrange2][integration]")
bool wt_selected = false;
scene.model()
.visit_arrangeable(mdl.wipe_tower.id(),
.visit_arrangeable(wipe_tower_instance_id(0),
[&wt_selected](
const arr2::Arrangeable &arrbl) {
wt_selected = arrbl.is_selected();