diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 8e4761540..665f42d67 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1091,7 +1091,7 @@ namespace DoExport { static inline std::vector sort_object_instances_by_max_z(const Print &print) { std::vector objects(print.objects().begin(), print.objects().end()); - std::sort(objects.begin(), objects.end(), [](const PrintObject *po1, const PrintObject *po2) { return po1->size()(2) < po2->size()(2); }); + std::sort(objects.begin(), objects.end(), [](const PrintObject *po1, const PrintObject *po2) { return po1->height() < po2->height(); }); std::vector instances; instances.reserve(objects.size()); for (const PrintObject *object : objects) @@ -2616,8 +2616,9 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou } } else if (seam_position == spRear) { - last_pos = m_layer->object()->bounding_box().center(); - last_pos(1) += coord_t(3. * m_layer->object()->bounding_box().radius()); + // Object is centered around (0,0) in its current coordinate system. + last_pos.x() = 0; + last_pos.y() += coord_t(3. * m_layer->object()->bounding_box().radius()); last_pos_weight = 5.f; } diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 6f61657a5..c869ad298 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -462,7 +462,7 @@ static std::vector print_objects_from_model_object if (model_instance->is_printable()) { trafo.trafo = model_instance->get_matrix(); auto shift = Point::new_scale(trafo.trafo.data()[12], trafo.trafo.data()[13]); - // Set the Z axis of the transformation. + // Reset the XY axes of the transformation. trafo.trafo.data()[12] = 0; trafo.trafo.data()[13] = 0; // Search or insert a trafo. @@ -930,8 +930,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ if (old.empty()) { // Simple case, just generate new instances. for (PrintObjectTrafoAndInstances &print_instances : new_print_instances) { - PrintObject *print_object = new PrintObject(this, model_object, false); - print_object->set_trafo_and_instances(print_instances.trafo, std::move(print_instances.instances)); + PrintObject *print_object = new PrintObject(this, model_object, print_instances.trafo, std::move(print_instances.instances)); print_object->config_apply(config); print_objects_new.emplace_back(print_object); // print_object_status.emplace(PrintObjectStatus(print_object, PrintObjectStatus::New)); @@ -948,8 +947,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ for (; it_old != old.end() && transform3d_lower((*it_old)->trafo, new_instances.trafo); ++ it_old); if (it_old == old.end() || ! transform3d_equal((*it_old)->trafo, new_instances.trafo)) { // This is a new instance (or a set of instances with the same trafo). Just add it. - PrintObject *print_object = new PrintObject(this, model_object, false); - print_object->set_trafo_and_instances(new_instances.trafo, std::move(new_instances.instances)); + PrintObject *print_object = new PrintObject(this, model_object, new_instances.trafo, std::move(new_instances.instances)); print_object->config_apply(config); print_objects_new.emplace_back(print_object); // print_object_status.emplace(PrintObjectStatus(print_object, PrintObjectStatus::New)); @@ -1151,6 +1149,62 @@ bool Print::has_skirt() const || this->has_infinite_skirt(); } +static inline bool sequential_print_horizontal_clearance_valid(const Print &print) +{ + Polygons convex_hulls_other; + std::map map_model_object_to_convex_hull; + for (const PrintObject *print_object : print.objects()) { + assert(! print_object->model_object()->instances.empty()); + assert(! print_object->instances().empty()); + ObjectID model_object_id = print_object->model_object()->id(); + auto it_convex_hull = map_model_object_to_convex_hull.find(model_object_id); + // Get convex hull of all printable volumes assigned to this print object. + ModelInstance *model_instance0 = print_object->model_object()->instances.front(); + if (it_convex_hull == map_model_object_to_convex_hull.end()) { + // Calculate the convex hull of a printable object. + // Grow convex hull with the clearance margin. + // FIXME: Arrangement has different parameters for offsetting (jtMiter, limit 2) + // which causes that the warning will be showed after arrangement with the + // appropriate object distance. Even if I set this to jtMiter the warning still shows up. + it_convex_hull = map_model_object_to_convex_hull.emplace_hint(it_convex_hull, model_object_id, + offset(print_object->model_object()->convex_hull_2d( + Geometry::assemble_transform(Vec3d::Zero(), model_instance0->get_rotation(), model_instance0->get_scaling_factor(), model_instance0->get_mirror())), + // Shrink the extruder_clearance_radius a tiny bit, so that if the object arrangement algorithm placed the objects + // exactly by satisfying the extruder_clearance_radius, this test will not trigger collision. + float(scale_(0.5 * print.config().extruder_clearance_radius.value - EPSILON)), + jtRound, float(scale_(0.1))).front()); + } + // Make a copy, so it may be rotated for instances. + Polygon convex_hull0 = it_convex_hull->second; + double z_diff = Geometry::rotation_diff_z(model_instance0->get_rotation(), print_object->instances().front().model_instance->get_rotation()); + if (std::abs(z_diff) > EPSILON) + convex_hull0.rotate(z_diff); + // Now we check that no instance of convex_hull intersects any of the previously checked object instances. + for (const PrintInstance &instance : print_object->instances()) { + Polygon convex_hull = convex_hull0; + // instance.shift is a position of a centered object, while model object may not be centered. + // Conver the shift from the PrintObject's coordinates into ModelObject's coordinates by removing the centering offset. + convex_hull.translate(instance.shift - print_object->center_offset()); + if (! intersection(convex_hulls_other, convex_hull).empty()) + return false; + polygons_append(convex_hulls_other, convex_hull); + } + } + return true; +} + +static inline bool sequential_print_vertical_clearance_valid(const Print &print) +{ + std::vector print_instances_ordered = sort_object_instances_by_model_order(print); + // Ignore the last instance printed. + print_instances_ordered.pop_back(); + // Find the other highest instance. + auto it = std::max_element(print_instances_ordered.begin(), print_instances_ordered.end(), [](auto l, auto r) { + return l->print_object->height() < r->print_object->height(); + }); + return it == print_instances_ordered.end() || (*it)->print_object->height() < scale_(print.config().extruder_clearance_height.value); +} + // Precondition: Print::validate() requires the Print::apply() to be called its invocation. std::string Print::validate() const { @@ -1161,48 +1215,11 @@ std::string Print::validate() const return L("The supplied settings will cause an empty print."); if (m_config.complete_objects) { - // Check horizontal clearance. - { - Polygons convex_hulls_other; - for (const PrintObject *print_object : m_objects) { - assert(! print_object->model_object()->instances.empty()); - assert(! print_object->instances().empty()); - // Get convex hull of all meshes assigned to this print object. - ModelInstance *model_instance0 = print_object->model_object()->instances.front(); - Vec3d rotation = model_instance0->get_rotation(); - rotation.z() = 0.; - // Calculate the convex hull of a printable object centered around X=0,Y=0. - // Grow convex hull with the clearance margin. - // FIXME: Arrangement has different parameters for offsetting (jtMiter, limit 2) - // which causes that the warning will be showed after arrangement with the - // appropriate object distance. Even if I set this to jtMiter the warning still shows up. - Polygon convex_hull0 = offset( - print_object->model_object()->convex_hull_2d( - Geometry::assemble_transform(Vec3d::Zero(), rotation, model_instance0->get_scaling_factor(), model_instance0->get_mirror())), - float(scale_(0.5 * m_config.extruder_clearance_radius.value)), jtRound, float(scale_(0.1))).front(); - // Now we check that no instance of convex_hull intersects any of the previously checked object instances. - for (const PrintInstance &instance : print_object->instances()) { - Polygon convex_hull = convex_hull0; - convex_hull.translate(instance.shift); - if (! intersection(convex_hulls_other, convex_hull).empty()) - return L("Some objects are too close; your extruder will collide with them."); - polygons_append(convex_hulls_other, convex_hull); - } - } - } - // Check vertical clearance. - { - std::vector object_height; - for (const PrintObject *object : m_objects) - object_height.insert(object_height.end(), object->instances().size(), object->size()(2)); - std::sort(object_height.begin(), object_height.end()); - // Ignore the tallest *copy* (this is why we repeat height for all of them): - // it will be printed as last one so its height doesn't matter. - object_height.pop_back(); - if (! object_height.empty() && object_height.back() > scale_(m_config.extruder_clearance_height.value)) - return L("Some objects are too tall and cannot be printed without extruder collisions."); - } - } // end if (m_config.complete_objects) + if (! sequential_print_horizontal_clearance_valid(*this)) + return L("Some objects are too close; your extruder will collide with them."); + if (! sequential_print_vertical_clearance_valid(*this)) + return L("Some objects are too tall and cannot be printed without extruder collisions."); + } if (m_config.spiral_vase) { size_t total_copies_count = 0; @@ -1418,6 +1435,7 @@ std::string Print::validate() const return std::string(); } +#if 0 // the bounding box of objects placed in copies position // (without taking skirt/brim/support material into account) BoundingBox Print::bounding_box() const @@ -1425,8 +1443,9 @@ BoundingBox Print::bounding_box() const BoundingBox bb; for (const PrintObject *object : m_objects) for (const PrintInstance &instance : object->instances()) { - bb.merge(instance.shift); - bb.merge(instance.shift + to_2d(object->size())); + BoundingBox bb2(object->bounding_box()); + bb.merge(bb2.min + instance.shift); + bb.merge(bb2.max + instance.shift); } return bb; } @@ -1471,6 +1490,7 @@ BoundingBox Print::total_bounding_box() const return bb; } +#endif double Print::skirt_first_layer_height() const { diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 1386b798e..359d162f3 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -84,7 +84,7 @@ private: PrintRegion(Print* print) : m_refcnt(0), m_print(print) {} PrintRegion(Print* print, const PrintRegionConfig &config) : m_refcnt(0), m_print(print), m_config(config) {} - ~PrintRegion() {} + ~PrintRegion() = default; }; @@ -101,7 +101,7 @@ struct PrintInstance PrintObject *print_object; // Source ModelInstance of a ModelObject, for which this print_object was created. const ModelInstance *model_instance; - // Shift of this instance towards its PrintObject + // Shift of this instance's center into the world coordinates. Point shift; }; @@ -116,21 +116,22 @@ public: // vector of (layer height ranges and vectors of volume ids), indexed by region_id std::vector>> region_volumes; - // this is set to true when LayerRegion->slices is split in top/internal/bottom - // so that next call to make_perimeters() performs a union() before computing loops - bool typed_slices; - - // XYZ in scaled coordinates - const Vec3crd& size() const { return m_size; } + // Size of an object: XYZ in scaled coordinates. The size might not be quite snug in XY plane. + const Vec3crd& size() const { return m_size; } const PrintObjectConfig& config() const { return m_config; } const LayerPtrs& layers() const { return m_layers; } const SupportLayerPtrs& support_layers() const { return m_support_layers; } const Transform3d& trafo() const { return m_trafo; } const PrintInstances& instances() const { return m_instances; } - const Point instance_center(size_t idx) const { return m_instances[idx].shift + m_copies_shift + Point(this->size().x() / 2, this->size().y() / 2); } - // since the object is aligned to origin, bounding box coincides with size - BoundingBox bounding_box() const { return BoundingBox(Point(0,0), to_2d(this->size())); } + // Bounding box is used to align the object infill patterns, and to calculate attractor for the rear seam. + // The bounding box may not be quite snug. + BoundingBox bounding_box() const { return BoundingBox(Point(- m_size.x() / 2, - m_size.y() / 2), Point(m_size.x() / 2, m_size.y() / 2)); } + // Height is used for slicing, for sorting the objects by height for sequential printing and for checking vertical clearence in sequential print mode. + // The height is snug. + coord_t height() const { return m_size.z(); } + // Centering offset of the sliced mesh from the scaled and rotated mesh of the model. + const Point& center_offset() const { return m_center_offset; } // adds region_id, too, if necessary void add_region_volume(unsigned int region_id, int volume_id, const t_layer_height_range &layer_range) { @@ -196,14 +197,12 @@ private: // to be called from Print only. friend class Print; - PrintObject(Print* print, ModelObject* model_object, bool add_instances = true); - ~PrintObject() {} + PrintObject(Print* print, ModelObject* model_object, const Transform3d& trafo, PrintInstances&& instances); + ~PrintObject() = default; void config_apply(const ConfigBase &other, bool ignore_nonexistent = false) { this->m_config.apply(other, ignore_nonexistent); } void config_apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false) { this->m_config.apply_only(other, keys, ignore_nonexistent); } - void set_trafo(const Transform3d& trafo) { m_trafo = trafo; } PrintBase::ApplyStatus set_instances(PrintInstances &&instances); - void set_trafo_and_instances(const Transform3d& trafo, PrintInstances &&instances) { this->set_trafo(trafo); this->set_instances(std::move(instances)); } // Invalidates the step, and its depending steps in PrintObject and Print. bool invalidate_step(PrintObjectStep step); // Invalidates all PrintObject and Print steps. @@ -242,15 +241,18 @@ private: Transform3d m_trafo = Transform3d::Identity(); // Slic3r::Point objects in scaled G-code coordinates std::vector m_instances; - // scaled coordinates to add to copies (to compensate for the alignment - // operated when creating the object but still preserving a coherent API - // for external callers) - Point m_copies_shift; + // The mesh is being centered before thrown to Clipper, so that the Clipper's fixed coordinates require less bits. + // This is the adjustment of the the Object's coordinate system towards PrintObject's coordinate system. + Point m_center_offset; SlicingParameters m_slicing_params; LayerPtrs m_layers; SupportLayerPtrs m_support_layers; + // this is set to true when LayerRegion->slices is split in top/internal/bottom + // so that next call to make_perimeters() performs a union() before computing loops + bool m_typed_slices = false; + std::vector slice_region(size_t region_id, const std::vector &z) const; std::vector slice_modifiers(size_t region_id, const std::vector &z) const; std::vector slice_volumes(const std::vector &z, const std::vector &volumes) const; @@ -343,7 +345,7 @@ private: // Prevents erroneous use by other classes. typedef PrintBaseWithState Inherited; public: - Print() {} + Print() = default; virtual ~Print() { this->clear(); } PrinterTechnology technology() const noexcept { return ptFFF; } @@ -379,8 +381,6 @@ public: // Returns an empty string if valid, otherwise returns an error message. std::string validate() const override; - BoundingBox bounding_box() const; - BoundingBox total_bounding_box() const; double skirt_first_layer_height() const; Flow brim_flow() const; Flow skirt_flow() const; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 2ff361309..5d0faa19d 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -40,41 +40,41 @@ namespace Slic3r { -PrintObject::PrintObject(Print* print, ModelObject* model_object, bool add_instances) : +// Constructor is called from the main thread, therefore all Model / ModelObject / ModelIntance data are valid. +PrintObject::PrintObject(Print* print, ModelObject* model_object, const Transform3d& trafo, PrintInstances&& instances) : PrintObjectBaseWithState(print, model_object), - typed_slices(false), - m_size(Vec3crd::Zero()) + m_trafo(trafo) { - // Compute the translation to be applied to our meshes so that we work with smaller coordinates - { - // Translate meshes so that our toolpath generation algorithms work with smaller - // XY coordinates; this translation is an optimization and not strictly required. - // A cloned mesh will be aligned to 0 before slicing in slice_region() since we - // don't assume it's already aligned and we don't alter the original position in model. - // We store the XY translation so that we can place copies correctly in the output G-code - // (copies are expressed in G-code coordinates and this translation is not publicly exposed). - const BoundingBoxf3 modobj_bbox = model_object->raw_bounding_box(); - m_copies_shift = Point::new_scale(modobj_bbox.min(0), modobj_bbox.min(1)); - // Scale the object size and store it - this->m_size = (modobj_bbox.size() * (1. / SCALING_FACTOR)).cast(); - } - - if (add_instances) { - PrintInstances instances; - instances.reserve(m_model_object->instances.size()); - for (const ModelInstance *mi : m_model_object->instances) { - assert(mi->is_printable()); - const Vec3d &offset = mi->get_offset(); - instances.emplace_back(PrintInstance{ nullptr, mi, Point::new_scale(offset(0), offset(1)) }); - } - this->set_instances(std::move(instances)); - } + // Compute centering offet to be applied to our meshes so that we work with smaller coordinates + // requiring less bits to represent Clipper coordinates. + + // Snug bounding box of a rotated and scaled object by the 1st instantion, without the instance translation applied. + // All the instances share the transformation matrix with the exception of translation in XY and rotation by Z, + // therefore a bounding box from 1st instance of a ModelObject is good enough for calculating the object center, + // snug height and an approximate bounding box in XY. + BoundingBoxf3 bbox = model_object->raw_bounding_box(); + Vec3d bbox_center = bbox.center(); + // We may need to rotate the bbox / bbox_center from the original instance to the current instance. + double z_diff = Geometry::rotation_diff_z(model_object->instances.front()->get_rotation(), instances.front().model_instance->get_rotation()); + if (std::abs(z_diff) > EPSILON) { + auto z_rot = Eigen::AngleAxisd(z_diff, Vec3d::UnitZ()); + bbox = bbox.transformed(Transform3d(z_rot)); + bbox_center = (z_rot * bbox_center).eval(); + } + + // Center of the transformed mesh (without translation). + m_center_offset = Point::new_scale(bbox_center.x(), bbox_center.y()); + // Size of the transformed mesh. This bounding may not be snug in XY plane, but it is snug in Z. + m_size = (bbox.size() * (1. / SCALING_FACTOR)).cast(); + + this->set_instances(std::move(instances)); } PrintBase::ApplyStatus PrintObject::set_instances(PrintInstances &&instances) { for (PrintInstance &i : instances) - i.shift += m_copies_shift; + // Add the center offset, which will be subtracted from the mesh when slicing. + i.shift += m_center_offset; // Invalidate and set copies. PrintBase::ApplyStatus status = PrintBase::APPLY_STATUS_UNCHANGED; bool equal_length = instances.size() == m_instances.size(); @@ -153,12 +153,12 @@ void PrintObject::make_perimeters() BOOST_LOG_TRIVIAL(info) << "Generating perimeters..." << log_memory_info(); // merge slices if they were split into types - if (this->typed_slices) { + if (m_typed_slices) { for (Layer *layer : m_layers) { layer->merge_slices(); m_print->throw_if_canceled(); } - this->typed_slices = false; + m_typed_slices = false; } // compare each layer to the one below, and mark those slices needing @@ -822,7 +822,7 @@ void PrintObject::detect_surfaces_type() } // for each this->print->region_count // Mark the object to have the region slices classified (typed, which also means they are split based on whether they are supported, bridging, top layers etc.) - this->typed_slices = true; + m_typed_slices = true; } void PrintObject::process_external_surfaces() @@ -1481,7 +1481,7 @@ void PrintObject::update_slicing_parameters() { if (! m_slicing_params.valid) m_slicing_params = SlicingParameters::create_from_config( - this->print()->config(), m_config, unscale(this->size()(2)), this->object_extruders()); + this->print()->config(), m_config, unscale(this->height()), this->object_extruders()); } SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig& full_config, const ModelObject& model_object, float object_max_z) @@ -1568,7 +1568,7 @@ void PrintObject::_slice(const std::vector &layer_height_profile) { BOOST_LOG_TRIVIAL(info) << "Slicing objects..." << log_memory_info(); - this->typed_slices = false; + m_typed_slices = false; #ifdef SLIC3R_PROFILE // Disable parallelization so the Shiny profiler works @@ -2030,7 +2030,7 @@ std::vector PrintObject::slice_volumes(const std::vector &z, if (mesh.stl.stats.number_of_facets > 0) { mesh.transform(m_trafo, true); // apply XY shift - mesh.translate(- unscale(m_copies_shift(0)), - unscale(m_copies_shift(1)), 0); + mesh.translate(- unscale(m_center_offset.x()), - unscale(m_center_offset.y()), 0); // perform actual slicing const Print *print = this->print(); auto callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();}); @@ -2060,7 +2060,7 @@ std::vector PrintObject::slice_volume(const std::vector &z, c if (mesh.stl.stats.number_of_facets > 0) { mesh.transform(m_trafo, true); // apply XY shift - mesh.translate(- unscale(m_copies_shift(0)), - unscale(m_copies_shift(1)), 0); + mesh.translate(- unscale(m_center_offset.x()), - unscale(m_center_offset.y()), 0); // perform actual slicing TriangleMeshSlicer mslicer; const Print *print = this->print(); diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 0c293c7fb..ccbb00ae8 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -544,8 +544,8 @@ static ClipperPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o } sl::rotate(poly, double(instances[i].rotation)); - sl::translate(poly, ClipperPoint{instances[i].shift(X), - instances[i].shift(Y)}); + sl::translate(poly, ClipperPoint{instances[i].shift.x(), + instances[i].shift.y()}); polygons.emplace_back(std::move(poly)); } diff --git a/src/libslic3r/ShortestPath.cpp b/src/libslic3r/ShortestPath.cpp index a5d3a41ab..0aa897fd7 100644 --- a/src/libslic3r/ShortestPath.cpp +++ b/src/libslic3r/ShortestPath.cpp @@ -1954,7 +1954,8 @@ std::vector chain_print_object_instances(const Print &prin for (size_t i = 0; i < print.objects().size(); ++ i) { const PrintObject &object = *print.objects()[i]; for (size_t j = 0; j < object.instances().size(); ++ j) { - object_reference_points.emplace_back(object.instance_center(j)); + // Sliced PrintObjects are centered, object.instances()[j].shift is the center of the PrintObject in G-code coordinates. + object_reference_points.emplace_back(object.instances()[j].shift); instances.emplace_back(i, j); } } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 37af4c897..7c0db32c0 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -6425,7 +6425,7 @@ void GLCanvas3D::_load_sla_shells() v.indexed_vertex_array.finalize_geometry(this->m_initialized); v.shader_outside_printer_detection_enabled = outside_printer_detection_enabled; v.composite_id.volume_id = volume_id; - v.set_instance_offset(unscale(instance.shift(0), instance.shift(1), 0)); + v.set_instance_offset(unscale(instance.shift.x(), instance.shift.y(), 0)); v.set_instance_rotation(Vec3d(0.0, 0.0, (double)instance.rotation)); v.set_instance_mirror(X, object.is_left_handed() ? -1. : 1.); v.set_convex_hull(mesh.convex_hull_3d()); diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index 2e329bfa6..0952513ca 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -148,9 +148,6 @@ _constant() croak("Configuration is not valid: %s\n", err.c_str()); RETVAL = 1; %}; - Clone bounding_box(); - Clone total_bounding_box(); - Clone size() %code%{ RETVAL = THIS->bounding_box().size(); %}; void set_callback_event(int evt) %code%{ %};