Fixed a bug in validation of the FDM Print to check for extruder

collisions against already printed objects: Rotation of an object
was not being taken into account.
Fixes #2450 PrusaSlicer 2.0 Sequential printing vs rotate object
Fixes #3585 Always Sequential Printing Error (Regardless of setting, placement or object size)

Fixed some collisions in sequential print just after arrangement by
making the validation using a slightly lower extruder radius value
than the arrangement.

Refactored PrintObject coordinate system so that the PrintObject's
coordinate system is always centered in respect to its ModelObject's
geometries. This refactoring may improve path planning a bit and
it may lower the requirements on bits representing Clipper coordinates
by 1 bit.
This commit is contained in:
bubnikv 2020-02-07 14:10:18 +01:00
parent e66632a595
commit b8c898bf40
8 changed files with 135 additions and 116 deletions

View File

@ -1091,7 +1091,7 @@ namespace DoExport {
static inline std::vector<const PrintInstance*> sort_object_instances_by_max_z(const Print &print)
{
std::vector<const PrintObject*> 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<const PrintInstance*> 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;
}

View File

@ -462,7 +462,7 @@ static std::vector<PrintObjectTrafoAndInstances> 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<ObjectID, Polygon> 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<const PrintInstance*> 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<coord_t> 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
{

View File

@ -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<std::vector<std::pair<t_layer_height_range, int>>> 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<PrintInstance> 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<ExPolygons> slice_region(size_t region_id, const std::vector<float> &z) const;
std::vector<ExPolygons> slice_modifiers(size_t region_id, const std::vector<float> &z) const;
std::vector<ExPolygons> slice_volumes(const std::vector<float> &z, const std::vector<const ModelVolume*> &volumes) const;
@ -343,7 +345,7 @@ private: // Prevents erroneous use by other classes.
typedef PrintBaseWithState<PrintStep, psCount> 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;

View File

@ -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<coord_t>();
}
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<coord_t>();
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<double>(this->size()(2)), this->object_extruders());
this->print()->config(), m_config, unscale<double>(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<coordf_t> &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<ExPolygons> PrintObject::slice_volumes(const std::vector<float> &z,
if (mesh.stl.stats.number_of_facets > 0) {
mesh.transform(m_trafo, true);
// apply XY shift
mesh.translate(- unscale<float>(m_copies_shift(0)), - unscale<float>(m_copies_shift(1)), 0);
mesh.translate(- unscale<float>(m_center_offset.x()), - unscale<float>(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<ExPolygons> PrintObject::slice_volume(const std::vector<float> &z, c
if (mesh.stl.stats.number_of_facets > 0) {
mesh.transform(m_trafo, true);
// apply XY shift
mesh.translate(- unscale<float>(m_copies_shift(0)), - unscale<float>(m_copies_shift(1)), 0);
mesh.translate(- unscale<float>(m_center_offset.x()), - unscale<float>(m_center_offset.y()), 0);
// perform actual slicing
TriangleMeshSlicer mslicer;
const Print *print = this->print();

View File

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

View File

@ -1954,7 +1954,8 @@ std::vector<const PrintInstance*> 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);
}
}

View File

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

View File

@ -148,9 +148,6 @@ _constant()
croak("Configuration is not valid: %s\n", err.c_str());
RETVAL = 1;
%};
Clone<BoundingBox> bounding_box();
Clone<BoundingBox> total_bounding_box();
Clone<Point> size() %code%{ RETVAL = THIS->bounding_box().size(); %};
void set_callback_event(int evt) %code%{
%};