Merge branch 'et_transformations' of https://github.com/Prusa-Development/PrusaSlicerPrivate into et_transformations

This commit is contained in:
enricoturri1966 2023-03-01 08:12:10 +01:00
commit 673a7ccff9
30 changed files with 2666 additions and 2330 deletions

File diff suppressed because it is too large Load diff

View file

@ -382,7 +382,7 @@ int CLI::run(int argc, char **argv)
} else if (opt_key == "align_xy") { } else if (opt_key == "align_xy") {
const Vec2d &p = m_config.option<ConfigOptionPoint>("align_xy")->value; const Vec2d &p = m_config.option<ConfigOptionPoint>("align_xy")->value;
for (auto &model : m_models) { for (auto &model : m_models) {
BoundingBoxf3 bb = model.bounding_box(); BoundingBoxf3 bb = model.bounding_box_exact();
// this affects volumes: // this affects volumes:
model.translate(-(bb.min.x() - p.x()), -(bb.min.y() - p.y()), -bb.min.z()); model.translate(-(bb.min.x() - p.x()), -(bb.min.y() - p.y()), -bb.min.z());
} }
@ -423,7 +423,7 @@ int CLI::run(int argc, char **argv)
} else if (opt_key == "cut" || opt_key == "cut_x" || opt_key == "cut_y") { } else if (opt_key == "cut" || opt_key == "cut_x" || opt_key == "cut_y") {
std::vector<Model> new_models; std::vector<Model> new_models;
for (auto &model : m_models) { for (auto &model : m_models) {
model.translate(0, 0, -model.bounding_box().min.z()); // align to z = 0 model.translate(0, 0, -model.bounding_box_exact().min.z()); // align to z = 0
size_t num_objects = model.objects.size(); size_t num_objects = model.objects.size();
for (size_t i = 0; i < num_objects; ++ i) { for (size_t i = 0; i < num_objects; ++ i) {

View file

@ -680,6 +680,13 @@ Slic3r::Polygons union_(const Slic3r::ExPolygons &subject)
{ return _clipper(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No); } { return _clipper(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No); }
Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2) Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2)
{ return _clipper(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(subject2), ApplySafetyOffset::No); } { return _clipper(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(subject2), ApplySafetyOffset::No); }
Slic3r::Polygons union_(Slic3r::Polygons &&subject, const Slic3r::Polygons &subject2) {
if (subject.empty())
return subject2;
if (subject2.empty())
return std::move(subject);
return union_(subject, subject2);
}
Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::ExPolygon &subject2) Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::ExPolygon &subject2)
{ return _clipper(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::ExPolygonProvider(subject2), ApplySafetyOffset::No); } { return _clipper(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::ExPolygonProvider(subject2), ApplySafetyOffset::No); }

View file

@ -323,14 +323,30 @@ bool Model::add_default_instances()
} }
// this returns the bounding box of the *transformed* instances // this returns the bounding box of the *transformed* instances
BoundingBoxf3 Model::bounding_box() const BoundingBoxf3 Model::bounding_box_approx() const
{ {
BoundingBoxf3 bb; BoundingBoxf3 bb;
for (ModelObject *o : this->objects) for (ModelObject *o : this->objects)
bb.merge(o->bounding_box()); bb.merge(o->bounding_box_approx());
return bb; return bb;
} }
BoundingBoxf3 Model::bounding_box_exact() const
{
BoundingBoxf3 bb;
for (ModelObject *o : this->objects)
bb.merge(o->bounding_box_exact());
return bb;
}
double Model::max_z() const
{
double z = 0;
for (ModelObject *o : this->objects)
z = std::max(z, o->max_z());
return z;
}
unsigned int Model::update_print_volume_state(const BuildVolume &build_volume) unsigned int Model::update_print_volume_state(const BuildVolume &build_volume)
{ {
unsigned int num_printable = 0; unsigned int num_printable = 0;
@ -377,7 +393,7 @@ void Model::duplicate_objects_grid(size_t x, size_t y, coordf_t dist)
ModelObject* object = this->objects.front(); ModelObject* object = this->objects.front();
object->clear_instances(); object->clear_instances();
Vec3d ext_size = object->bounding_box().size() + dist * Vec3d::Ones(); Vec3d ext_size = object->bounding_box_exact().size() + dist * Vec3d::Ones();
for (size_t x_copy = 1; x_copy <= x; ++x_copy) { for (size_t x_copy = 1; x_copy <= x; ++x_copy) {
for (size_t y_copy = 1; y_copy <= y; ++y_copy) { for (size_t y_copy = 1; y_copy <= y; ++y_copy) {
@ -548,13 +564,13 @@ void Model::adjust_min_z()
if (objects.empty()) if (objects.empty())
return; return;
if (bounding_box().min(2) < 0.0) if (this->bounding_box_exact().min.z() < 0.0)
{ {
for (ModelObject* obj : objects) for (ModelObject* obj : objects)
{ {
if (obj != nullptr) if (obj != nullptr)
{ {
coordf_t obj_min_z = obj->bounding_box().min(2); coordf_t obj_min_z = obj->min_z();
if (obj_min_z < 0.0) if (obj_min_z < 0.0)
obj->translate_instances(Vec3d(0.0, 0.0, -obj_min_z)); obj->translate_instances(Vec3d(0.0, 0.0, -obj_min_z));
} }
@ -627,8 +643,11 @@ ModelObject& ModelObject::assign_copy(const ModelObject &rhs)
this->printable = rhs.printable; this->printable = rhs.printable;
this->origin_translation = rhs.origin_translation; this->origin_translation = rhs.origin_translation;
this->cut_id.copy(rhs.cut_id); this->cut_id.copy(rhs.cut_id);
m_bounding_box = rhs.m_bounding_box; m_bounding_box_approx = rhs.m_bounding_box_approx;
m_bounding_box_valid = rhs.m_bounding_box_valid; m_bounding_box_approx_valid = rhs.m_bounding_box_approx_valid;
m_bounding_box_exact = rhs.m_bounding_box_exact;
m_bounding_box_exact_valid = rhs.m_bounding_box_exact_valid;
m_min_max_z_valid = rhs.m_min_max_z_valid;
m_raw_bounding_box = rhs.m_raw_bounding_box; m_raw_bounding_box = rhs.m_raw_bounding_box;
m_raw_bounding_box_valid = rhs.m_raw_bounding_box_valid; m_raw_bounding_box_valid = rhs.m_raw_bounding_box_valid;
m_raw_mesh_bounding_box = rhs.m_raw_mesh_bounding_box; m_raw_mesh_bounding_box = rhs.m_raw_mesh_bounding_box;
@ -668,8 +687,11 @@ ModelObject& ModelObject::assign_copy(ModelObject &&rhs)
this->layer_height_profile = std::move(rhs.layer_height_profile); this->layer_height_profile = std::move(rhs.layer_height_profile);
this->printable = std::move(rhs.printable); this->printable = std::move(rhs.printable);
this->origin_translation = std::move(rhs.origin_translation); this->origin_translation = std::move(rhs.origin_translation);
m_bounding_box = std::move(rhs.m_bounding_box); m_bounding_box_approx = std::move(rhs.m_bounding_box_approx);
m_bounding_box_valid = std::move(rhs.m_bounding_box_valid); m_bounding_box_approx_valid = std::move(rhs.m_bounding_box_approx_valid);
m_bounding_box_exact = std::move(rhs.m_bounding_box_exact);
m_bounding_box_exact_valid = std::move(rhs.m_bounding_box_exact_valid);
m_min_max_z_valid = rhs.m_min_max_z_valid;
m_raw_bounding_box = rhs.m_raw_bounding_box; m_raw_bounding_box = rhs.m_raw_bounding_box;
m_raw_bounding_box_valid = rhs.m_raw_bounding_box_valid; m_raw_bounding_box_valid = rhs.m_raw_bounding_box_valid;
m_raw_mesh_bounding_box = rhs.m_raw_mesh_bounding_box; m_raw_mesh_bounding_box = rhs.m_raw_mesh_bounding_box;
@ -864,16 +886,73 @@ void ModelObject::clear_instances()
// Returns the bounding box of the transformed instances. // Returns the bounding box of the transformed instances.
// This bounding box is approximate and not snug. // This bounding box is approximate and not snug.
const BoundingBoxf3& ModelObject::bounding_box() const const BoundingBoxf3& ModelObject::bounding_box_approx() const
{ {
if (! m_bounding_box_valid) { if (! m_bounding_box_approx_valid) {
m_bounding_box_valid = true; m_bounding_box_approx_valid = true;
BoundingBoxf3 raw_bbox = this->raw_mesh_bounding_box(); BoundingBoxf3 raw_bbox = this->raw_mesh_bounding_box();
m_bounding_box.reset(); m_bounding_box_approx.reset();
for (const ModelInstance *i : this->instances) for (const ModelInstance *i : this->instances)
m_bounding_box.merge(i->transform_bounding_box(raw_bbox)); m_bounding_box_approx.merge(i->transform_bounding_box(raw_bbox));
}
return m_bounding_box_approx;
}
// Returns the bounding box of the transformed instances.
// This bounding box is approximate and not snug.
const BoundingBoxf3& ModelObject::bounding_box_exact() const
{
if (! m_bounding_box_exact_valid) {
m_bounding_box_exact_valid = true;
m_min_max_z_valid = true;
BoundingBoxf3 raw_bbox = this->raw_mesh_bounding_box();
m_bounding_box_exact.reset();
for (size_t i = 0; i < this->instances.size(); ++ i)
m_bounding_box_exact.merge(this->instance_bounding_box(i));
}
return m_bounding_box_exact;
}
double ModelObject::min_z() const
{
const_cast<ModelObject*>(this)->update_min_max_z();
return m_bounding_box_exact.min.z();
}
double ModelObject::max_z() const
{
const_cast<ModelObject*>(this)->update_min_max_z();
return m_bounding_box_exact.max.z();
}
void ModelObject::update_min_max_z()
{
assert(! this->instances.empty());
if (! m_min_max_z_valid && ! this->instances.empty()) {
m_min_max_z_valid = true;
const Transform3d mat_instance = this->instances.front()->get_transformation().get_matrix();
double global_min_z = std::numeric_limits<double>::max();
double global_max_z = - std::numeric_limits<double>::max();
for (const ModelVolume *v : this->volumes)
if (v->is_model_part()) {
const Transform3d m = mat_instance * v->get_matrix();
const Vec3d row_z = m.linear().row(2).cast<double>();
const double shift_z = m.translation().z();
double this_min_z = std::numeric_limits<double>::max();
double this_max_z = - std::numeric_limits<double>::max();
for (const Vec3f &p : v->mesh().its.vertices) {
double z = row_z.dot(p.cast<double>());
this_min_z = std::min(this_min_z, z);
this_max_z = std::max(this_max_z, z);
}
this_min_z += shift_z;
this_max_z += shift_z;
global_min_z = std::min(global_min_z, this_min_z);
global_max_z = std::max(global_max_z, this_max_z);
}
m_bounding_box_exact.min.z() = global_min_z;
m_bounding_box_exact.max.z() = global_max_z;
} }
return m_bounding_box;
} }
// A mesh containing all transformed instances of this object. // A mesh containing all transformed instances of this object.
@ -1031,19 +1110,19 @@ void ModelObject::ensure_on_bed(bool allow_negative_z)
if (allow_negative_z) { if (allow_negative_z) {
if (parts_count() == 1) { if (parts_count() == 1) {
const double min_z = get_min_z(); const double min_z = this->min_z();
const double max_z = get_max_z(); const double max_z = this->max_z();
if (min_z >= SINKING_Z_THRESHOLD || max_z < 0.0) if (min_z >= SINKING_Z_THRESHOLD || max_z < 0.0)
z_offset = -min_z; z_offset = -min_z;
} }
else { else {
const double max_z = get_max_z(); const double max_z = this->max_z();
if (max_z < SINKING_MIN_Z_THRESHOLD) if (max_z < SINKING_MIN_Z_THRESHOLD)
z_offset = SINKING_MIN_Z_THRESHOLD - max_z; z_offset = SINKING_MIN_Z_THRESHOLD - max_z;
} }
} }
else else
z_offset = -get_min_z(); z_offset = -this->min_z();
if (z_offset != 0.0) if (z_offset != 0.0)
translate_instances(z_offset * Vec3d::UnitZ()); translate_instances(z_offset * Vec3d::UnitZ());
@ -1070,8 +1149,10 @@ void ModelObject::translate(double x, double y, double z)
v->translate(x, y, z); v->translate(x, y, z);
} }
if (m_bounding_box_valid) if (m_bounding_box_approx_valid)
m_bounding_box.translate(x, y, z); m_bounding_box_approx.translate(x, y, z);
if (m_bounding_box_exact_valid)
m_bounding_box_exact.translate(x, y, z);
} }
void ModelObject::scale(const Vec3d &versor) void ModelObject::scale(const Vec3d &versor)
@ -1241,7 +1322,7 @@ indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes conn
break; break;
} }
if (connector_attributes.style == CutConnectorStyle::Prizm) if (connector_attributes.style == CutConnectorStyle::Prism)
connector_mesh = its_make_cylinder(1.0, 1.0, (2 * PI / sectorCount)); connector_mesh = its_make_cylinder(1.0, 1.0, (2 * PI / sectorCount));
else if (connector_attributes.type == CutConnectorType::Plug) else if (connector_attributes.type == CutConnectorType::Plug)
connector_mesh = its_make_cone(1.0, 1.0, (2 * PI / sectorCount)); connector_mesh = its_make_cone(1.0, 1.0, (2 * PI / sectorCount));
@ -1347,12 +1428,7 @@ void ModelVolume::apply_tolerance()
return; return;
Vec3d sf = get_scaling_factor(); Vec3d sf = get_scaling_factor();
/*
// correct Z offset in respect to the new size
Vec3d pos = vol->get_offset();
pos[Z] += sf[Z] * 0.5 * vol->cut_info.height_tolerance;
vol->set_offset(pos);
*/
// make a "hole" wider // make a "hole" wider
sf[X] += double(cut_info.radius_tolerance); sf[X] += double(cut_info.radius_tolerance);
sf[Y] += double(cut_info.radius_tolerance); sf[Y] += double(cut_info.radius_tolerance);
@ -1361,9 +1437,39 @@ void ModelVolume::apply_tolerance()
sf[Z] += double(cut_info.height_tolerance); sf[Z] += double(cut_info.height_tolerance);
set_scaling_factor(sf); set_scaling_factor(sf);
// correct offset in respect to the new depth
Vec3d rot_norm = Geometry::rotation_transform(get_rotation()) * Vec3d::UnitZ();
if (rot_norm.norm() != 0.0)
rot_norm.normalize();
double z_offset = 0.5 * static_cast<double>(cut_info.height_tolerance);
if (cut_info.connector_type == CutConnectorType::Plug)
z_offset -= 0.05; // add small Z offset to better preview
set_offset(get_offset() + rot_norm * z_offset);
} }
void ModelObject::process_connector_cut(ModelVolume* volume, ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelVolume* src_volume, const Transform3d& cut_matrix, const std::string& suffix = {}, ModelVolumeType type = ModelVolumeType::MODEL_PART)
{
if (mesh.empty())
return;
mesh.transform(cut_matrix);
ModelVolume* vol = object->add_volume(mesh);
vol->set_type(type);
vol->name = src_volume->name + suffix;
// Don't copy the config's ID.
vol->config.assign_config(src_volume->config);
assert(vol->config.id().valid());
assert(vol->config.id() != src_volume->config.id());
vol->set_material(src_volume->material_id(), *src_volume->material());
vol->cut_info = src_volume->cut_info;
}
void ModelObject::process_connector_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower,
std::vector<ModelObject*>& dowels, Vec3d& local_dowels_displace) std::vector<ModelObject*>& dowels, Vec3d& local_dowels_displace)
{ {
assert(volume->cut_info.is_connector); assert(volume->cut_info.is_connector);
@ -1373,39 +1479,53 @@ void ModelObject::process_connector_cut(ModelVolume* volume, ModelObjectCutAttri
// ! Don't apply instance transformation for the conntectors. // ! Don't apply instance transformation for the conntectors.
// This transformation is already there // This transformation is already there
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { if (volume->cut_info.connector_type != CutConnectorType::Dowel) {
ModelVolume* vol = upper->add_volume(*volume); if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
vol->set_transformation(volume_matrix); ModelVolume* vol = upper->add_volume(*volume);
vol->apply_tolerance(); vol->set_transformation(volume_matrix);
}
if (attributes.has(ModelObjectCutAttribute::KeepLower)) {
ModelVolume* vol = lower->add_volume(*volume);
vol->set_transformation(volume_matrix);
if (volume->cut_info.connector_type == CutConnectorType::Dowel)
vol->apply_tolerance(); vol->apply_tolerance();
else }
if (attributes.has(ModelObjectCutAttribute::KeepLower)) {
ModelVolume* vol = lower->add_volume(*volume);
vol->set_transformation(volume_matrix);
// for lower part change type of connector from NEGATIVE_VOLUME to MODEL_PART if this connector is a plug // for lower part change type of connector from NEGATIVE_VOLUME to MODEL_PART if this connector is a plug
vol->set_type(ModelVolumeType::MODEL_PART); vol->set_type(ModelVolumeType::MODEL_PART);
}
} }
if (volume->cut_info.connector_type == CutConnectorType::Dowel && else {
attributes.has(ModelObjectCutAttribute::CreateDowels)) { if (attributes.has(ModelObjectCutAttribute::CreateDowels)) {
ModelObject* dowel{ nullptr }; ModelObject* dowel{ nullptr };
// Clone the object to duplicate instances, materials etc. // Clone the object to duplicate instances, materials etc.
clone_for_cut(&dowel); clone_for_cut(&dowel);
// add one more solid part same as connector if this connector is a dowel // add one more solid part same as connector if this connector is a dowel
ModelVolume* vol = dowel->add_volume(*volume); ModelVolume* vol = dowel->add_volume(*volume);
vol->set_type(ModelVolumeType::MODEL_PART); vol->set_type(ModelVolumeType::MODEL_PART);
// But discard rotation and Z-offset for this volume // But discard rotation and Z-offset for this volume
vol->set_rotation(Vec3d::Zero()); vol->set_rotation(Vec3d::Zero());
vol->set_offset(Z, 0.0); vol->set_offset(Z, 0.0);
// Compute the displacement (in instance coordinates) to be applied to place the dowels // Compute the displacement (in instance coordinates) to be applied to place the dowels
local_dowels_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(1.0, 1.0, 0.0)); local_dowels_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(1.0, 1.0, 0.0));
dowels.push_back(dowel); dowels.push_back(dowel);
}
// Cut the dowel
volume->apply_tolerance();
// Perform cut
TriangleMesh upper_mesh, lower_mesh;
process_volume_cut(volume, instance_matrix, cut_matrix, attributes, upper_mesh, lower_mesh);
// add small Z offset to better preview
upper_mesh.translate((-0.05 * Vec3d::UnitZ()).cast<float>());
lower_mesh.translate((0.05 * Vec3d::UnitZ()).cast<float>());
// Add cut parts to the related objects
add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A", volume->type());
add_cut_volume(lower_mesh, lower, volume, cut_matrix, "_B", volume->type());
} }
} }
@ -1427,25 +1547,8 @@ void ModelObject::process_modifier_cut(ModelVolume* volume, const Transform3d& i
lower->add_volume(*volume); lower->add_volume(*volume);
} }
static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelVolume* src_volume, const Transform3d& cut_matrix, const std::string& suffix = {}) void ModelObject::process_volume_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
{ ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh)
if (mesh.empty())
return;
mesh.transform(cut_matrix);
ModelVolume* vol = object->add_volume(mesh);
vol->name = src_volume->name + suffix;
// Don't copy the config's ID.
vol->config.assign_config(src_volume->config);
assert(vol->config.id().valid());
assert(vol->config.id() != src_volume->config.id());
vol->set_material(src_volume->material_id(), *src_volume->material());
vol->cut_info = src_volume->cut_info;
}
void ModelObject::process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, Vec3d& local_displace)
{ {
const auto volume_matrix = volume->get_matrix(); const auto volume_matrix = volume->get_matrix();
@ -1459,23 +1562,20 @@ void ModelObject::process_solid_part_cut(ModelVolume* volume, const Transform3d&
TriangleMesh mesh(volume->mesh()); TriangleMesh mesh(volume->mesh());
mesh.transform(invert_cut_matrix * instance_matrix * volume_matrix, true); mesh.transform(invert_cut_matrix * instance_matrix * volume_matrix, true);
volume->reset_mesh(); indexed_triangle_set upper_its, lower_its;
// Reset volume transformation except for offset cut_mesh(mesh.its, 0.0f, &upper_its, &lower_its);
const Vec3d offset = volume->get_offset(); if (attributes.has(ModelObjectCutAttribute::KeepUpper))
volume->set_transformation(Geometry::Transformation()); upper_mesh = TriangleMesh(upper_its);
volume->set_offset(offset); if (attributes.has(ModelObjectCutAttribute::KeepLower))
lower_mesh = TriangleMesh(lower_its);
}
void ModelObject::process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, Vec3d& local_displace)
{
// Perform cut // Perform cut
TriangleMesh upper_mesh, lower_mesh; TriangleMesh upper_mesh, lower_mesh;
{ process_volume_cut(volume, instance_matrix, cut_matrix, attributes, upper_mesh, lower_mesh);
indexed_triangle_set upper_its, lower_its;
cut_mesh(mesh.its, 0.0f, &upper_its, &lower_its);
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
upper_mesh = TriangleMesh(upper_its);
if (attributes.has(ModelObjectCutAttribute::KeepLower))
lower_mesh = TriangleMesh(lower_its);
}
// Add required cut parts to the objects // Add required cut parts to the objects
@ -1606,7 +1706,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Transform3d& cut_matrix,
if (volume->cut_info.is_processed) if (volume->cut_info.is_processed)
process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, attributes, upper, lower); process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, attributes, upper, lower);
else else
process_connector_cut(volume, attributes, upper, lower, dowels, local_dowels_displace); process_connector_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower, dowels, local_dowels_displace);
} }
else if (!volume->mesh().empty()) else if (!volume->mesh().empty())
process_solid_part_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower, local_displace); process_solid_part_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower, local_displace);
@ -1847,32 +1947,6 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx)
this->invalidate_bounding_box(); this->invalidate_bounding_box();
} }
double ModelObject::get_min_z() const
{
if (instances.empty())
return 0.0;
else {
double min_z = DBL_MAX;
for (size_t i = 0; i < instances.size(); ++i) {
min_z = std::min(min_z, get_instance_min_z(i));
}
return min_z;
}
}
double ModelObject::get_max_z() const
{
if (instances.empty())
return 0.0;
else {
double max_z = -DBL_MAX;
for (size_t i = 0; i < instances.size(); ++i) {
max_z = std::max(max_z, get_instance_max_z(i));
}
return max_z;
}
}
double ModelObject::get_instance_min_z(size_t instance_idx) const double ModelObject::get_instance_min_z(size_t instance_idx) const
{ {
double min_z = DBL_MAX; double min_z = DBL_MAX;
@ -2249,7 +2323,7 @@ void ModelVolume::scale(const Vec3d& scaling_factors)
void ModelObject::scale_to_fit(const Vec3d &size) void ModelObject::scale_to_fit(const Vec3d &size)
{ {
Vec3d orig_size = this->bounding_box().size(); Vec3d orig_size = this->bounding_box_exact().size();
double factor = std::min( double factor = std::min(
size.x() / orig_size.x(), size.x() / orig_size.x(),
std::min( std::min(
@ -2354,37 +2428,6 @@ void ModelInstance::transform_mesh(TriangleMesh* mesh, bool dont_translate) cons
#endif // ENABLE_WORLD_COORDINATE #endif // ENABLE_WORLD_COORDINATE
} }
BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh& mesh, bool dont_translate) const
{
// Rotate around mesh origin.
TriangleMesh copy(mesh);
#if ENABLE_WORLD_COORDINATE
copy.transform(get_transformation().get_rotation_matrix());
#else
copy.transform(get_matrix(true, false, true, true));
#endif // ENABLE_WORLD_COORDINATE
BoundingBoxf3 bbox = copy.bounding_box();
if (!empty(bbox)) {
// Scale the bounding box along the three axes.
for (unsigned int i = 0; i < 3; ++i)
{
if (std::abs(get_scaling_factor((Axis)i)-1.0) > EPSILON)
{
bbox.min(i) *= get_scaling_factor((Axis)i);
bbox.max(i) *= get_scaling_factor((Axis)i);
}
}
// Translate the bounding box.
if (! dont_translate) {
bbox.min += get_offset();
bbox.max += get_offset();
}
}
return bbox;
}
BoundingBoxf3 ModelInstance::transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate) const BoundingBoxf3 ModelInstance::transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate) const
{ {
#if ENABLE_WORLD_COORDINATE #if ENABLE_WORLD_COORDINATE

View file

@ -168,7 +168,7 @@ private:
friend class cereal::access; friend class cereal::access;
friend class UndoRedo::StackImpl; friend class UndoRedo::StackImpl;
// Create an object for deserialization, don't allocate IDs for ModelMaterial and its config. // Create an object for deserialization, don't allocate IDs for ModelMaterial and its config.
ModelMaterial() : ObjectBase(-1), config(-1), m_model(nullptr) { assert(this->id().invalid()); assert(this->config.id().invalid()); } ModelMaterial() : ObjectBase(-1), config(-1) { assert(this->id().invalid()); assert(this->config.id().invalid()); }
template<class Archive> void serialize(Archive &ar) { template<class Archive> void serialize(Archive &ar) {
assert(this->id().invalid()); assert(this->config.id().invalid()); assert(this->id().invalid()); assert(this->config.id().invalid());
Internal::StaticSerializationWrapper<ModelConfigObject> config_wrapper(config); Internal::StaticSerializationWrapper<ModelConfigObject> config_wrapper(config);
@ -228,7 +228,7 @@ enum class CutConnectorType : int {
}; };
enum class CutConnectorStyle : int { enum class CutConnectorStyle : int {
Prizm Prism
, Frustum , Frustum
, Undef , Undef
//,Claw //,Claw
@ -246,7 +246,7 @@ enum class CutConnectorShape : int {
struct CutConnectorAttributes struct CutConnectorAttributes
{ {
CutConnectorType type{ CutConnectorType::Plug }; CutConnectorType type{ CutConnectorType::Plug };
CutConnectorStyle style{ CutConnectorStyle::Prizm }; CutConnectorStyle style{ CutConnectorStyle::Prism };
CutConnectorShape shape{ CutConnectorShape::Circle }; CutConnectorShape shape{ CutConnectorShape::Circle };
CutConnectorAttributes() {} CutConnectorAttributes() {}
@ -343,7 +343,7 @@ public:
// The pairs of <z, layer_height> are packed into a 1D array. // The pairs of <z, layer_height> are packed into a 1D array.
LayerHeightProfile layer_height_profile; LayerHeightProfile layer_height_profile;
// Whether or not this object is printable // Whether or not this object is printable
bool printable; bool printable { true };
// This vector holds position of selected support points for SLA. The data are // This vector holds position of selected support points for SLA. The data are
// saved in mesh coordinates to allow using them for several instances. // saved in mesh coordinates to allow using them for several instances.
@ -397,11 +397,22 @@ public:
void delete_last_instance(); void delete_last_instance();
void clear_instances(); void clear_instances();
// Returns the bounding box of the transformed instances. // Returns the bounding box of the transformed instances. This bounding box is approximate and not snug, it is being cached.
// This bounding box is approximate and not snug. const BoundingBoxf3& bounding_box_approx() const;
// This bounding box is being cached. // Returns an exact bounding box of the transformed instances. The result it is being cached.
const BoundingBoxf3& bounding_box() const; const BoundingBoxf3& bounding_box_exact() const;
void invalidate_bounding_box() { m_bounding_box_valid = false; m_raw_bounding_box_valid = false; m_raw_mesh_bounding_box_valid = false; } // Return minimum / maximum of a printable object transformed into the world coordinate system.
// All instances share the same min / max Z.
double min_z() const;
double max_z() const;
void invalidate_bounding_box() {
m_bounding_box_approx_valid = false;
m_bounding_box_exact_valid = false;
m_min_max_z_valid = false;
m_raw_bounding_box_valid = false;
m_raw_mesh_bounding_box_valid = false;
}
// A mesh containing all transformed instances of this object. // A mesh containing all transformed instances of this object.
TriangleMesh mesh() const; TriangleMesh mesh() const;
@ -459,10 +470,13 @@ public:
void synchronize_model_after_cut(); void synchronize_model_after_cut();
void apply_cut_attributes(ModelObjectCutAttributes attributes); void apply_cut_attributes(ModelObjectCutAttributes attributes);
void clone_for_cut(ModelObject **obj); void clone_for_cut(ModelObject **obj);
void process_connector_cut(ModelVolume* volume, ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, void process_connector_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower,
std::vector<ModelObject*>& dowels, Vec3d& local_dowels_displace); std::vector<ModelObject*>& dowels, Vec3d& local_dowels_displace);
void process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix, void process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix,
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower); ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower);
void process_volume_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh);
void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, Vec3d& local_displace); ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, Vec3d& local_displace);
ModelObjectPtrs cut(size_t instance, const Transform3d&cut_matrix, ModelObjectCutAttributes attributes); ModelObjectPtrs cut(size_t instance, const Transform3d&cut_matrix, ModelObjectCutAttributes attributes);
@ -474,8 +488,6 @@ public:
// Rotation and mirroring is being baked in. In case the instance scaling was non-uniform, it is baked in as well. // Rotation and mirroring is being baked in. In case the instance scaling was non-uniform, it is baked in as well.
void bake_xy_rotation_into_meshes(size_t instance_idx); void bake_xy_rotation_into_meshes(size_t instance_idx);
double get_min_z() const;
double get_max_z() const;
double get_instance_min_z(size_t instance_idx) const; double get_instance_min_z(size_t instance_idx) const;
double get_instance_max_z(size_t instance_idx) const; double get_instance_max_z(size_t instance_idx) const;
@ -497,14 +509,13 @@ public:
private: private:
friend class Model; friend class Model;
// This constructor assigns new ID to this ModelObject and its config. // This constructor assigns new ID to this ModelObject and its config.
explicit ModelObject(Model* model) : m_model(model), printable(true), origin_translation(Vec3d::Zero()), explicit ModelObject(Model* model) : m_model(model), origin_translation(Vec3d::Zero())
m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false)
{ {
assert(this->id().valid()); assert(this->id().valid());
assert(this->config.id().valid()); assert(this->config.id().valid());
assert(this->layer_height_profile.id().valid()); assert(this->layer_height_profile.id().valid());
} }
explicit ModelObject(int) : ObjectBase(-1), config(-1), layer_height_profile(-1), m_model(nullptr), printable(true), origin_translation(Vec3d::Zero()), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) explicit ModelObject(int) : ObjectBase(-1), config(-1), layer_height_profile(-1), origin_translation(Vec3d::Zero())
{ {
assert(this->id().invalid()); assert(this->id().invalid());
assert(this->config.id().invalid()); assert(this->config.id().invalid());
@ -582,15 +593,18 @@ private:
OBJECTBASE_DERIVED_COPY_MOVE_CLONE(ModelObject) OBJECTBASE_DERIVED_COPY_MOVE_CLONE(ModelObject)
// Parent object, owning this ModelObject. Set to nullptr here, so the macros above will have it initialized. // Parent object, owning this ModelObject. Set to nullptr here, so the macros above will have it initialized.
Model *m_model = nullptr; Model *m_model { nullptr };
// Bounding box, cached. // Bounding box, cached.
mutable BoundingBoxf3 m_bounding_box; mutable BoundingBoxf3 m_bounding_box_approx;
mutable bool m_bounding_box_valid; mutable bool m_bounding_box_approx_valid { false };
mutable BoundingBoxf3 m_bounding_box_exact;
mutable bool m_bounding_box_exact_valid { false };
mutable bool m_min_max_z_valid { false };
mutable BoundingBoxf3 m_raw_bounding_box; mutable BoundingBoxf3 m_raw_bounding_box;
mutable bool m_raw_bounding_box_valid; mutable bool m_raw_bounding_box_valid { false };
mutable BoundingBoxf3 m_raw_mesh_bounding_box; mutable BoundingBoxf3 m_raw_mesh_bounding_box;
mutable bool m_raw_mesh_bounding_box_valid; mutable bool m_raw_mesh_bounding_box_valid { false };
// Called by Print::apply() to set the model pointer after making a copy. // Called by Print::apply() to set the model pointer after making a copy.
friend class Print; friend class Print;
@ -602,8 +616,7 @@ private:
friend class UndoRedo::StackImpl; friend class UndoRedo::StackImpl;
// Used for deserialization -> Don't allocate any IDs for the ModelObject or its config. // Used for deserialization -> Don't allocate any IDs for the ModelObject or its config.
ModelObject() : ModelObject() :
ObjectBase(-1), config(-1), layer_height_profile(-1), ObjectBase(-1), config(-1), layer_height_profile(-1) {
m_model(nullptr), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) {
assert(this->id().invalid()); assert(this->id().invalid());
assert(this->config.id().invalid()); assert(this->config.id().invalid());
assert(this->layer_height_profile.id().invalid()); assert(this->layer_height_profile.id().invalid());
@ -614,12 +627,17 @@ private:
Internal::StaticSerializationWrapper<LayerHeightProfile> layer_heigth_profile_wrapper(layer_height_profile); Internal::StaticSerializationWrapper<LayerHeightProfile> layer_heigth_profile_wrapper(layer_height_profile);
ar(name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_heigth_profile_wrapper, ar(name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_heigth_profile_wrapper,
sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation, sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation,
m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid, m_bounding_box_approx, m_bounding_box_approx_valid,
m_bounding_box_exact, m_bounding_box_exact_valid, m_min_max_z_valid,
m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid,
cut_connectors, cut_id); cut_connectors, cut_id);
} }
// Called by Print::validate() from the UI thread. // Called by Print::validate() from the UI thread.
unsigned int update_instances_print_volume_state(const BuildVolume &build_volume); unsigned int update_instances_print_volume_state(const BuildVolume &build_volume);
// Called by min_z(), max_z()
void update_min_max_z();
}; };
enum class EnforcerBlockerType : int8_t { enum class EnforcerBlockerType : int8_t {
@ -1103,7 +1121,7 @@ public:
// flag showing the position of this instance with respect to the print volume (set by Print::validate() using ModelObject::check_instances_print_volume_state()) // flag showing the position of this instance with respect to the print volume (set by Print::validate() using ModelObject::check_instances_print_volume_state())
ModelInstanceEPrintVolumeState print_volume_state; ModelInstanceEPrintVolumeState print_volume_state;
// Whether or not this instance is printable // Whether or not this instance is printable
bool printable; bool printable { true };
ModelObject* get_object() const { return this->object; } ModelObject* get_object() const { return this->object; }
@ -1153,9 +1171,7 @@ public:
// To be called on an external mesh // To be called on an external mesh
void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const; void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const;
// Calculate a bounding box of a transformed mesh. To be called on an external mesh. // Transform an external bounding box, thus the resulting bounding box is no more snug.
BoundingBoxf3 transform_mesh_bounding_box(const TriangleMesh& mesh, bool dont_translate = false) const;
// Transform an external bounding box.
BoundingBoxf3 transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate = false) const; BoundingBoxf3 transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate = false) const;
// Transform an external vector. // Transform an external vector.
Vec3d transform_vector(const Vec3d& v, bool dont_translate = false) const; Vec3d transform_vector(const Vec3d& v, bool dont_translate = false) const;
@ -1198,7 +1214,7 @@ private:
ModelObject* object; ModelObject* object;
// Constructor, which assigns a new unique ID. // Constructor, which assigns a new unique ID.
explicit ModelInstance(ModelObject* object) : print_volume_state(ModelInstancePVS_Inside), printable(true), object(object) { assert(this->id().valid()); } explicit ModelInstance(ModelObject* object) : print_volume_state(ModelInstancePVS_Inside), object(object) { assert(this->id().valid()); }
// Constructor, which assigns a new unique ID. // Constructor, which assigns a new unique ID.
explicit ModelInstance(ModelObject *object, const ModelInstance &other) : explicit ModelInstance(ModelObject *object, const ModelInstance &other) :
m_transformation(other.m_transformation), print_volume_state(ModelInstancePVS_Inside), printable(other.printable), object(object) { assert(this->id().valid() && this->id() != other.id()); } m_transformation(other.m_transformation), print_volume_state(ModelInstancePVS_Inside), printable(other.printable), object(object) { assert(this->id().valid() && this->id() != other.id()); }
@ -1313,8 +1329,12 @@ public:
void delete_material(t_model_material_id material_id); void delete_material(t_model_material_id material_id);
void clear_materials(); void clear_materials();
bool add_default_instances(); bool add_default_instances();
// Returns approximate axis aligned bounding box of this model // Returns approximate axis aligned bounding box of this model.
BoundingBoxf3 bounding_box() const; BoundingBoxf3 bounding_box_approx() const;
// Returns exact axis aligned bounding box of this model.
BoundingBoxf3 bounding_box_exact() const;
// Return maximum height of all printable objects.
double max_z() const;
// Set the print_volume_state of PrintObject::instances, // Set the print_volume_state of PrintObject::instances,
// return total number of printable objects. // return total number of printable objects.
unsigned int update_print_volume_state(const BuildVolume &build_volume); unsigned int update_print_volume_state(const BuildVolume &build_volume);

View file

@ -2861,8 +2861,9 @@ void PrintConfigDef::init_fff_params()
def->label = L("Branch Density"); def->label = L("Branch Density");
def->category = L("Support material"); def->category = L("Support material");
def->tooltip = L("Adjusts the density of the support structure used to generate the tips of the branches. " def->tooltip = L("Adjusts the density of the support structure used to generate the tips of the branches. "
"A higher value results in better overhangs, but the supports are harder to remove. " "A higher value results in better overhangs but the supports are harder to remove, "
"Use Support Roof for very high values or ensure support density is similarly high at the top."); "thus it is recommended to enable top support interfaces instead of a high branch density value "
"if dense interfaces are needed.");
def->sidetext = L("%"); def->sidetext = L("%");
def->min = 5; def->min = 5;
def->max_literal = 35; def->max_literal = 35;
@ -3654,7 +3655,7 @@ void PrintConfigDef::init_sla_params()
def = this->add_nullable("idle_temperature", coInts); def = this->add_nullable("idle_temperature", coInts);
def->label = L("Idle temperature"); def->label = L("Idle temperature");
def->tooltip = L("Nozzle temperature when the tool is currently not used in multi-tool setups." def->tooltip = L("Nozzle temperature when the tool is currently not used in multi-tool setups."
"This is only used when 'Ooze prevention is active in Print Settings.'"); "This is only used when 'Ooze prevention' is active in Print Settings.");
def->sidetext = L("°C"); def->sidetext = L("°C");
def->min = 0; def->min = 0;
def->max = max_temp; def->max = max_temp;

View file

@ -77,7 +77,7 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, const Transfor
Vec3d bbox_center = bbox.center(); Vec3d bbox_center = bbox.center();
// We may need to rotate the bbox / bbox_center from the original instance to the current instance. // 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_matrix(), instances.front().model_instance->get_matrix()); double z_diff = Geometry::rotation_diff_z(model_object->instances.front()->get_matrix(), instances.front().model_instance->get_matrix());
if (std::abs(z_diff) > EPSILON) { if (std::abs(z_diff) > EPSILON) {
auto z_rot = Eigen::AngleAxisd(z_diff, Vec3d::UnitZ()); auto z_rot = Eigen::AngleAxisd(z_diff, Vec3d::UnitZ());
bbox = bbox.transformed(Transform3d(z_rot)); bbox = bbox.transformed(Transform3d(z_rot));
bbox_center = (z_rot * bbox_center).eval(); bbox_center = (z_rot * bbox_center).eval();
@ -87,6 +87,7 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, const Transfor
m_center_offset = Point::new_scale(bbox_center.x(), bbox_center.y()); 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. // 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>(); m_size = (bbox.size() * (1. / SCALING_FACTOR)).cast<coord_t>();
m_size.z() = model_object->max_z();
this->set_instances(std::move(instances)); this->set_instances(std::move(instances));
} }
@ -1736,7 +1737,7 @@ void PrintObject::update_slicing_parameters()
{ {
if (!m_slicing_params.valid) if (!m_slicing_params.valid)
m_slicing_params = SlicingParameters::create_from_config( m_slicing_params = SlicingParameters::create_from_config(
this->print()->config(), m_config, this->model_object()->bounding_box().max.z(), this->object_extruders()); this->print()->config(), m_config, this->model_object()->max_z(), this->object_extruders());
} }
SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig& full_config, const ModelObject& model_object, float object_max_z) SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig& full_config, const ModelObject& model_object, float object_max_z)
@ -2226,9 +2227,11 @@ void PrintObject::combine_infill()
void PrintObject::_generate_support_material() void PrintObject::_generate_support_material()
{ {
if (m_config.support_material_style == smsTree || m_config.support_material_style == smsOrganic) { if (this->has_support() && (m_config.support_material_style == smsTree || m_config.support_material_style == smsOrganic)) {
fff_tree_support_generate(*this, std::function<void()>([this](){ this->throw_if_canceled(); })); fff_tree_support_generate(*this, std::function<void()>([this](){ this->throw_if_canceled(); }));
} else { } else {
// If support style is set to Organic however only raft will be built but no support,
// build snug raft instead.
PrintObjectSupportMaterial support_material(this, m_slicing_params); PrintObjectSupportMaterial support_material(this, m_slicing_params);
support_material.generate(*this); support_material.generate(*this);
} }

View file

@ -335,6 +335,7 @@ SupportParameters::SupportParameters(const PrintObject &object)
this->first_layer_flow = Slic3r::support_material_1st_layer_flow(&object, float(slicing_params.first_print_layer_height)); this->first_layer_flow = Slic3r::support_material_1st_layer_flow(&object, float(slicing_params.first_print_layer_height));
this->support_material_flow = Slic3r::support_material_flow(&object, float(slicing_params.layer_height)); this->support_material_flow = Slic3r::support_material_flow(&object, float(slicing_params.layer_height));
this->support_material_interface_flow = Slic3r::support_material_interface_flow(&object, float(slicing_params.layer_height)); this->support_material_interface_flow = Slic3r::support_material_interface_flow(&object, float(slicing_params.layer_height));
this->raft_interface_flow = support_material_interface_flow;
// Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um. // Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um.
this->support_layer_height_min = scaled<coord_t>(0.01); this->support_layer_height_min = scaled<coord_t>(0.01);
@ -377,13 +378,14 @@ SupportParameters::SupportParameters(const PrintObject &object)
this->base_angle = Geometry::deg2rad(float(object_config.support_material_angle.value)); this->base_angle = Geometry::deg2rad(float(object_config.support_material_angle.value));
this->interface_angle = Geometry::deg2rad(float(object_config.support_material_angle.value + 90.)); this->interface_angle = Geometry::deg2rad(float(object_config.support_material_angle.value + 90.));
this->interface_spacing = object_config.support_material_interface_spacing.value + this->support_material_interface_flow.spacing(); double interface_spacing = object_config.support_material_interface_spacing.value + this->support_material_interface_flow.spacing();
this->interface_density = std::min(1., this->support_material_interface_flow.spacing() / this->interface_spacing); this->interface_density = std::min(1., this->support_material_interface_flow.spacing() / interface_spacing);
this->support_spacing = object_config.support_material_spacing.value + this->support_material_flow.spacing(); double raft_interface_spacing = object_config.support_material_interface_spacing.value + this->raft_interface_flow.spacing();
this->support_density = std::min(1., this->support_material_flow.spacing() / this->support_spacing); this->raft_interface_density = std::min(1., this->raft_interface_flow.spacing() / raft_interface_spacing);
double support_spacing = object_config.support_material_spacing.value + this->support_material_flow.spacing();
this->support_density = std::min(1., this->support_material_flow.spacing() / support_spacing);
if (object_config.support_material_interface_layers.value == 0) { if (object_config.support_material_interface_layers.value == 0) {
// No interface layers allowed, print everything with the base support pattern. // No interface layers allowed, print everything with the base support pattern.
this->interface_spacing = this->support_spacing;
this->interface_density = this->support_density; this->interface_density = this->support_density;
} }
@ -393,6 +395,7 @@ SupportParameters::SupportParameters(const PrintObject &object)
support_pattern == smpHoneycomb ? ipHoneycomb : support_pattern == smpHoneycomb ? ipHoneycomb :
this->support_density > 0.95 || this->with_sheath ? ipRectilinear : ipSupportBase; this->support_density > 0.95 || this->with_sheath ? ipRectilinear : ipSupportBase;
this->interface_fill_pattern = (this->interface_density > 0.95 ? ipRectilinear : ipSupportBase); this->interface_fill_pattern = (this->interface_density > 0.95 ? ipRectilinear : ipSupportBase);
this->raft_interface_fill_pattern = this->raft_interface_density > 0.95 ? ipRectilinear : ipSupportBase;
this->contact_fill_pattern = this->contact_fill_pattern =
(object_config.support_material_interface_pattern == smipAuto && slicing_params.soluble_interface) || (object_config.support_material_interface_pattern == smipAuto && slicing_params.soluble_interface) ||
object_config.support_material_interface_pattern == smipConcentric ? object_config.support_material_interface_pattern == smipConcentric ?
@ -799,10 +802,6 @@ public:
) )
{ {
switch (m_style) { switch (m_style) {
case smsTree:
case smsOrganic:
assert(false);
[[fallthrough]];
case smsGrid: case smsGrid:
{ {
#ifdef SUPPORT_USE_AGG_RASTERIZER #ifdef SUPPORT_USE_AGG_RASTERIZER
@ -893,6 +892,10 @@ public:
polygons_rotate(out, m_support_angle); polygons_rotate(out, m_support_angle);
return out; return out;
} }
case smsTree:
case smsOrganic:
// assert(false);
[[fallthrough]];
case smsSnug: case smsSnug:
// Merge the support polygons by applying morphological closing and inwards smoothing. // Merge the support polygons by applying morphological closing and inwards smoothing.
auto closing_distance = scaled<float>(m_support_material_closing_radius); auto closing_distance = scaled<float>(m_support_material_closing_radius);
@ -1763,7 +1766,7 @@ static inline void fill_contact_layer(
#endif // SLIC3R_DEBUG #endif // SLIC3R_DEBUG
)); ));
// 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra. // 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra.
bool reduce_interfaces = object_config.support_material_style.value != smsSnug && layer_id > 0 && !slicing_params.soluble_interface; bool reduce_interfaces = object_config.support_material_style.value == smsGrid && layer_id > 0 && !slicing_params.soluble_interface;
if (reduce_interfaces) { if (reduce_interfaces) {
// Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions. // Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions.
Polygons dense_interface_polygons = diff(overhang_polygons, lower_layer_polygons_for_dense_interface()); Polygons dense_interface_polygons = diff(overhang_polygons, lower_layer_polygons_for_dense_interface());
@ -2011,11 +2014,11 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::top_contact_layers(
// Find the bottom contact layers above the top surfaces of this layer. // Find the bottom contact layers above the top surfaces of this layer.
static inline SupportGeneratorLayer* detect_bottom_contacts( static inline SupportGeneratorLayer* detect_bottom_contacts(
const SlicingParameters &slicing_params, const SlicingParameters &slicing_params,
const SupportParameters &support_params, const SupportParameters &support_params,
const PrintObject &object, const PrintObject &object,
const Layer &layer, const Layer &layer,
// Existing top contact layers, to which this newly created bottom contact layer will be snapped to guarantee a minimum layer height. // Existing top contact layers, to which this newly created bottom contact layer will be snapped to guarantee a minimum layer height.
const SupportGeneratorLayersPtr &top_contacts, const SupportGeneratorLayersPtr &top_contacts,
// First top contact layer index overlapping with this new bottom interface layer. // First top contact layer index overlapping with this new bottom interface layer.
size_t contact_idx, size_t contact_idx,
// To allocate a new layer from. // To allocate a new layer from.
@ -2888,6 +2891,7 @@ SupportGeneratorLayersPtr generate_raft_base(
// If there is brim to be generated, calculate the trimming regions. // If there is brim to be generated, calculate the trimming regions.
Polygons brim; Polygons brim;
if (object.has_brim()) { if (object.has_brim()) {
// The object does not have a raft.
// Calculate the area covered by the brim. // Calculate the area covered by the brim.
const BrimType brim_type = object.config().brim_type; const BrimType brim_type = object.config().brim_type;
const bool brim_outer = brim_type == btOuterOnly || brim_type == btOuterAndInner; const bool brim_outer = brim_type == btOuterOnly || brim_type == btOuterAndInner;
@ -2948,12 +2952,20 @@ SupportGeneratorLayersPtr generate_raft_base(
if (slicing_params.raft_layers() > 1) { if (slicing_params.raft_layers() > 1) {
Polygons base; Polygons base;
Polygons columns; Polygons columns;
Polygons first_layer;
if (columns_base != nullptr) { if (columns_base != nullptr) {
base = columns_base->polygons; if (columns_base->print_z > slicing_params.raft_contact_top_z - EPSILON) {
columns = base; // Classic supports with colums above the raft interface.
if (! interface_polygons.empty()) base = columns_base->polygons;
// Trim the 1st layer columns with the inflated interface polygons. columns = base;
columns = diff(columns, interface_polygons); if (! interface_polygons.empty())
// Trim the 1st layer columns with the inflated interface polygons.
columns = diff(columns, interface_polygons);
} else {
// Organic supports with raft on print bed.
assert(is_approx(columns_base->print_z, slicing_params.first_print_layer_height));
first_layer = columns_base->polygons;
}
} }
if (! interface_polygons.empty()) { if (! interface_polygons.empty()) {
// Merge the untrimmed columns base with the expanded raft interface, to be used for the support base and interface. // Merge the untrimmed columns base with the expanded raft interface, to be used for the support base and interface.
@ -2967,7 +2979,8 @@ SupportGeneratorLayersPtr generate_raft_base(
new_layer.print_z = slicing_params.first_print_layer_height; new_layer.print_z = slicing_params.first_print_layer_height;
new_layer.height = slicing_params.first_print_layer_height; new_layer.height = slicing_params.first_print_layer_height;
new_layer.bottom_z = 0.; new_layer.bottom_z = 0.;
new_layer.polygons = inflate_factor_1st_layer > 0 ? expand(base, inflate_factor_1st_layer) : base; first_layer = union_(std::move(first_layer), base);
new_layer.polygons = inflate_factor_1st_layer > 0 ? expand(first_layer, inflate_factor_1st_layer) : first_layer;
} }
// Insert the base layers. // Insert the base layers.
for (size_t i = 1; i < slicing_params.base_raft_layers; ++ i) { for (size_t i = 1; i < slicing_params.base_raft_layers; ++ i) {
@ -3045,7 +3058,7 @@ std::pair<SupportGeneratorLayersPtr, SupportGeneratorLayersPtr> PrintObjectSuppo
m_object_config->support_material_interface_extruder.value > 0 && m_print_config->filament_soluble.get_at(m_object_config->support_material_interface_extruder.value - 1) && m_object_config->support_material_interface_extruder.value > 0 && m_print_config->filament_soluble.get_at(m_object_config->support_material_interface_extruder.value - 1) &&
// Base extruder: Either "print with active extruder" not soluble. // Base extruder: Either "print with active extruder" not soluble.
(m_object_config->support_material_extruder.value == 0 || ! m_print_config->filament_soluble.get_at(m_object_config->support_material_extruder.value - 1)); (m_object_config->support_material_extruder.value == 0 || ! m_print_config->filament_soluble.get_at(m_object_config->support_material_extruder.value - 1));
bool snug_supports = m_object_config->support_material_style.value == smsSnug; bool snug_supports = m_object_config->support_material_style.value != smsGrid;
int num_interface_layers_top = m_object_config->support_material_interface_layers; int num_interface_layers_top = m_object_config->support_material_interface_layers;
int num_interface_layers_bottom = m_object_config->support_material_bottom_interface_layers; int num_interface_layers_bottom = m_object_config->support_material_bottom_interface_layers;
if (num_interface_layers_bottom < 0) if (num_interface_layers_bottom < 0)
@ -3492,12 +3505,7 @@ static inline void fill_expolygons_with_sheath_generate_paths(
if (polygons.empty()) if (polygons.empty())
return; return;
if (with_sheath) { if (! with_sheath) {
if (density == 0) {
tree_supports_generate_paths(dst, polygons, flow);
return;
}
} else {
fill_expolygons_generate_paths(dst, closing_ex(polygons, float(SCALED_EPSILON)), filler, density, role, flow); fill_expolygons_generate_paths(dst, closing_ex(polygons, float(SCALED_EPSILON)), filler, density, role, flow);
return; return;
} }
@ -4227,33 +4235,41 @@ void generate_support_toolpaths(
} }
// Insert the raft base layers. // Insert the raft base layers.
size_t n_raft_layers = size_t(std::max(0, int(slicing_params.raft_layers()) - 1)); auto n_raft_layers = std::min<size_t>(support_layers.size(), std::max(0, int(slicing_params.raft_layers()) - 1));
tbb::parallel_for(tbb::blocked_range<size_t>(0, n_raft_layers), tbb::parallel_for(tbb::blocked_range<size_t>(0, n_raft_layers),
[&support_layers, &raft_layers, &config, &support_params, &slicing_params, [&support_layers, &raft_layers, &intermediate_layers, &config, &support_params, &slicing_params,
&bbox_object, raft_angle_1st_layer, raft_angle_base, raft_angle_interface, link_max_length_factor] &bbox_object, raft_angle_1st_layer, raft_angle_base, raft_angle_interface, link_max_length_factor]
(const tbb::blocked_range<size_t>& range) { (const tbb::blocked_range<size_t>& range) {
for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id)
{ {
assert(support_layer_id < raft_layers.size()); assert(support_layer_id < raft_layers.size());
SupportLayer &support_layer = *support_layers[support_layer_id]; SupportLayer &support_layer = *support_layers[support_layer_id];
assert(support_layer.support_fills.entities.empty()); assert(support_layer.support_fills.entities.empty());
SupportGeneratorLayer &raft_layer = *raft_layers[support_layer_id]; SupportGeneratorLayer &raft_layer = *raft_layers[support_layer_id];
std::unique_ptr<Fill> filler_interface = std::unique_ptr<Fill>(Fill::new_from_type(support_params.interface_fill_pattern)); std::unique_ptr<Fill> filler_interface = std::unique_ptr<Fill>(Fill::new_from_type(support_params.raft_interface_fill_pattern));
std::unique_ptr<Fill> filler_support = std::unique_ptr<Fill>(Fill::new_from_type(support_params.base_fill_pattern)); std::unique_ptr<Fill> filler_support = std::unique_ptr<Fill>(Fill::new_from_type(support_params.base_fill_pattern));
filler_interface->set_bounding_box(bbox_object); filler_interface->set_bounding_box(bbox_object);
filler_support->set_bounding_box(bbox_object); filler_support->set_bounding_box(bbox_object);
// Print the tree supports cutting through the raft with the exception of the 1st layer, where a full support layer will be printed below
// both the raft and the trees.
// Trim the raft layers with the tree polygons.
const Polygons &tree_polygons =
support_layer_id > 0 && support_layer_id < intermediate_layers.size() && is_approx(intermediate_layers[support_layer_id]->print_z, support_layer.print_z) ?
intermediate_layers[support_layer_id]->polygons : Polygons();
// Print the support base below the support columns, or the support base for the support columns plus the contacts. // Print the support base below the support columns, or the support base for the support columns plus the contacts.
if (support_layer_id > 0) { if (support_layer_id > 0) {
const Polygons &to_infill_polygons = (support_layer_id < slicing_params.base_raft_layers) ? const Polygons &to_infill_polygons = (support_layer_id < slicing_params.base_raft_layers) ?
raft_layer.polygons : raft_layer.polygons :
//FIXME misusing contact_polygons for support columns. //FIXME misusing contact_polygons for support columns.
((raft_layer.contact_polygons == nullptr) ? Polygons() : *raft_layer.contact_polygons); ((raft_layer.contact_polygons == nullptr) ? Polygons() : *raft_layer.contact_polygons);
// Trees may cut through the raft layers down to a print bed.
Flow flow(float(support_params.support_material_flow.width()), float(raft_layer.height), support_params.support_material_flow.nozzle_diameter());
assert(!raft_layer.bridging);
if (! to_infill_polygons.empty()) { if (! to_infill_polygons.empty()) {
assert(! raft_layer.bridging); Fill *filler = filler_support.get();
Flow flow(float(support_params.support_material_flow.width()), float(raft_layer.height), support_params.support_material_flow.nozzle_diameter());
Fill * filler = filler_support.get();
filler->angle = raft_angle_base; filler->angle = raft_angle_base;
filler->spacing = support_params.support_material_flow.spacing(); filler->spacing = support_params.support_material_flow.spacing();
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.support_density)); filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.support_density));
@ -4261,13 +4277,15 @@ void generate_support_toolpaths(
// Destination // Destination
support_layer.support_fills.entities, support_layer.support_fills.entities,
// Regions to fill // Regions to fill
to_infill_polygons, tree_polygons.empty() ? to_infill_polygons : diff(to_infill_polygons, tree_polygons),
// Filler and its parameters // Filler and its parameters
filler, float(support_params.support_density), filler, float(support_params.support_density),
// Extrusion parameters // Extrusion parameters
ExtrusionRole::SupportMaterial, flow, ExtrusionRole::SupportMaterial, flow,
support_params.with_sheath, false); support_params.with_sheath, false);
} }
if (! tree_polygons.empty())
tree_supports_generate_paths(support_layer.support_fills.entities, tree_polygons, flow);
} }
Fill *filler = filler_interface.get(); Fill *filler = filler_interface.get();
@ -4284,8 +4302,8 @@ void generate_support_toolpaths(
// value that guarantees that all layers are correctly aligned. // value that guarantees that all layers are correctly aligned.
filler->spacing = support_params.support_material_flow.spacing(); filler->spacing = support_params.support_material_flow.spacing();
assert(! raft_layer.bridging); assert(! raft_layer.bridging);
flow = Flow(float(support_params.support_material_interface_flow.width()), float(raft_layer.height), support_params.support_material_flow.nozzle_diameter()); flow = Flow(float(support_params.raft_interface_flow.width()), float(raft_layer.height), support_params.raft_interface_flow.nozzle_diameter());
density = float(support_params.interface_density); density = float(support_params.raft_interface_density);
} else } else
continue; continue;
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density));
@ -4293,7 +4311,7 @@ void generate_support_toolpaths(
// Destination // Destination
support_layer.support_fills.entities, support_layer.support_fills.entities,
// Regions to fill // Regions to fill
raft_layer.polygons, tree_polygons.empty() ? raft_layer.polygons : diff(raft_layer.polygons, tree_polygons),
// Filler and its parameters // Filler and its parameters
filler, density, filler, density,
// Extrusion parameters // Extrusion parameters
@ -4328,7 +4346,7 @@ void generate_support_toolpaths(
tbb::parallel_for(tbb::blocked_range<size_t>(n_raft_layers, support_layers.size()), tbb::parallel_for(tbb::blocked_range<size_t>(n_raft_layers, support_layers.size()),
[&config, &support_params, &support_layers, &bottom_contacts, &top_contacts, &intermediate_layers, &interface_layers, &base_interface_layers, &layer_caches, &loop_interface_processor, [&config, &support_params, &support_layers, &bottom_contacts, &top_contacts, &intermediate_layers, &interface_layers, &base_interface_layers, &layer_caches, &loop_interface_processor,
&bbox_object, &angles, link_max_length_factor] &bbox_object, &angles, n_raft_layers, link_max_length_factor]
(const tbb::blocked_range<size_t>& range) { (const tbb::blocked_range<size_t>& range) {
// Indices of the 1st layer in their respective container at the support layer height. // Indices of the 1st layer in their respective container at the support layer height.
size_t idx_layer_bottom_contact = size_t(-1); size_t idx_layer_bottom_contact = size_t(-1);
@ -4342,6 +4360,11 @@ void generate_support_toolpaths(
auto filler_first_layer_ptr = std::unique_ptr<Fill>(range.begin() == 0 && support_params.contact_fill_pattern != fill_type_first_layer ? Fill::new_from_type(fill_type_first_layer) : nullptr); auto filler_first_layer_ptr = std::unique_ptr<Fill>(range.begin() == 0 && support_params.contact_fill_pattern != fill_type_first_layer ? Fill::new_from_type(fill_type_first_layer) : nullptr);
// Pointer to the 1st layer interface filler. // Pointer to the 1st layer interface filler.
auto filler_first_layer = filler_first_layer_ptr ? filler_first_layer_ptr.get() : filler_interface.get(); auto filler_first_layer = filler_first_layer_ptr ? filler_first_layer_ptr.get() : filler_interface.get();
// Filler for the 1st layer interface, if different from filler_interface.
auto filler_raft_contact_ptr = std::unique_ptr<Fill>(range.begin() == n_raft_layers && config.support_material_interface_layers.value == 0 ?
Fill::new_from_type(support_params.raft_interface_fill_pattern) : nullptr);
// Pointer to the 1st layer interface filler.
auto filler_raft_contact = filler_raft_contact_ptr ? filler_raft_contact_ptr.get() : filler_interface.get();
// Filler for the base interface (to be used for soluble interface / non soluble base, to produce non soluble interface layer below soluble interface layer). // Filler for the base interface (to be used for soluble interface / non soluble base, to produce non soluble interface layer below soluble interface layer).
auto filler_base_interface = std::unique_ptr<Fill>(base_interface_layers.empty() ? nullptr : auto filler_base_interface = std::unique_ptr<Fill>(base_interface_layers.empty() ? nullptr :
Fill::new_from_type(support_params.interface_density > 0.95 || support_params.with_sheath ? ipRectilinear : ipSupportBase)); Fill::new_from_type(support_params.interface_density > 0.95 || support_params.with_sheath ? ipRectilinear : ipSupportBase));
@ -4349,6 +4372,8 @@ void generate_support_toolpaths(
filler_interface->set_bounding_box(bbox_object); filler_interface->set_bounding_box(bbox_object);
if (filler_first_layer_ptr) if (filler_first_layer_ptr)
filler_first_layer_ptr->set_bounding_box(bbox_object); filler_first_layer_ptr->set_bounding_box(bbox_object);
if (filler_raft_contact_ptr)
filler_raft_contact_ptr->set_bounding_box(bbox_object);
if (filler_base_interface) if (filler_base_interface)
filler_base_interface->set_bounding_box(bbox_object); filler_base_interface->set_bounding_box(bbox_object);
filler_support->set_bounding_box(bbox_object); filler_support->set_bounding_box(bbox_object);
@ -4356,7 +4381,7 @@ void generate_support_toolpaths(
{ {
SupportLayer &support_layer = *support_layers[support_layer_id]; SupportLayer &support_layer = *support_layers[support_layer_id];
LayerCache &layer_cache = layer_caches[support_layer_id]; LayerCache &layer_cache = layer_caches[support_layer_id];
float interface_angle_delta = config.support_material_style.value == smsSnug || config.support_material_style.value == smsTree || config.support_material_style.value == smsOrganic ? float interface_angle_delta = config.support_material_style.value != smsGrid ?
(support_layer.interface_id() & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.) : (support_layer.interface_id() & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.) :
0; 0;
@ -4387,10 +4412,12 @@ void generate_support_toolpaths(
if (idx_layer_intermediate < intermediate_layers.size() && intermediate_layers[idx_layer_intermediate]->print_z < support_layer.print_z + EPSILON) if (idx_layer_intermediate < intermediate_layers.size() && intermediate_layers[idx_layer_intermediate]->print_z < support_layer.print_z + EPSILON)
base_layer.layer = intermediate_layers[idx_layer_intermediate]; base_layer.layer = intermediate_layers[idx_layer_intermediate];
bool raft_layer = support_layer_id == n_raft_layers;
if (config.support_material_interface_layers == 0) { if (config.support_material_interface_layers == 0) {
// If no top interface layers were requested, we treat the contact layer exactly as a generic base layer. // If no top interface layers were requested, we treat the contact layer exactly as a generic base layer.
if (support_params.can_merge_support_regions) { // Don't merge the raft contact layer though.
if (base_layer.could_merge(top_contact_layer)) if (support_params.can_merge_support_regions && ! raft_layer) {
if (base_layer.could_merge(top_contact_layer))
base_layer.merge(std::move(top_contact_layer)); base_layer.merge(std::move(top_contact_layer));
else if (base_layer.empty()) else if (base_layer.empty())
base_layer = std::move(top_contact_layer); base_layer = std::move(top_contact_layer);
@ -4400,7 +4427,7 @@ void generate_support_toolpaths(
// If no loops are allowed, we treat the contact layer exactly as a generic interface layer. // If no loops are allowed, we treat the contact layer exactly as a generic interface layer.
// Merge interface_layer into top_contact_layer, as the top_contact_layer is not synchronized and therefore it will be used // Merge interface_layer into top_contact_layer, as the top_contact_layer is not synchronized and therefore it will be used
// to trim other layers. // to trim other layers.
if (top_contact_layer.could_merge(interface_layer)) if (top_contact_layer.could_merge(interface_layer) && ! raft_layer)
top_contact_layer.merge(std::move(interface_layer)); top_contact_layer.merge(std::move(interface_layer));
} }
if ((config.support_material_interface_layers == 0 || config.support_material_bottom_interface_layers == 0) && support_params.can_merge_support_regions) { if ((config.support_material_interface_layers == 0 || config.support_material_bottom_interface_layers == 0) && support_params.can_merge_support_regions) {
@ -4408,7 +4435,7 @@ void generate_support_toolpaths(
base_layer.merge(std::move(bottom_contact_layer)); base_layer.merge(std::move(bottom_contact_layer));
else if (base_layer.empty() && ! bottom_contact_layer.empty() && ! bottom_contact_layer.layer->bridging) else if (base_layer.empty() && ! bottom_contact_layer.empty() && ! bottom_contact_layer.layer->bridging)
base_layer = std::move(bottom_contact_layer); base_layer = std::move(bottom_contact_layer);
} else if (bottom_contact_layer.could_merge(top_contact_layer)) } else if (bottom_contact_layer.could_merge(top_contact_layer) && ! raft_layer)
top_contact_layer.merge(std::move(bottom_contact_layer)); top_contact_layer.merge(std::move(bottom_contact_layer));
else if (bottom_contact_layer.could_merge(interface_layer)) else if (bottom_contact_layer.could_merge(interface_layer))
bottom_contact_layer.merge(std::move(interface_layer)); bottom_contact_layer.merge(std::move(interface_layer));
@ -4426,35 +4453,44 @@ void generate_support_toolpaths(
#endif #endif
// Top and bottom contacts, interface layers. // Top and bottom contacts, interface layers.
for (size_t i = 0; i < 3; ++ i) { enum class InterfaceLayerType { TopContact, BottomContact, RaftContact, Interface, InterfaceAsBase };
SupportGeneratorLayerExtruded &layer_ex = (i == 0) ? top_contact_layer : (i == 1 ? bottom_contact_layer : interface_layer); auto extrude_interface = [&](SupportGeneratorLayerExtruded &layer_ex, InterfaceLayerType interface_layer_type) {
if (layer_ex.empty() || layer_ex.polygons_to_extrude().empty()) if (! layer_ex.empty() && ! layer_ex.polygons_to_extrude().empty()) {
continue; bool interface_as_base = interface_layer_type == InterfaceLayerType::InterfaceAsBase;
bool interface_as_base = config.support_material_interface_layers.value == 0 || bool raft_contact = interface_layer_type == InterfaceLayerType::RaftContact;
(config.support_material_bottom_interface_layers == 0 && &layer_ex == &bottom_contact_layer); //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore
//FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b)
// the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) auto *filler = raft_contact ? filler_raft_contact : filler_interface.get();
auto interface_flow = layer_ex.layer->bridging ? auto interface_flow = layer_ex.layer->bridging ?
Flow::bridging_flow(layer_ex.layer->height, support_params.support_material_bottom_interface_flow.nozzle_diameter()) : Flow::bridging_flow(layer_ex.layer->height, support_params.support_material_bottom_interface_flow.nozzle_diameter()) :
(interface_as_base ? &support_params.support_material_flow : &support_params.support_material_interface_flow)->with_height(float(layer_ex.layer->height)); (raft_contact ? &support_params.raft_interface_flow :
filler_interface->angle = interface_as_base ? interface_as_base ? &support_params.support_material_flow : &support_params.support_material_interface_flow)
// If zero interface layers are configured, use the same angle as for the base layers. ->with_height(float(layer_ex.layer->height));
angles[support_layer_id % angles.size()] : filler->angle = interface_as_base ?
// Use interface angle for the interface layers. // If zero interface layers are configured, use the same angle as for the base layers.
support_params.interface_angle + interface_angle_delta; angles[support_layer_id % angles.size()] :
double density = interface_as_base ? support_params.support_density : support_params.interface_density; // Use interface angle for the interface layers.
filler_interface->spacing = interface_as_base ? support_params.support_material_flow.spacing() : support_params.support_material_interface_flow.spacing(); support_params.interface_angle + interface_angle_delta;
filler_interface->link_max_length = coord_t(scale_(filler_interface->spacing * link_max_length_factor / density)); double density = raft_contact ? support_params.raft_interface_density : interface_as_base ? support_params.support_density : support_params.interface_density;
fill_expolygons_generate_paths( filler->spacing = raft_contact ? support_params.raft_interface_flow.spacing() :
// Destination interface_as_base ? support_params.support_material_flow.spacing() : support_params.support_material_interface_flow.spacing();
layer_ex.extrusions, filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density));
// Regions to fill fill_expolygons_generate_paths(
union_safety_offset_ex(layer_ex.polygons_to_extrude()), // Destination
// Filler and its parameters layer_ex.extrusions,
filler_interface.get(), float(density), // Regions to fill
// Extrusion parameters union_safety_offset_ex(layer_ex.polygons_to_extrude()),
ExtrusionRole::SupportMaterialInterface, interface_flow); // Filler and its parameters
} filler, float(density),
// Extrusion parameters
ExtrusionRole::SupportMaterialInterface, interface_flow);
}
};
const bool top_interfaces = config.support_material_interface_layers.value != 0;
const bool bottom_interfaces = top_interfaces && config.support_material_bottom_interface_layers != 0;
extrude_interface(top_contact_layer, raft_layer ? InterfaceLayerType::RaftContact : top_interfaces ? InterfaceLayerType::TopContact : InterfaceLayerType::InterfaceAsBase);
extrude_interface(bottom_contact_layer, bottom_interfaces ? InterfaceLayerType::BottomContact : InterfaceLayerType::InterfaceAsBase);
extrude_interface(interface_layer, top_interfaces ? InterfaceLayerType::Interface : InterfaceLayerType::InterfaceAsBase);
// Base interface layers under soluble interfaces // Base interface layers under soluble interfaces
if ( ! base_interface_layer.empty() && ! base_interface_layer.polygons_to_extrude().empty()) { if ( ! base_interface_layer.empty() && ! base_interface_layer.polygons_to_extrude().empty()) {
@ -4491,6 +4527,7 @@ void generate_support_toolpaths(
float density = float(support_params.support_density); float density = float(support_params.support_density);
bool sheath = support_params.with_sheath; bool sheath = support_params.with_sheath;
bool no_sort = false; bool no_sort = false;
bool done = false;
if (base_layer.layer->bottom_z < EPSILON) { if (base_layer.layer->bottom_z < EPSILON) {
// Base flange (the 1st layer). // Base flange (the 1st layer).
filler = filler_first_layer; filler = filler_first_layer;
@ -4504,18 +4541,21 @@ void generate_support_toolpaths(
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density));
sheath = true; sheath = true;
no_sort = true; no_sort = true;
} else if (config.support_material_style == SupportMaterialStyle::smsOrganic) {
tree_supports_generate_paths(base_layer.extrusions, base_layer.polygons_to_extrude(), flow);
done = true;
} }
fill_expolygons_with_sheath_generate_paths( if (! done)
// Destination fill_expolygons_with_sheath_generate_paths(
base_layer.extrusions, // Destination
// Regions to fill base_layer.extrusions,
base_layer.polygons_to_extrude(), // Regions to fill
// Filler and its parameters base_layer.polygons_to_extrude(),
filler, density, // Filler and its parameters
// Extrusion parameters filler, density,
ExtrusionRole::SupportMaterial, flow, // Extrusion parameters
sheath, no_sort); ExtrusionRole::SupportMaterial, flow,
sheath, no_sort);
} }
// Merge base_interface_layers to base_layers to avoid unneccessary retractions // Merge base_interface_layers to base_layers to avoid unneccessary retractions
@ -4708,3 +4748,4 @@ sub clip_with_shape {
*/ */
} // namespace Slic3r } // namespace Slic3r

View file

@ -13,7 +13,7 @@ class PrintObjectConfig;
// Support layer type to be used by SupportGeneratorLayer. This type carries a much more detailed information // Support layer type to be used by SupportGeneratorLayer. This type carries a much more detailed information
// about the support layer type than the final support layers stored in a PrintObject. // about the support layer type than the final support layers stored in a PrintObject.
enum SupporLayerType { enum class SupporLayerType {
Unknown = 0, Unknown = 0,
// Ratft base layer, to be printed with the support material. // Ratft base layer, to be printed with the support material.
RaftBase, RaftBase,
@ -122,10 +122,17 @@ using SupportGeneratorLayersPtr = std::vector<SupportGeneratorLayer*>;
struct SupportParameters { struct SupportParameters {
SupportParameters(const PrintObject &object); SupportParameters(const PrintObject &object);
// Flow at the 1st print layer.
Flow first_layer_flow; Flow first_layer_flow;
// Flow at the support base (neither top, nor bottom interface).
// Also flow at the raft base with the exception of raft interface and contact layers.
Flow support_material_flow; Flow support_material_flow;
// Flow at the top interface and contact layers.
Flow support_material_interface_flow; Flow support_material_interface_flow;
// Flow at the bottom interfaces and contacts.
Flow support_material_bottom_interface_flow; Flow support_material_bottom_interface_flow;
// Flow at raft inteface & contact layers.
Flow raft_interface_flow;
// Is merging of regions allowed? Could the interface & base support regions be printed with the same extruder? // Is merging of regions allowed? Could the interface & base support regions be printed with the same extruder?
bool can_merge_support_regions; bool can_merge_support_regions;
@ -136,14 +143,23 @@ struct SupportParameters {
float base_angle; float base_angle;
float interface_angle; float interface_angle;
coordf_t interface_spacing;
// Density of the top / bottom interface and contact layers.
coordf_t interface_density; coordf_t interface_density;
coordf_t support_spacing; // Density of the raft interface and contact layers.
coordf_t raft_interface_density;
// Density of the base support layers.
coordf_t support_density; coordf_t support_density;
// Pattern of the sparse infill including sparse raft layers.
InfillPattern base_fill_pattern; InfillPattern base_fill_pattern;
// Pattern of the top / bottom interface and contact layers.
InfillPattern interface_fill_pattern; InfillPattern interface_fill_pattern;
// Pattern of the raft interface and contact layers.
InfillPattern raft_interface_fill_pattern;
// Pattern of the contact layers.
InfillPattern contact_fill_pattern; InfillPattern contact_fill_pattern;
// Shall the sparse (base) layers be printed with a single perimeter line (sheath) for robustness?
bool with_sheath; bool with_sheath;
}; };

View file

@ -158,14 +158,24 @@ TreeModelVolumes::TreeModelVolumes(
{ {
m_anti_overhang = print_object.slice_support_blockers(); m_anti_overhang = print_object.slice_support_blockers();
TreeSupportMeshGroupSettings mesh_settings(print_object); TreeSupportMeshGroupSettings mesh_settings(print_object);
m_layer_outlines.emplace_back(mesh_settings, std::vector<Polygons>{}); const TreeSupportSettings config{ mesh_settings, print_object.slicing_parameters() };
m_current_min_xy_dist = config.xy_min_distance;
m_current_min_xy_dist_delta = config.xy_distance - m_current_min_xy_dist;
assert(m_current_min_xy_dist_delta >= 0);
m_increase_until_radius = config.increase_radius_until_radius;
m_radius_0 = config.getRadius(0);
m_raft_layers = config.raft_layers;
m_current_outline_idx = 0; m_current_outline_idx = 0;
m_layer_outlines.emplace_back(mesh_settings, std::vector<Polygons>{});
std::vector<Polygons> &outlines = m_layer_outlines.front().second; std::vector<Polygons> &outlines = m_layer_outlines.front().second;
outlines.assign(print_object.layer_count(), Polygons{}); size_t num_raft_layers = m_raft_layers.size();
tbb::parallel_for(tbb::blocked_range<size_t>(0, print_object.layer_count(), std::min<size_t>(1, std::max<size_t>(16, print_object.layer_count() / (8 * tbb::this_task_arena::max_concurrency())))), size_t num_layers = print_object.layer_count() + num_raft_layers;
outlines.assign(num_layers, Polygons{});
tbb::parallel_for(tbb::blocked_range<size_t>(num_raft_layers, num_layers, std::min<size_t>(1, std::max<size_t>(16, num_layers / (8 * tbb::this_task_arena::max_concurrency())))),
[&](const tbb::blocked_range<size_t> &range) { [&](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx)
outlines[layer_idx] = to_polygons(expolygons_simplify(print_object.get_layer(layer_idx)->lslices, mesh_settings.resolution)); outlines[layer_idx] = to_polygons(expolygons_simplify(print_object.get_layer(layer_idx - num_raft_layers)->lslices, mesh_settings.resolution));
}); });
} }
#endif #endif
@ -177,13 +187,6 @@ TreeModelVolumes::TreeModelVolumes(
m_min_resolution = std::min(m_min_resolution, data_pair.first.resolution); m_min_resolution = std::min(m_min_resolution, data_pair.first.resolution);
} }
const TreeSupportSettings config{ m_layer_outlines[m_current_outline_idx].first };
m_current_min_xy_dist = config.xy_min_distance;
m_current_min_xy_dist_delta = config.xy_distance - m_current_min_xy_dist;
assert(m_current_min_xy_dist_delta >= 0);
m_increase_until_radius = config.increase_radius_until_radius;
m_radius_0 = config.getRadius(0);
#if 0 #if 0
for (size_t mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++) { for (size_t mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++) {
SliceMeshStorage mesh = storage.meshes[mesh_idx]; SliceMeshStorage mesh = storage.meshes[mesh_idx];
@ -214,7 +217,7 @@ TreeModelVolumes::TreeModelVolumes(
#endif #endif
} }
void TreeModelVolumes::precalculate(const coord_t max_layer, std::function<void()> throw_on_cancel) void TreeModelVolumes::precalculate(const PrintObject& print_object, const coord_t max_layer, std::function<void()> throw_on_cancel)
{ {
auto t_start = std::chrono::high_resolution_clock::now(); auto t_start = std::chrono::high_resolution_clock::now();
m_precalculated = true; m_precalculated = true;
@ -222,7 +225,7 @@ void TreeModelVolumes::precalculate(const coord_t max_layer, std::function<void(
// Get the config corresponding to one mesh that is in the current group. Which one has to be irrelevant. // Get the config corresponding to one mesh that is in the current group. Which one has to be irrelevant.
// Not the prettiest way to do this, but it ensures some calculations that may be a bit more complex // Not the prettiest way to do this, but it ensures some calculations that may be a bit more complex
// like inital layer diameter are only done in once. // like inital layer diameter are only done in once.
TreeSupportSettings config(m_layer_outlines[m_current_outline_idx].first); TreeSupportSettings config(m_layer_outlines[m_current_outline_idx].first, print_object.slicing_parameters());
{ {
// calculate which radius each layer in the tip may have. // calculate which radius each layer in the tip may have.

View file

@ -179,6 +179,10 @@ struct TreeSupportMeshGroupSettings {
// Tree Support Branch Density // Tree Support Branch Density
// Adjusts the density of the support structure used to generate the tips of the branches. A higher value results in better overhangs, // Adjusts the density of the support structure used to generate the tips of the branches. A higher value results in better overhangs,
// but the supports are harder to remove. Use Support Roof for very high values or ensure support density is similarly high at the top. // but the supports are harder to remove. Use Support Roof for very high values or ensure support density is similarly high at the top.
// ->
// Adjusts the density of the support structure used to generate the tips of the branches.
// A higher value results in better overhangs but the supports are harder to remove, thus it is recommended to enable top support interfaces
// instead of a high branch density value if dense interfaces are needed.
// 5%-35% // 5%-35%
double support_tree_top_rate { 15. }; double support_tree_top_rate { 15. };
// Tree Support Tip Diameter // Tree Support Tip Diameter
@ -240,7 +244,7 @@ public:
* Knowledge about branch angle is used to only calculate avoidances and collisions that may actually be needed. * Knowledge about branch angle is used to only calculate avoidances and collisions that may actually be needed.
* Not calling precalculate() will cause the class to lazily calculate avoidances and collisions as needed, which will be a lot slower on systems with more then one or two cores! * Not calling precalculate() will cause the class to lazily calculate avoidances and collisions as needed, which will be a lot slower on systems with more then one or two cores!
*/ */
void precalculate(const coord_t max_layer, std::function<void()> throw_on_cancel); void precalculate(const PrintObject& print_object, const coord_t max_layer, std::function<void()> throw_on_cancel);
/*! /*!
* \brief Provides the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer. * \brief Provides the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer.
@ -614,6 +618,9 @@ private:
*/ */
coord_t m_radius_0; coord_t m_radius_0;
// Z heights of the raft layers (additional layers below the object, last raft layer aligned with the bottom of the first object layer).
std::vector<double> m_raft_layers;
/*! /*!
* \brief Caches for the collision, avoidance and areas on the model where support can be placed safely * \brief Caches for the collision, avoidance and areas on the model where support can be placed safely
* at given radius and layer indices. * at given radius and layer indices.

View file

@ -59,6 +59,91 @@ namespace Slic3r
namespace FFFTreeSupport namespace FFFTreeSupport
{ {
TreeSupportSettings::TreeSupportSettings(const TreeSupportMeshGroupSettings& mesh_group_settings, const SlicingParameters &slicing_params)
: angle(mesh_group_settings.support_tree_angle),
angle_slow(mesh_group_settings.support_tree_angle_slow),
support_line_width(mesh_group_settings.support_line_width),
layer_height(mesh_group_settings.layer_height),
branch_radius(mesh_group_settings.support_tree_branch_diameter / 2),
min_radius(mesh_group_settings.support_tree_tip_diameter / 2), // The actual radius is 50 microns larger as the resulting branches will be increased by 50 microns to avoid rounding errors effectively increasing the xydistance
maximum_move_distance((angle < M_PI / 2.) ? (coord_t)(tan(angle) * layer_height) : std::numeric_limits<coord_t>::max()),
maximum_move_distance_slow((angle_slow < M_PI / 2.) ? (coord_t)(tan(angle_slow) * layer_height) : std::numeric_limits<coord_t>::max()),
support_bottom_layers(mesh_group_settings.support_bottom_enable ? (mesh_group_settings.support_bottom_height + layer_height / 2) / layer_height : 0),
tip_layers(std::max((branch_radius - min_radius) / (support_line_width / 3), branch_radius / layer_height)), // Ensure lines always stack nicely even if layer height is large
diameter_angle_scale_factor(sin(mesh_group_settings.support_tree_branch_diameter_angle) * layer_height / branch_radius),
max_to_model_radius_increase(mesh_group_settings.support_tree_max_diameter_increase_by_merges_when_support_to_model / 2),
min_dtt_to_model(round_up_divide(mesh_group_settings.support_tree_min_height_to_model, layer_height)),
increase_radius_until_radius(mesh_group_settings.support_tree_branch_diameter / 2),
increase_radius_until_layer(increase_radius_until_radius <= branch_radius ? tip_layers * (increase_radius_until_radius / branch_radius) : (increase_radius_until_radius - branch_radius) / (branch_radius * diameter_angle_scale_factor)),
support_rests_on_model(! mesh_group_settings.support_material_buildplate_only),
xy_distance(mesh_group_settings.support_xy_distance),
xy_min_distance(std::min(mesh_group_settings.support_xy_distance, mesh_group_settings.support_xy_distance_overhang)),
bp_radius(mesh_group_settings.support_tree_bp_diameter / 2),
diameter_scale_bp_radius(std::min(sin(0.7) * layer_height / branch_radius, 1.0 / (branch_radius / (support_line_width / 2.0)))), // Either 40? or as much as possible so that 2 lines will overlap by at least 50%, whichever is smaller.
z_distance_top_layers(round_up_divide(mesh_group_settings.support_top_distance, layer_height)),
z_distance_bottom_layers(round_up_divide(mesh_group_settings.support_bottom_distance, layer_height)),
performance_interface_skip_layers(round_up_divide(mesh_group_settings.support_interface_skip_height, layer_height)),
// support_infill_angles(mesh_group_settings.support_infill_angles),
support_roof_angles(mesh_group_settings.support_roof_angles),
roof_pattern(mesh_group_settings.support_roof_pattern),
support_pattern(mesh_group_settings.support_pattern),
support_roof_line_width(mesh_group_settings.support_roof_line_width),
support_line_spacing(mesh_group_settings.support_line_spacing),
support_bottom_offset(mesh_group_settings.support_bottom_offset),
support_wall_count(mesh_group_settings.support_wall_count),
resolution(mesh_group_settings.resolution),
support_roof_line_distance(mesh_group_settings.support_roof_line_distance), // in the end the actual infill has to be calculated to subtract interface from support areas according to interface_preference.
settings(mesh_group_settings),
min_feature_size(mesh_group_settings.min_feature_size)
{
layer_start_bp_radius = (bp_radius - branch_radius) / (branch_radius * diameter_scale_bp_radius);
if (TreeSupportSettings::soluble) {
// safeOffsetInc can only work in steps of the size xy_min_distance in the worst case => xy_min_distance has to be a bit larger than 0 in this worst case and should be large enough for performance to not suffer extremely
// When for all meshes the z bottom and top distance is more than one layer though the worst case is xy_min_distance + min_feature_size
// This is not the best solution, but the only one to ensure areas can not lag though walls at high maximum_move_distance.
xy_min_distance = std::max(xy_min_distance, scaled<coord_t>(0.1));
xy_distance = std::max(xy_distance, xy_min_distance);
}
// const std::unordered_map<std::string, InterfacePreference> interface_map = { { "support_area_overwrite_interface_area", InterfacePreference::SupportAreaOverwritesInterface }, { "interface_area_overwrite_support_area", InterfacePreference::InterfaceAreaOverwritesSupport }, { "support_lines_overwrite_interface_area", InterfacePreference::SupportLinesOverwriteInterface }, { "interface_lines_overwrite_support_area", InterfacePreference::InterfaceLinesOverwriteSupport }, { "nothing", InterfacePreference::Nothing } };
// interface_preference = interface_map.at(mesh_group_settings.get<std::string>("support_interface_priority"));
//FIXME this was the default
// interface_preference = InterfacePreference::SupportLinesOverwriteInterface;
//interface_preference = InterfacePreference::SupportAreaOverwritesInterface;
interface_preference = InterfacePreference::InterfaceAreaOverwritesSupport;
if (slicing_params.raft_layers() > 0) {
// Fill in raft_layers with the heights of the layers below the first object layer.
// First layer
double z = slicing_params.first_print_layer_height;
this->raft_layers.emplace_back(z);
// Raft base layers
for (size_t i = 1; i < slicing_params.base_raft_layers; ++ i) {
z += slicing_params.base_raft_layer_height;
this->raft_layers.emplace_back(z);
}
// Raft interface layers
for (size_t i = 0; i + 1 < slicing_params.interface_raft_layers; ++ i) {
z += slicing_params.interface_raft_layer_height;
this->raft_layers.emplace_back(z);
}
// Raft contact layer
z = slicing_params.raft_contact_top_z;
this->raft_layers.emplace_back(z);
if (double dist_to_go = slicing_params.object_print_z_min - z; dist_to_go > EPSILON) {
// Layers between the raft contacts and bottom of the object.
auto nsteps = int(ceil(dist_to_go / slicing_params.max_suport_layer_height));
double step = dist_to_go / nsteps;
for (size_t i = 0; i < nsteps; ++ i) {
z += step;
this->raft_layers.emplace_back(z);
}
}
}
}
enum class LineStatus enum class LineStatus
{ {
INVALID, INVALID,
@ -158,7 +243,7 @@ static std::vector<std::pair<TreeSupportSettings, std::vector<size_t>>> group_me
assert(object_config.support_material_style == smsTree || object_config.support_material_style == smsOrganic); assert(object_config.support_material_style == smsTree || object_config.support_material_style == smsOrganic);
bool found_existing_group = false; bool found_existing_group = false;
TreeSupportSettings next_settings{ TreeSupportMeshGroupSettings{ print_object } }; TreeSupportSettings next_settings{ TreeSupportMeshGroupSettings{ print_object }, print_object.slicing_parameters() };
//FIXME for now only a single object per group is enabled. //FIXME for now only a single object per group is enabled.
#if 0 #if 0
for (size_t idx = 0; idx < grouped_meshes.size(); ++ idx) for (size_t idx = 0; idx < grouped_meshes.size(); ++ idx)
@ -222,9 +307,12 @@ void tree_supports_show_error(std::string_view message, bool critical)
#endif // TREE_SUPPORT_SHOW_ERRORS_WIN32 #endif // TREE_SUPPORT_SHOW_ERRORS_WIN32
} }
[[nodiscard]] static const std::vector<Polygons> generate_overhangs(const PrintObject &print_object, std::function<void()> throw_on_cancel) [[nodiscard]] static const std::vector<Polygons> generate_overhangs(const TreeSupportSettings &settings, const PrintObject &print_object, std::function<void()> throw_on_cancel)
{ {
std::vector<Polygons> out(print_object.layer_count(), Polygons{}); const size_t num_raft_layers = settings.raft_layers.size();
const size_t num_object_layers = print_object.layer_count();
const size_t num_layers = num_object_layers + num_raft_layers;
std::vector<Polygons> out(num_layers, Polygons{});
const PrintConfig &print_config = print_object.print()->config(); const PrintConfig &print_config = print_object.print()->config();
const PrintObjectConfig &config = print_object.config(); const PrintObjectConfig &config = print_object.config();
@ -241,10 +329,10 @@ void tree_supports_show_error(std::string_view message, bool critical)
//FIXME this is a fudge constant! //FIXME this is a fudge constant!
auto enforcer_overhang_offset = scaled<double>(config.support_tree_tip_diameter.value); auto enforcer_overhang_offset = scaled<double>(config.support_tree_tip_diameter.value);
size_t num_overhang_layers = support_auto ? out.size() : std::max(size_t(support_enforce_layers), enforcers_layers.size()); size_t num_overhang_layers = support_auto ? num_object_layers : std::max(size_t(support_enforce_layers), enforcers_layers.size());
tbb::parallel_for(tbb::blocked_range<LayerIndex>(1, num_overhang_layers), tbb::parallel_for(tbb::blocked_range<LayerIndex>(1, num_overhang_layers),
[&print_object, &config, &print_config, &enforcers_layers, &blockers_layers, [&print_object, &config, &print_config, &enforcers_layers, &blockers_layers,
support_auto, support_enforce_layers, support_threshold_auto, tan_threshold, enforcer_overhang_offset, &throw_on_cancel, &out] support_auto, support_enforce_layers, support_threshold_auto, tan_threshold, enforcer_overhang_offset, num_raft_layers, &throw_on_cancel, &out]
(const tbb::blocked_range<LayerIndex> &range) { (const tbb::blocked_range<LayerIndex> &range) {
for (LayerIndex layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { for (LayerIndex layer_id = range.begin(); layer_id < range.end(); ++ layer_id) {
const Layer &current_layer = *print_object.get_layer(layer_id); const Layer &current_layer = *print_object.get_layer(layer_id);
@ -314,11 +402,36 @@ void tree_supports_show_error(std::string_view message, bool critical)
//check_self_intersections(overhangs, "generate_overhangs - enforcers"); //check_self_intersections(overhangs, "generate_overhangs - enforcers");
} }
} }
out[layer_id] = std::move(overhangs); out[layer_id + num_raft_layers] = std::move(overhangs);
throw_on_cancel(); throw_on_cancel();
} }
}); });
#if 0
if (num_raft_layers > 0) {
const Layer &first_layer = *print_object.get_layer(0);
// Final overhangs.
Polygons overhangs =
// Don't apply blockes on raft layer.
//(! blockers_layers.empty() && ! blockers_layers[layer_id].empty() ?
// diff(first_layer.lslices, blockers_layers[layer_id], ApplySafetyOffset::Yes) :
to_polygons(first_layer.lslices);
#if 0
if (! enforcers_layers.empty() && ! enforcers_layers[layer_id].empty()) {
if (Polygons enforced_overhangs = intersection(first_layer.lslices, enforcers_layers[layer_id] /*, ApplySafetyOffset::Yes */);
! enforced_overhangs.empty()) {
//FIXME this is a hack to make enforcers work on steep overhangs.
//FIXME enforcer_overhang_offset is a fudge constant!
enforced_overhangs = offset(union_ex(enforced_overhangs), enforcer_overhang_offset);
overhangs = overhangs.empty() ? std::move(enforced_overhangs) : union_(overhangs, enforced_overhangs);
}
}
#endif
out[num_raft_layers] = std::move(overhangs);
throw_on_cancel();
}
#endif
return out; return out;
} }
@ -333,16 +446,18 @@ void tree_supports_show_error(std::string_view message, bool critical)
// calculate top most layer that is relevant for support // calculate top most layer that is relevant for support
LayerIndex max_layer = 0; LayerIndex max_layer = 0;
for (size_t object_id : object_ids) { for (size_t object_id : object_ids) {
const PrintObject &print_object = *print.get_object(object_id); const PrintObject &print_object = *print.get_object(object_id);
int max_support_layer_id = 0; const int num_raft_layers = int(config.raft_layers.size());
for (int layer_id = 1; layer_id < int(print_object.layer_count()); ++ layer_id) const int num_layers = int(print_object.layer_count()) + num_raft_layers;
int max_support_layer_id = 0;
for (int layer_id = std::max<int>(num_raft_layers, 1); layer_id < num_layers; ++ layer_id)
if (! overhangs[layer_id].empty()) if (! overhangs[layer_id].empty())
max_support_layer_id = layer_id; max_support_layer_id = layer_id;
max_layer = std::max(max_support_layer_id - int(config.z_distance_top_layers), 0); max_layer = std::max(max_support_layer_id - int(config.z_distance_top_layers), 0);
} }
if (max_layer > 0) if (max_layer > 0)
// The actual precalculation happens in TreeModelVolumes. // The actual precalculation happens in TreeModelVolumes.
volumes.precalculate(max_layer, throw_on_cancel); volumes.precalculate(*print.get_object(object_ids.front()), max_layer, throw_on_cancel);
return max_layer; return max_layer;
} }
@ -815,29 +930,38 @@ static std::optional<std::pair<Point, size_t>> polyline_sample_next_point_at_dis
return union_(ret); return union_(ret);
} }
static double layer_z(const SlicingParameters &slicing_params, const size_t layer_idx) static double layer_z(const SlicingParameters &slicing_params, const TreeSupportSettings &config, const size_t layer_idx)
{ {
return slicing_params.object_print_z_min + slicing_params.first_object_layer_height + layer_idx * slicing_params.layer_height; return layer_idx >= config.raft_layers.size() ?
slicing_params.object_print_z_min + slicing_params.first_object_layer_height + (layer_idx - config.raft_layers.size()) * slicing_params.layer_height :
config.raft_layers[layer_idx];
} }
static LayerIndex layer_idx_ceil(const SlicingParameters &slicing_params, const double z) // Lowest collision layer
static LayerIndex layer_idx_ceil(const SlicingParameters &slicing_params, const TreeSupportSettings &config, const double z)
{ {
return LayerIndex(ceil((z - slicing_params.object_print_z_min - slicing_params.first_object_layer_height) / slicing_params.layer_height)); return
LayerIndex(config.raft_layers.size()) +
std::max<LayerIndex>(0, ceil((z - slicing_params.object_print_z_min - slicing_params.first_object_layer_height) / slicing_params.layer_height));
} }
static LayerIndex layer_idx_floor(const SlicingParameters &slicing_params, const double z) // Highest collision layer
static LayerIndex layer_idx_floor(const SlicingParameters &slicing_params, const TreeSupportSettings &config, const double z)
{ {
return LayerIndex(floor((z - slicing_params.object_print_z_min - slicing_params.first_object_layer_height) / slicing_params.layer_height)); return
LayerIndex(config.raft_layers.size()) +
std::max<LayerIndex>(0, floor((z - slicing_params.object_print_z_min - slicing_params.first_object_layer_height) / slicing_params.layer_height));
} }
static inline SupportGeneratorLayer& layer_initialize( static inline SupportGeneratorLayer& layer_initialize(
SupportGeneratorLayer &layer_new, SupportGeneratorLayer &layer_new,
const SupporLayerType layer_type, const SupporLayerType layer_type,
const SlicingParameters &slicing_params, const SlicingParameters &slicing_params,
const TreeSupportSettings &config,
const size_t layer_idx) const size_t layer_idx)
{ {
layer_new.layer_type = layer_type; layer_new.layer_type = layer_type;
layer_new.print_z = layer_z(slicing_params, layer_idx); layer_new.print_z = layer_z(slicing_params, config, layer_idx);
layer_new.height = layer_idx == 0 ? slicing_params.first_object_layer_height : slicing_params.layer_height; layer_new.bottom_z = layer_idx > 0 ? layer_z(slicing_params, config, layer_idx - 1) : 0;
layer_new.bottom_z = layer_idx == 0 ? slicing_params.object_print_z_min : layer_new.print_z - layer_new.height; layer_new.height = layer_new.print_z - layer_new.bottom_z;
return layer_new; return layer_new;
} }
@ -846,11 +970,12 @@ inline SupportGeneratorLayer& layer_allocate(
std::deque<SupportGeneratorLayer> &layer_storage, std::deque<SupportGeneratorLayer> &layer_storage,
SupporLayerType layer_type, SupporLayerType layer_type,
const SlicingParameters &slicing_params, const SlicingParameters &slicing_params,
const TreeSupportSettings &config,
size_t layer_idx) size_t layer_idx)
{ {
//FIXME take raft into account. //FIXME take raft into account.
layer_storage.push_back(SupportGeneratorLayer()); layer_storage.push_back(SupportGeneratorLayer());
return layer_initialize(layer_storage.back(), layer_type, slicing_params, layer_idx); return layer_initialize(layer_storage.back(), layer_type, slicing_params, config, layer_idx);
} }
inline SupportGeneratorLayer& layer_allocate( inline SupportGeneratorLayer& layer_allocate(
@ -858,11 +983,12 @@ inline SupportGeneratorLayer& layer_allocate(
tbb::spin_mutex& layer_storage_mutex, tbb::spin_mutex& layer_storage_mutex,
SupporLayerType layer_type, SupporLayerType layer_type,
const SlicingParameters &slicing_params, const SlicingParameters &slicing_params,
const TreeSupportSettings &config,
size_t layer_idx) size_t layer_idx)
{ {
tbb::spin_mutex::scoped_lock lock(layer_storage_mutex); tbb::spin_mutex::scoped_lock lock(layer_storage_mutex);
layer_storage.push_back(SupportGeneratorLayer()); layer_storage.push_back(SupportGeneratorLayer());
return layer_initialize(layer_storage.back(), layer_type, slicing_params, layer_idx); return layer_initialize(layer_storage.back(), layer_type, slicing_params, config, layer_idx);
} }
using SupportElements = std::deque<SupportElement>; using SupportElements = std::deque<SupportElement>;
@ -890,7 +1016,7 @@ static void generate_initial_areas(
static constexpr const auto base_radius = scaled<int>(0.01); static constexpr const auto base_radius = scaled<int>(0.01);
const Polygon base_circle = make_circle(base_radius, SUPPORT_TREE_CIRCLE_RESOLUTION); const Polygon base_circle = make_circle(base_radius, SUPPORT_TREE_CIRCLE_RESOLUTION);
TreeSupportMeshGroupSettings mesh_group_settings(print_object); TreeSupportMeshGroupSettings mesh_group_settings(print_object);
TreeSupportSettings mesh_config{ mesh_group_settings }; TreeSupportSettings mesh_config{ mesh_group_settings, print_object.slicing_parameters() };
SupportParameters support_params(print_object); SupportParameters support_params(print_object);
support_params.with_sheath = true; support_params.with_sheath = true;
support_params.support_density = 0; support_params.support_density = 0;
@ -935,12 +1061,31 @@ static void generate_initial_areas(
std::max<coord_t>(round_up_divide(mesh_config.xy_distance, max_overhang_speed / 2), 2 * mesh_config.z_distance_top_layers) : std::max<coord_t>(round_up_divide(mesh_config.xy_distance, max_overhang_speed / 2), 2 * mesh_config.z_distance_top_layers) :
0; 0;
//FIXME const size_t num_raft_layers = config.raft_layers.size();
size_t num_support_layers = print_object.layer_count(); const size_t num_support_layers = size_t(std::max(0, int(print_object.layer_count()) + int(num_raft_layers) - int(z_distance_delta)));
std::vector<std::unordered_set<Point, PointHash>> already_inserted(num_support_layers - z_distance_delta); const size_t first_support_layer = std::max(int(num_raft_layers) - int(z_distance_delta), 1);
size_t first_tree_layer = 0;
size_t raft_contact_layer_idx = std::numeric_limits<size_t>::max();
if (num_raft_layers > 0 && print_object.layer_count() > 0) {
// Produce raft contact layer outside of the tree support loop, so that no trees will be generated for the raft contact layer.
// Raft layers supporting raft contact interface will be produced by the classic raft generator.
// Find the raft contact layer.
raft_contact_layer_idx = config.raft_layers.size() - 1;
while (raft_contact_layer_idx > 0 && config.raft_layers[raft_contact_layer_idx] > print_object.slicing_parameters().raft_contact_top_z + EPSILON)
-- raft_contact_layer_idx;
// Create the raft contact layer.
SupportGeneratorLayer &raft_contact_layer = layer_allocate(layer_storage, SupporLayerType::TopContact, print_object.slicing_parameters(), config, raft_contact_layer_idx);
top_contacts[raft_contact_layer_idx] = &raft_contact_layer;
const ExPolygons &lslices = print_object.get_layer(0)->lslices;
double expansion = print_object.config().raft_expansion.value;
raft_contact_layer.polygons = expansion > 0 ? expand(lslices, scaled<float>(expansion)) : to_polygons(lslices);
first_tree_layer = print_object.slicing_parameters().raft_layers() - 1;
}
std::mutex mutex_layer_storage, mutex_movebounds; std::mutex mutex_layer_storage, mutex_movebounds;
tbb::parallel_for(tbb::blocked_range<size_t>(1, num_support_layers - z_distance_delta), std::vector<std::unordered_set<Point, PointHash>> already_inserted(num_support_layers);
tbb::parallel_for(tbb::blocked_range<size_t>(first_support_layer, num_support_layers),
[&print_object, &volumes, &config, &overhangs, &mesh_config, &mesh_group_settings, &support_params, [&print_object, &volumes, &config, &overhangs, &mesh_config, &mesh_group_settings, &support_params,
z_distance_delta, min_xy_dist, force_tip_to_roof, roof_enabled, support_roof_layers, extra_outset, circle_length_to_half_linewidth_change, connect_length, max_overhang_insert_lag, z_distance_delta, min_xy_dist, force_tip_to_roof, roof_enabled, support_roof_layers, extra_outset, circle_length_to_half_linewidth_change, connect_length, max_overhang_insert_lag,
&base_circle, &mutex_layer_storage, &mutex_movebounds, &top_contacts, &layer_storage, &already_inserted, &base_circle, &mutex_layer_storage, &mutex_movebounds, &top_contacts, &layer_storage, &already_inserted,
@ -1060,7 +1205,7 @@ static void generate_initial_areas(
std::lock_guard<std::mutex> lock(mutex_layer_storage); std::lock_guard<std::mutex> lock(mutex_layer_storage);
SupportGeneratorLayer *&l = top_contacts[insert_layer_idx - dtt_roof_tip]; SupportGeneratorLayer *&l = top_contacts[insert_layer_idx - dtt_roof_tip];
if (l == nullptr) if (l == nullptr)
l = &layer_allocate(layer_storage, SupporLayerType::TopContact, print_object.slicing_parameters(), insert_layer_idx - dtt_roof_tip); l = &layer_allocate(layer_storage, SupporLayerType::TopContact, print_object.slicing_parameters(), config, insert_layer_idx - dtt_roof_tip);
append(l->polygons, std::move(added_roofs)); append(l->polygons, std::move(added_roofs));
} }
} }
@ -1242,7 +1387,7 @@ static void generate_initial_areas(
if (! added_roofs[idx].empty()) { if (! added_roofs[idx].empty()) {
SupportGeneratorLayer *&l = top_contacts[layer_idx - idx]; SupportGeneratorLayer *&l = top_contacts[layer_idx - idx];
if (l == nullptr) if (l == nullptr)
l = &layer_allocate(layer_storage, SupporLayerType::TopContact, print_object.slicing_parameters(), layer_idx - idx); l = &layer_allocate(layer_storage, SupporLayerType::TopContact, print_object.slicing_parameters(), config, layer_idx - idx);
// will be unioned in finalize_interface_and_support_areas() // will be unioned in finalize_interface_and_support_areas()
append(l->polygons, std::move(added_roofs[idx])); append(l->polygons, std::move(added_roofs[idx]));
} }
@ -1280,7 +1425,7 @@ static void generate_initial_areas(
std::lock_guard<std::mutex> lock(mutex_layer_storage); std::lock_guard<std::mutex> lock(mutex_layer_storage);
SupportGeneratorLayer*& l = top_contacts[0]; SupportGeneratorLayer*& l = top_contacts[0];
if (l == nullptr) if (l == nullptr)
l = &layer_allocate(layer_storage, SupporLayerType::TopContact, print_object.slicing_parameters(), 0); l = &layer_allocate(layer_storage, SupporLayerType::TopContact, print_object.slicing_parameters(), config, 0);
append(l->polygons, std::move(overhang_outset)); append(l->polygons, std::move(overhang_outset));
} else // normal trees have to be generated } else // normal trees have to be generated
addLinesAsInfluenceAreas(overhang_lines, force_tip_to_roof ? support_roof_layers - dtt_roof : 0, layer_idx - dtt_roof, dtt_roof > 0, roof_enabled ? support_roof_layers - dtt_roof : 0); addLinesAsInfluenceAreas(overhang_lines, force_tip_to_roof ? support_roof_layers - dtt_roof : 0, layer_idx - dtt_roof, dtt_roof > 0, roof_enabled ? support_roof_layers - dtt_roof : 0);
@ -1288,6 +1433,49 @@ static void generate_initial_areas(
} }
} }
}); });
// Remove tree tips that start below the raft contact,
// remove interface layers below the raft contact.
for (size_t i = 0; i < first_tree_layer; ++i) {
top_contacts[i] = nullptr;
move_bounds[i].clear();
}
if (raft_contact_layer_idx != std::numeric_limits<size_t>::max() && print_object.config().raft_expansion.value > 0) {
// If any tips at first_tree_layer now are completely inside the expanded raft layer, remove them as well before they are propagated to the ground.
Polygons &raft_polygons = top_contacts[raft_contact_layer_idx]->polygons;
EdgeGrid::Grid grid(get_extents(raft_polygons).inflated(SCALED_EPSILON));
grid.create(raft_polygons, Polylines{}, coord_t(scale_(10.)));
SupportElements &first_layer_move_bounds = move_bounds[first_tree_layer];
double threshold = scaled<double>(print_object.config().raft_expansion.value) * 2.;
first_layer_move_bounds.erase(std::remove_if(first_layer_move_bounds.begin(), first_layer_move_bounds.end(),
[&grid, threshold](const SupportElement &el) {
coordf_t dist;
if (grid.signed_distance_edges(el.state.result_on_layer, threshold, dist)) {
assert(std::abs(dist) < threshold + SCALED_EPSILON);
// Support point is inside the expanded raft, remove it.
return dist < - 0.;
}
return false;
}), first_layer_move_bounds.end());
#if 0
// Remove the remaining tips from the raft: Closing operation on tip circles.
if (! first_layer_move_bounds.empty()) {
const double eps = 0.1;
// All tips supporting this layer are expected to have the same radius.
double radius = config.getRadius(first_layer_move_bounds.front().state);
// Connect the tips with the following closing radius.
double closing_distance = radius;
Polygon circle = make_circle(radius + closing_distance, eps);
Polygons circles;
circles.reserve(first_layer_move_bounds.size());
for (const SupportElement &el : first_layer_move_bounds) {
circles.emplace_back(circle);
circles.back().translate(el.state.result_on_layer);
}
raft_polygons = diff(raft_polygons, offset(union_(circles), - closing_distance));
}
#endif
}
} }
static unsigned int move_inside(const Polygons &polygons, Point &from, int distance = 0, int64_t maxDist2 = std::numeric_limits<int64_t>::max()) static unsigned int move_inside(const Polygons &polygons, Point &from, int distance = 0, int64_t maxDist2 = std::numeric_limits<int64_t>::max())
@ -3047,7 +3235,7 @@ static void finalize_interface_and_support_areas(
} }
if (! floor_layer.empty()) { if (! floor_layer.empty()) {
if (support_bottom == nullptr) if (support_bottom == nullptr)
support_bottom = &layer_allocate(layer_storage, layer_storage_mutex, SupporLayerType::BottomContact, print_object.slicing_parameters(), layer_idx); support_bottom = &layer_allocate(layer_storage, layer_storage_mutex, SupporLayerType::BottomContact, print_object.slicing_parameters(), config, layer_idx);
support_bottom->polygons = union_(floor_layer, support_bottom->polygons); support_bottom->polygons = union_(floor_layer, support_bottom->polygons);
base_layer_polygons = diff_clipped(base_layer_polygons, offset(support_bottom->polygons, scaled<float>(0.01), jtMiter, 1.2)); // Subtract the support floor from the normal support. base_layer_polygons = diff_clipped(base_layer_polygons, offset(support_bottom->polygons, scaled<float>(0.01), jtMiter, 1.2)); // Subtract the support floor from the normal support.
} }
@ -3055,11 +3243,11 @@ static void finalize_interface_and_support_areas(
if (! support_roof_polygons.empty()) { if (! support_roof_polygons.empty()) {
if (support_roof == nullptr) if (support_roof == nullptr)
support_roof = top_contacts[layer_idx] = &layer_allocate(layer_storage, layer_storage_mutex, SupporLayerType::TopContact, print_object.slicing_parameters(), layer_idx); support_roof = top_contacts[layer_idx] = &layer_allocate(layer_storage, layer_storage_mutex, SupporLayerType::TopContact, print_object.slicing_parameters(), config, layer_idx);
support_roof->polygons = union_(support_roof_polygons); support_roof->polygons = union_(support_roof_polygons);
} }
if (! base_layer_polygons.empty()) { if (! base_layer_polygons.empty()) {
SupportGeneratorLayer *base_layer = intermediate_layers[layer_idx] = &layer_allocate(layer_storage, layer_storage_mutex, SupporLayerType::Base, print_object.slicing_parameters(), layer_idx); SupportGeneratorLayer *base_layer = intermediate_layers[layer_idx] = &layer_allocate(layer_storage, layer_storage_mutex, SupporLayerType::Base, print_object.slicing_parameters(), config, layer_idx);
base_layer->polygons = union_(base_layer_polygons); base_layer->polygons = union_(base_layer_polygons);
} }
@ -3391,8 +3579,8 @@ static void extrude_branch(
const SupportElement &prev = *path[ipath - 1]; const SupportElement &prev = *path[ipath - 1];
const SupportElement &current = *path[ipath]; const SupportElement &current = *path[ipath];
assert(prev.state.layer_idx + 1 == current.state.layer_idx); assert(prev.state.layer_idx + 1 == current.state.layer_idx);
p1 = to_3d(unscaled<double>(prev .state.result_on_layer), layer_z(slicing_params, prev .state.layer_idx)); p1 = to_3d(unscaled<double>(prev .state.result_on_layer), layer_z(slicing_params, config, prev .state.layer_idx));
p2 = to_3d(unscaled<double>(current.state.result_on_layer), layer_z(slicing_params, current.state.layer_idx)); p2 = to_3d(unscaled<double>(current.state.result_on_layer), layer_z(slicing_params, config, current.state.layer_idx));
v1 = (p2 - p1).normalized(); v1 = (p2 - p1).normalized();
if (ipath == 1) { if (ipath == 1) {
nprev = v1; nprev = v1;
@ -3439,7 +3627,7 @@ static void extrude_branch(
} else { } else {
const SupportElement &next = *path[ipath + 1]; const SupportElement &next = *path[ipath + 1];
assert(current.state.layer_idx + 1 == next.state.layer_idx); assert(current.state.layer_idx + 1 == next.state.layer_idx);
p3 = to_3d(unscaled<double>(next.state.result_on_layer), layer_z(slicing_params, next.state.layer_idx)); p3 = to_3d(unscaled<double>(next.state.result_on_layer), layer_z(slicing_params, config, next.state.layer_idx));
v2 = (p3 - p2).normalized(); v2 = (p3 - p2).normalized();
ncurrent = (v1 + v2).normalized(); ncurrent = (v1 + v2).normalized();
float radius = unscaled<float>(config.getRadius(current.state)); float radius = unscaled<float>(config.getRadius(current.state));
@ -3549,7 +3737,7 @@ static void organic_smooth_branches_avoid_collisions(
element.parents.empty() || (link_down == -1 && element.state.layer_idx > 0), element.parents.empty() || (link_down == -1 && element.state.layer_idx > 0),
unscaled<float>(config.getRadius(element.state)), unscaled<float>(config.getRadius(element.state)),
// 3D position // 3D position
to_3d(unscaled<float>(element.state.result_on_layer), float(layer_z(slicing_params, element.state.layer_idx))) to_3d(unscaled<float>(element.state.result_on_layer), float(layer_z(slicing_params, config, element.state.layer_idx)))
}); });
// Update min_z coordinate to min_z of the tree below. // Update min_z coordinate to min_z of the tree below.
CollisionSphere &collision_sphere = collision_spheres.back(); CollisionSphere &collision_sphere = collision_spheres.back();
@ -3580,8 +3768,9 @@ static void organic_smooth_branches_avoid_collisions(
//FIXME limit the collision span by the tree slope. //FIXME limit the collision span by the tree slope.
collision_sphere.min_z = std::max(collision_sphere.min_z, collision_sphere.position.z() - collision_sphere.radius); collision_sphere.min_z = std::max(collision_sphere.min_z, collision_sphere.position.z() - collision_sphere.radius);
collision_sphere.max_z = std::min(collision_sphere.max_z, collision_sphere.position.z() + collision_sphere.radius); collision_sphere.max_z = std::min(collision_sphere.max_z, collision_sphere.position.z() + collision_sphere.radius);
collision_sphere.layer_begin = std::min(collision_sphere.element.state.layer_idx, layer_idx_ceil(slicing_params, collision_sphere.min_z)); collision_sphere.layer_begin = std::min(collision_sphere.element.state.layer_idx, layer_idx_ceil(slicing_params, config, collision_sphere.min_z));
collision_sphere.layer_end = std::max(collision_sphere.element.state.layer_idx, layer_idx_floor(slicing_params, collision_sphere.max_z)) + 1; assert(collision_sphere.layer_begin < layer_collision_cache.size());
collision_sphere.layer_end = std::min(LayerIndex(layer_collision_cache.size()), std::max(collision_sphere.element.state.layer_idx, layer_idx_floor(slicing_params, config, collision_sphere.max_z)) + 1);
} }
throw_on_cancel(); throw_on_cancel();
@ -3596,7 +3785,7 @@ static void organic_smooth_branches_avoid_collisions(
collision_sphere.prev_position = collision_sphere.position; collision_sphere.prev_position = collision_sphere.position;
std::atomic<size_t> num_moved{ 0 }; std::atomic<size_t> num_moved{ 0 };
tbb::parallel_for(tbb::blocked_range<size_t>(0, collision_spheres.size()), tbb::parallel_for(tbb::blocked_range<size_t>(0, collision_spheres.size()),
[&collision_spheres, &layer_collision_cache, &slicing_params, &move_bounds, &linear_data_layers, &num_moved, &throw_on_cancel](const tbb::blocked_range<size_t> range) { [&collision_spheres, &layer_collision_cache, &slicing_params, &config, &move_bounds, &linear_data_layers, &num_moved, &throw_on_cancel](const tbb::blocked_range<size_t> range) {
for (size_t collision_sphere_id = range.begin(); collision_sphere_id < range.end(); ++ collision_sphere_id) for (size_t collision_sphere_id = range.begin(); collision_sphere_id < range.end(); ++ collision_sphere_id)
if (CollisionSphere &collision_sphere = collision_spheres[collision_sphere_id]; ! collision_sphere.locked) { if (CollisionSphere &collision_sphere = collision_spheres[collision_sphere_id]; ! collision_sphere.locked) {
// Calculate collision of multiple 2D layers against a collision sphere. // Calculate collision of multiple 2D layers against a collision sphere.
@ -3613,7 +3802,7 @@ static void organic_smooth_branches_avoid_collisions(
double collision_depth = sqrt(r2) - dist; double collision_depth = sqrt(r2) - dist;
if (collision_depth > collision_sphere.last_collision_depth) { if (collision_depth > collision_sphere.last_collision_depth) {
collision_sphere.last_collision_depth = collision_depth; collision_sphere.last_collision_depth = collision_depth;
collision_sphere.last_collision = to_3d(hit_point_out.cast<float>(), float(layer_z(slicing_params, layer_id))); collision_sphere.last_collision = to_3d(hit_point_out.cast<float>(), float(layer_z(slicing_params, config, layer_id)));
} }
} }
} }
@ -3693,7 +3882,7 @@ static void organic_smooth_branches_avoid_collisions(
std::vector<openvdb::Vec3R> pts, prev, projections; std::vector<openvdb::Vec3R> pts, prev, projections;
std::vector<float> distances; std::vector<float> distances;
for (const std::pair<SupportElement*, int>& element : elements_with_link_down) { for (const std::pair<SupportElement*, int>& element : elements_with_link_down) {
Vec3d pt = to_3d(unscaled<double>(element.first->state.result_on_layer), layer_z(print_object.slicing_parameters(), element.first->state.layer_idx)) * scale; Vec3d pt = to_3d(unscaled<double>(element.first->state.result_on_layer), layer_z(print_object.slicing_parameters(), config, element.first->state.layer_idx)) * scale;
pts.push_back({ pt.x(), pt.y(), pt.z() }); pts.push_back({ pt.x(), pt.y(), pt.z() });
} }
@ -3918,9 +4107,9 @@ static void slice_branches(
const SlicingParameters &slicing_params = print_object.slicing_parameters(); const SlicingParameters &slicing_params = print_object.slicing_parameters();
std::vector<float> slice_z; std::vector<float> slice_z;
for (size_t layer_idx = 0; layer_idx < move_bounds.size(); ++ layer_idx) { for (size_t layer_idx = 0; layer_idx < move_bounds.size(); ++ layer_idx) {
double print_z = slicing_params.object_print_z_min + slicing_params.first_object_layer_height + layer_idx * slicing_params.layer_height; const double print_z = layer_z(print_object.slicing_parameters(), config, layer_idx);
double layer_height = layer_idx == 0 ? slicing_params.first_object_layer_height : slicing_params.layer_height; const double bottom_z = layer_idx > 0 ? layer_z(print_object.slicing_parameters(), config, layer_idx - 1) : 0.;
slice_z.emplace_back(float(print_z - layer_height * 0.5)); slice_z.emplace_back(float(0.5 * (bottom_z + print_z)));
} }
// Remove the trailing slices. // Remove the trailing slices.
while (! slice_z.empty()) while (! slice_z.empty())
@ -4009,7 +4198,7 @@ static void generate_support_areas(Print &print, const BuildVolume &build_volume
//FIXME generating overhangs just for the furst mesh of the group. //FIXME generating overhangs just for the furst mesh of the group.
assert(processing.second.size() == 1); assert(processing.second.size() == 1);
std::vector<Polygons> overhangs = generate_overhangs(*print.get_object(processing.second.front()), throw_on_cancel); std::vector<Polygons> overhangs = generate_overhangs(config, *print.get_object(processing.second.front()), throw_on_cancel);
// ### Precalculate avoidances, collision etc. // ### Precalculate avoidances, collision etc.
size_t num_support_layers = precalculate(print, overhangs, processing.first, processing.second, volumes, throw_on_cancel); size_t num_support_layers = precalculate(print, overhangs, processing.first, processing.second, volumes, throw_on_cancel);
@ -4096,8 +4285,6 @@ static void generate_support_areas(Print &print, const BuildVolume &build_volume
// Produce the support G-code. // Produce the support G-code.
// Used by both classic and tree supports. // Used by both classic and tree supports.
SupportParameters support_params(print_object); SupportParameters support_params(print_object);
support_params.with_sheath = true;
support_params.support_density = 0;
SupportGeneratorLayersPtr interface_layers, base_interface_layers; SupportGeneratorLayersPtr interface_layers, base_interface_layers;
SupportGeneratorLayersPtr raft_layers = generate_raft_base(print_object, support_params, print_object.slicing_parameters(), top_contacts, interface_layers, base_interface_layers, intermediate_layers, layer_storage); SupportGeneratorLayersPtr raft_layers = generate_raft_base(print_object, support_params, print_object.slicing_parameters(), top_contacts, interface_layers, base_interface_layers, intermediate_layers, layer_storage);
#if 1 //#ifdef SLIC3R_DEBUG #if 1 //#ifdef SLIC3R_DEBUG

View file

@ -40,6 +40,7 @@ namespace Slic3r
class Print; class Print;
class PrintObject; class PrintObject;
class SupportGeneratorLayer; class SupportGeneratorLayer;
struct SlicingParameters;
using SupportGeneratorLayerStorage = std::deque<SupportGeneratorLayer>; using SupportGeneratorLayerStorage = std::deque<SupportGeneratorLayer>;
using SupportGeneratorLayersPtr = std::vector<SupportGeneratorLayer*>; using SupportGeneratorLayersPtr = std::vector<SupportGeneratorLayer*>;
@ -255,64 +256,7 @@ struct SupportElement
struct TreeSupportSettings struct TreeSupportSettings
{ {
TreeSupportSettings() = default; // required for the definition of the config variable in the TreeSupportGenerator class. TreeSupportSettings() = default; // required for the definition of the config variable in the TreeSupportGenerator class.
explicit TreeSupportSettings(const TreeSupportMeshGroupSettings &mesh_group_settings, const SlicingParameters &slicing_params);
explicit TreeSupportSettings(const TreeSupportMeshGroupSettings& mesh_group_settings)
: angle(mesh_group_settings.support_tree_angle),
angle_slow(mesh_group_settings.support_tree_angle_slow),
support_line_width(mesh_group_settings.support_line_width),
layer_height(mesh_group_settings.layer_height),
branch_radius(mesh_group_settings.support_tree_branch_diameter / 2),
min_radius(mesh_group_settings.support_tree_tip_diameter / 2), // The actual radius is 50 microns larger as the resulting branches will be increased by 50 microns to avoid rounding errors effectively increasing the xydistance
maximum_move_distance((angle < M_PI / 2.) ? (coord_t)(tan(angle) * layer_height) : std::numeric_limits<coord_t>::max()),
maximum_move_distance_slow((angle_slow < M_PI / 2.) ? (coord_t)(tan(angle_slow) * layer_height) : std::numeric_limits<coord_t>::max()),
support_bottom_layers(mesh_group_settings.support_bottom_enable ? (mesh_group_settings.support_bottom_height + layer_height / 2) / layer_height : 0),
tip_layers(std::max((branch_radius - min_radius) / (support_line_width / 3), branch_radius / layer_height)), // Ensure lines always stack nicely even if layer height is large
diameter_angle_scale_factor(sin(mesh_group_settings.support_tree_branch_diameter_angle) * layer_height / branch_radius),
max_to_model_radius_increase(mesh_group_settings.support_tree_max_diameter_increase_by_merges_when_support_to_model / 2),
min_dtt_to_model(round_up_divide(mesh_group_settings.support_tree_min_height_to_model, layer_height)),
increase_radius_until_radius(mesh_group_settings.support_tree_branch_diameter / 2),
increase_radius_until_layer(increase_radius_until_radius <= branch_radius ? tip_layers * (increase_radius_until_radius / branch_radius) : (increase_radius_until_radius - branch_radius) / (branch_radius * diameter_angle_scale_factor)),
support_rests_on_model(! mesh_group_settings.support_material_buildplate_only),
xy_distance(mesh_group_settings.support_xy_distance),
xy_min_distance(std::min(mesh_group_settings.support_xy_distance, mesh_group_settings.support_xy_distance_overhang)),
bp_radius(mesh_group_settings.support_tree_bp_diameter / 2),
diameter_scale_bp_radius(std::min(sin(0.7) * layer_height / branch_radius, 1.0 / (branch_radius / (support_line_width / 2.0)))), // Either 40? or as much as possible so that 2 lines will overlap by at least 50%, whichever is smaller.
z_distance_top_layers(round_up_divide(mesh_group_settings.support_top_distance, layer_height)),
z_distance_bottom_layers(round_up_divide(mesh_group_settings.support_bottom_distance, layer_height)),
performance_interface_skip_layers(round_up_divide(mesh_group_settings.support_interface_skip_height, layer_height)),
// support_infill_angles(mesh_group_settings.support_infill_angles),
support_roof_angles(mesh_group_settings.support_roof_angles),
roof_pattern(mesh_group_settings.support_roof_pattern),
support_pattern(mesh_group_settings.support_pattern),
support_roof_line_width(mesh_group_settings.support_roof_line_width),
support_line_spacing(mesh_group_settings.support_line_spacing),
support_bottom_offset(mesh_group_settings.support_bottom_offset),
support_wall_count(mesh_group_settings.support_wall_count),
resolution(mesh_group_settings.resolution),
support_roof_line_distance(mesh_group_settings.support_roof_line_distance), // in the end the actual infill has to be calculated to subtract interface from support areas according to interface_preference.
settings(mesh_group_settings),
min_feature_size(mesh_group_settings.min_feature_size)
{
layer_start_bp_radius = (bp_radius - branch_radius) / (branch_radius * diameter_scale_bp_radius);
if (TreeSupportSettings::soluble) {
// safeOffsetInc can only work in steps of the size xy_min_distance in the worst case => xy_min_distance has to be a bit larger than 0 in this worst case and should be large enough for performance to not suffer extremely
// When for all meshes the z bottom and top distance is more than one layer though the worst case is xy_min_distance + min_feature_size
// This is not the best solution, but the only one to ensure areas can not lag though walls at high maximum_move_distance.
xy_min_distance = std::max(xy_min_distance, scaled<coord_t>(0.1));
xy_distance = std::max(xy_distance, xy_min_distance);
}
// const std::unordered_map<std::string, InterfacePreference> interface_map = { { "support_area_overwrite_interface_area", InterfacePreference::SupportAreaOverwritesInterface }, { "interface_area_overwrite_support_area", InterfacePreference::InterfaceAreaOverwritesSupport }, { "support_lines_overwrite_interface_area", InterfacePreference::SupportLinesOverwriteInterface }, { "interface_lines_overwrite_support_area", InterfacePreference::InterfaceLinesOverwriteSupport }, { "nothing", InterfacePreference::Nothing } };
// interface_preference = interface_map.at(mesh_group_settings.get<std::string>("support_interface_priority"));
//FIXME this was the default
// interface_preference = InterfacePreference::SupportLinesOverwriteInterface;
//interface_preference = InterfacePreference::SupportAreaOverwritesInterface;
interface_preference = InterfacePreference::InterfaceAreaOverwritesSupport;
}
private: private:
double angle; double angle;
@ -466,6 +410,9 @@ public:
*/ */
coord_t min_feature_size; coord_t min_feature_size;
// Extra raft layers below the object.
std::vector<coordf_t> raft_layers;
public: public:
bool operator==(const TreeSupportSettings& other) const bool operator==(const TreeSupportSettings& other) const
{ {
@ -497,6 +444,7 @@ public:
settings.get<int>("meshfix_maximum_extrusion_area_deviation") == other.settings.get<int>("meshfix_maximum_extrusion_area_deviation")) settings.get<int>("meshfix_maximum_extrusion_area_deviation") == other.settings.get<int>("meshfix_maximum_extrusion_area_deviation"))
) )
#endif #endif
&& raft_layers == other.raft_layers
; ;
} }

View file

@ -140,8 +140,8 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
} }
} else { } else {
if ((config->opt_int("support_material_extruder") != 0 || config->opt_int("support_material_interface_extruder") != 0)) { if ((config->opt_int("support_material_extruder") != 0 || config->opt_int("support_material_interface_extruder") != 0)) {
wxString msg_text = _(L("The Wipe Tower currently supports the non-soluble supports only\n" wxString msg_text = _(L("The Wipe Tower currently supports the non-soluble supports only "
"if they are printed with the current extruder without triggering a tool change.\n" "if they are printed with the current extruder without triggering a tool change. "
"(both support_material_extruder and support_material_interface_extruder need to be set to 0).")); "(both support_material_extruder and support_material_interface_extruder need to be set to 0)."));
if (is_global_config) if (is_global_config)
msg_text += "\n\n" + _(L("Shall I adjust those settings in order to enable the Wipe Tower?")); msg_text += "\n\n" + _(L("Shall I adjust those settings in order to enable the Wipe Tower?"));

View file

@ -1414,7 +1414,7 @@ PageDownloader::PageDownloader(ConfigWizard* parent)
append_spacer(VERTICAL_SPACING); append_spacer(VERTICAL_SPACING);
auto* box_allow_downloads = new wxCheckBox(this, wxID_ANY, _L("Allow build-in downloader")); auto* box_allow_downloads = new wxCheckBox(this, wxID_ANY, _L("Allow built-in downloader"));
// TODO: Do we want it like this? The downloader is allowed for very first time the wizard is run. // TODO: Do we want it like this? The downloader is allowed for very first time the wizard is run.
bool box_allow_value = (app_config->has("downloader_url_registered") ? app_config->get_bool("downloader_url_registered") : true); bool box_allow_value = (app_config->has("downloader_url_registered") ? app_config->get_bool("downloader_url_registered") : true);
box_allow_downloads->SetValue(box_allow_value); box_allow_downloads->SetValue(box_allow_value);

View file

@ -2246,7 +2246,7 @@ void GCodeViewer::load_shells(const Print& print)
if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF) { if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF) {
// adds wipe tower's volume // adds wipe tower's volume
const double max_z = print.objects()[0]->model_object()->get_model()->bounding_box().max(2); const double max_z = print.objects()[0]->model_object()->get_model()->max_z();
const PrintConfig& config = print.config(); const PrintConfig& config = print.config();
const size_t extruders_count = config.nozzle_diameter.size(); const size_t extruders_count = config.nozzle_diameter.size();
if (extruders_count > 1 && config.wipe_tower && !config.complete_objects) { if (extruders_count > 1 && config.wipe_tower && !config.complete_objects) {

View file

@ -140,7 +140,7 @@ void GLCanvas3D::LayersEditing::select_object(const Model &model, int object_id)
// Maximum height of an object changes when the object gets rotated or scaled. // Maximum height of an object changes when the object gets rotated or scaled.
// Changing maximum height of an object will invalidate the layer heigth editing profile. // Changing maximum height of an object will invalidate the layer heigth editing profile.
// m_model_object->bounding_box() is cached, therefore it is cheap even if this method is called frequently. // m_model_object->bounding_box() is cached, therefore it is cheap even if this method is called frequently.
const float new_max_z = (model_object_new == nullptr) ? 0.0f : static_cast<float>(model_object_new->bounding_box().max.z()); const float new_max_z = (model_object_new == nullptr) ? 0.0f : static_cast<float>(model_object_new->max_z());
if (m_model_object != model_object_new || this->last_object_id != object_id || m_object_max_z != new_max_z || if (m_model_object != model_object_new || this->last_object_id != object_id || m_object_max_z != new_max_z ||
(model_object_new != nullptr && m_model_object->id() != model_object_new->id())) { (model_object_new != nullptr && m_model_object->id() != model_object_new->id())) {
m_layer_height_profile.clear(); m_layer_height_profile.clear();
@ -1977,7 +1977,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
if (extruders_count > 1 && wt && !co) { if (extruders_count > 1 && wt && !co) {
// Height of a print (Show at least a slab) // Height of a print (Show at least a slab)
const double height = std::max(m_model->bounding_box().max.z(), 10.0); const double height = std::max(m_model->max_z(), 10.0);
const float x = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_x"))->value; const float x = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_x"))->value;
const float y = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_y"))->value; const float y = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_y"))->value;
@ -7126,12 +7126,10 @@ const ModelVolume *get_model_volume(const GLVolume &v, const Model &model)
{ {
const ModelVolume * ret = nullptr; const ModelVolume * ret = nullptr;
if (model.objects.size() < v.object_idx()) { if (v.object_idx() < model.objects.size()) {
if (v.object_idx() < model.objects.size()) { const ModelObject *obj = model.objects[v.object_idx()];
const ModelObject *obj = model.objects[v.object_idx()]; if (v.volume_idx() < obj->volumes.size()) {
if (v.volume_idx() < obj->volumes.size()) { ret = obj->volumes[v.volume_idx()];
ret = obj->volumes[v.volume_idx()];
}
} }
} }

View file

@ -2060,10 +2060,10 @@ bool ObjectList::del_from_cut_object(bool is_cut_connector, bool is_model_part/*
is_model_part ? _L("Delete solid part from object which is a part of cut") : is_model_part ? _L("Delete solid part from object which is a part of cut") :
is_negative_volume ? _L("Delete negative volume from object which is a part of cut") : ""; is_negative_volume ? _L("Delete negative volume from object which is a part of cut") : "";
const wxString msg_end = is_cut_connector ? ("\n" + _L("To save cut correspondence you can delete all connectors from all related objects.")) : ""; const wxString msg_end = is_cut_connector ? ("\n" + _L("To save cut information you can delete all connectors from all related objects.")) : "";
InfoDialog dialog(wxGetApp().plater(), title, InfoDialog dialog(wxGetApp().plater(), title,
_L("This action will break a cut correspondence.\n" _L("This action will break a cut information.\n"
"After that PrusaSlicer can't guarantee model consistency.\n" "After that PrusaSlicer can't guarantee model consistency.\n"
"\n" "\n"
"To manipulate with solid parts or negative volumes you have to invalidate cut infornation first." + msg_end ), "To manipulate with solid parts or negative volumes you have to invalidate cut infornation first." + msg_end ),

View file

@ -757,7 +757,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection)
m_new_rotation = volume->get_instance_rotation() * (180.0 / M_PI); m_new_rotation = volume->get_instance_rotation() * (180.0 / M_PI);
m_new_size = volume->get_instance_scaling_factor().cwiseProduct(wxGetApp().model().objects[volume->object_idx()]->raw_mesh_bounding_box().size()); m_new_size = volume->get_instance_scaling_factor().cwiseProduct(wxGetApp().model().objects[volume->object_idx()]->raw_mesh_bounding_box().size());
#endif // ENABLE_WORLD_COORDINATE #endif // ENABLE_WORLD_COORDINATE
m_new_scale = volume->get_instance_scaling_factor() * 100.0; m_new_scale = m_new_size.cwiseQuotient(selection.get_full_unscaled_instance_local_bounding_box().size()) * 100.0;
} }
m_new_enabled = true; m_new_enabled = true;

View file

@ -200,7 +200,7 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename,
: GLGizmoBase(parent, icon_filename, sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id)
, m_connectors_group_id (GrabberID::Count) , m_connectors_group_id (GrabberID::Count)
, m_connector_type (CutConnectorType::Plug) , m_connector_type (CutConnectorType::Plug)
, m_connector_style (size_t(CutConnectorStyle::Prizm)) , m_connector_style (size_t(CutConnectorStyle::Prism))
, m_connector_shape_id (size_t(CutConnectorShape::Circle)) , m_connector_shape_id (size_t(CutConnectorShape::Circle))
{ {
m_modes = { _u8L("Planar")//, _u8L("Grid") m_modes = { _u8L("Planar")//, _u8L("Grid")
@ -219,7 +219,7 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename,
m_connector_types.push_back(type_label); m_connector_types.push_back(type_label);
} }
m_connector_styles = { _u8L("Prizm"), _u8L("Frustum") m_connector_styles = { _u8L("Prism"), _u8L("Frustum")
// , _u8L("Claw") // , _u8L("Claw")
}; };
@ -245,15 +245,17 @@ std::string GLGizmoCut3D::get_tooltip() const
double koef = m_imperial_units ? ObjectManipulation::mm_to_in : 1.0; double koef = m_imperial_units ? ObjectManipulation::mm_to_in : 1.0;
std::string unit_str = " " + (m_imperial_units ? _u8L("inch") : _u8L("mm")); std::string unit_str = " " + (m_imperial_units ? _u8L("inch") : _u8L("mm"));
const BoundingBoxf3& tbb = m_transformed_bounding_box; const BoundingBoxf3& tbb = m_transformed_bounding_box;
const std::string name = m_keep_as_parts ? _u8L("Part") : _u8L("Object");
if (tbb.max.z() >= 0.0) { if (tbb.max.z() >= 0.0) {
double top = (tbb.min.z() <= 0.0 ? tbb.max.z() : tbb.size().z()) * koef; double top = (tbb.min.z() <= 0.0 ? tbb.max.z() : tbb.size().z()) * koef;
tooltip += format(top, 2) + " " + unit_str + " (" + _u8L("Top part") + ")"; tooltip += format(static_cast<float>(top), 2) + " " + unit_str + " (" + name + " A)";
if (tbb.min.z() <= 0.0) if (tbb.min.z() <= 0.0)
tooltip += "\n"; tooltip += "\n";
} }
if (tbb.min.z() <= 0.0) { if (tbb.min.z() <= 0.0) {
double bottom = (tbb.max.z() <= 0.0 ? tbb.size().z() : (tbb.min.z() * (-1))) * koef; double bottom = (tbb.max.z() <= 0.0 ? tbb.size().z() : (tbb.min.z() * (-1))) * koef;
tooltip += format(bottom, 2) + " " + unit_str + " (" + _u8L("Bottom part") + ")"; tooltip += format(static_cast<float>(bottom), 2) + " " + unit_str + " (" + name + " B)";
} }
return tooltip; return tooltip;
} }
@ -362,11 +364,8 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event)
void GLGizmoCut3D::shift_cut(double delta) void GLGizmoCut3D::shift_cut(double delta)
{ {
Vec3d starting_vec = m_rotation_m * Vec3d::UnitZ();
if (starting_vec.norm() != 0.0)
starting_vec.normalize();
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move cut plane"), UndoRedo::SnapshotType::GizmoAction); Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move cut plane"), UndoRedo::SnapshotType::GizmoAction);
set_center(m_plane_center + starting_vec * delta, true); set_center(m_plane_center + m_cut_normal * delta, true);
m_ar_plane_center = m_plane_center; m_ar_plane_center = m_plane_center;
} }
@ -405,18 +404,15 @@ bool GLGizmoCut3D::is_looking_forward() const
void GLGizmoCut3D::update_clipper() void GLGizmoCut3D::update_clipper()
{ {
// update cut_normal // update cut_normal
Vec3d beg, end = beg = m_bb_center; Vec3d normal = m_rotation_m * Vec3d::UnitZ();
beg[Z] -= m_radius; normal.normalize();
end[Z] += m_radius; m_cut_normal = normal;
rotate_vec3d_around_plane_center(beg);
rotate_vec3d_around_plane_center(end);
// calculate normal for cut plane
Vec3d normal = m_cut_normal = end - beg;
// calculate normal and offset for clipping plane // calculate normal and offset for clipping plane
normal.normalize(); Vec3d beg = m_bb_center;
beg[Z] -= m_radius;
rotate_vec3d_around_plane_center(beg);
m_clp_normal = normal; m_clp_normal = normal;
double offset = normal.dot(m_plane_center); double offset = normal.dot(m_plane_center);
double dist = normal.dot(beg); double dist = normal.dot(beg);
@ -425,17 +421,13 @@ void GLGizmoCut3D::update_clipper()
if (!is_looking_forward()) { if (!is_looking_forward()) {
// recalculate normal and offset for clipping plane, if camera is looking downward to cut plane // recalculate normal and offset for clipping plane, if camera is looking downward to cut plane
end = beg = m_bb_center; normal = m_rotation_m * (-1. * Vec3d::UnitZ());
beg[Z] += m_radius;
end[Z] -= m_radius;
rotate_vec3d_around_plane_center(beg);
rotate_vec3d_around_plane_center(end);
normal = end - beg;
if (normal == Vec3d::Zero())
return;
normal.normalize(); normal.normalize();
beg = m_bb_center;
beg[Z] += m_radius;
rotate_vec3d_around_plane_center(beg);
m_clp_normal = normal; m_clp_normal = normal;
offset = normal.dot(m_plane_center); offset = normal.dot(m_plane_center);
dist = normal.dot(beg); dist = normal.dot(beg);
@ -910,7 +902,7 @@ void GLGizmoCut3D::on_set_state()
update_bb(); update_bb();
m_connectors_editing = !m_selected.empty(); m_connectors_editing = !m_selected.empty();
m_transformed_bounding_box = transformed_bounding_box(m_plane_center); m_transformed_bounding_box = transformed_bounding_box(m_plane_center, m_rotation_m);
// initiate archived values // initiate archived values
m_ar_plane_center = m_plane_center; m_ar_plane_center = m_plane_center;
@ -1011,9 +1003,11 @@ void GLGizmoCut3D::update_raycasters_for_picking_transform()
// recalculate connector position to world position // recalculate connector position to world position
Vec3d pos = connector.pos + instance_offset; Vec3d pos = connector.pos + instance_offset;
if (connector.attribs.type == CutConnectorType::Dowel && if (connector.attribs.type == CutConnectorType::Dowel &&
connector.attribs.style == CutConnectorStyle::Prizm) { connector.attribs.style == CutConnectorStyle::Prism) {
pos -= height * m_clp_normal; if (is_looking_forward())
height *= 2; pos -= static_cast<double>(height - 0.05f) * m_clp_normal;
else
pos += 0.05 * m_clp_normal;
} }
pos[Z] += sla_shift; pos[Z] += sla_shift;
@ -1344,7 +1338,8 @@ void GLGizmoCut3D::update_bb()
on_unregister_raycasters_for_picking(); on_unregister_raycasters_for_picking();
clear_selection(); clear_selection();
if (CommonGizmosDataObjects::SelectionInfo* selection = m_c->selection_info()) if (CommonGizmosDataObjects::SelectionInfo* selection = m_c->selection_info();
selection && selection->model_object())
m_selected.resize(selection->model_object()->cut_connectors.size(), false); m_selected.resize(selection->model_object()->cut_connectors.size(), false);
} }
} }
@ -1559,7 +1554,7 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors)
m_imgui->disabled_begin(m_connector_type == CutConnectorType::Dowel); m_imgui->disabled_begin(m_connector_type == CutConnectorType::Dowel);
if (type_changed && m_connector_type == CutConnectorType::Dowel) { if (type_changed && m_connector_type == CutConnectorType::Dowel) {
m_connector_style = size_t(CutConnectorStyle::Prizm); m_connector_style = size_t(CutConnectorStyle::Prism);
apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.style = CutConnectorStyle(m_connector_style); }); apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.style = CutConnectorStyle(m_connector_style); });
} }
if (render_combo(_u8L("Style"), m_connector_styles, m_connector_style)) if (render_combo(_u8L("Style"), m_connector_styles, m_connector_style))
@ -1611,7 +1606,7 @@ void GLGizmoCut3D::render_build_size()
", Z: " + double_to_string(tbb_sz.z() * koef, 2) + unit_str; ", Z: " + double_to_string(tbb_sz.z() * koef, 2) + unit_str;
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
m_imgui->text(_L("Build size")); m_imgui->text(_L("Build Volume"));
ImGui::SameLine(m_label_width); ImGui::SameLine(m_label_width);
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, size); m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, size);
} }
@ -1860,7 +1855,7 @@ void GLGizmoCut3D::validate_connector_settings()
if (m_connector_type == CutConnectorType::Undef) if (m_connector_type == CutConnectorType::Undef)
m_connector_type = CutConnectorType::Plug; m_connector_type = CutConnectorType::Plug;
if (m_connector_style == size_t(CutConnectorStyle::Undef)) if (m_connector_style == size_t(CutConnectorStyle::Undef))
m_connector_style = size_t(CutConnectorStyle::Prizm); m_connector_style = size_t(CutConnectorStyle::Prism);
if (m_connector_shape_id == size_t(CutConnectorShape::Undef)) if (m_connector_shape_id == size_t(CutConnectorShape::Undef))
m_connector_shape_id = size_t(CutConnectorShape::Circle); m_connector_shape_id = size_t(CutConnectorShape::Circle);
} }
@ -2096,12 +2091,20 @@ void GLGizmoCut3D::render_connectors()
const Camera& camera = wxGetApp().plater()->get_camera(); const Camera& camera = wxGetApp().plater()->get_camera();
if (connector.attribs.type == CutConnectorType::Dowel && if (connector.attribs.type == CutConnectorType::Dowel &&
connector.attribs.style == CutConnectorStyle::Prizm) { connector.attribs.style == CutConnectorStyle::Prism) {
if (is_looking_forward()) if (m_connectors_editing) {
pos -= height * m_clp_normal; if (is_looking_forward())
else pos -= static_cast<double>(height-0.05f) * m_clp_normal;
pos += height * m_clp_normal; else
height *= 2; pos += 0.05 * m_clp_normal;
}
else {
if (is_looking_forward())
pos -= static_cast<double>(height) * m_clp_normal;
else
pos += static_cast<double>(height) * m_clp_normal;
height *= 2;
}
} }
else if (!is_looking_forward()) else if (!is_looking_forward())
pos += 0.05 * m_clp_normal; pos += 0.05 * m_clp_normal;
@ -2136,16 +2139,13 @@ void GLGizmoCut3D::apply_connectors_in_model(ModelObject* mo, bool &create_dowel
connector.rotation_m = m_rotation_m; connector.rotation_m = m_rotation_m;
if (connector.attribs.type == CutConnectorType::Dowel) { if (connector.attribs.type == CutConnectorType::Dowel) {
if (connector.attribs.style == CutConnectorStyle::Prizm) if (connector.attribs.style == CutConnectorStyle::Prism)
connector.height *= 2; connector.height *= 2;
create_dowels_as_separate_object = true; create_dowels_as_separate_object = true;
} }
else { else {
// calculate shift of the connector center regarding to the position on the cut plane // calculate shift of the connector center regarding to the position on the cut plane
Vec3d shifted_center = m_plane_center + Vec3d::UnitZ(); connector.pos += m_cut_normal * 0.5 * double(connector.height);
rotate_vec3d_around_plane_center(shifted_center);
Vec3d norm = (shifted_center - m_plane_center).normalized();
connector.pos += norm * 0.5 * double(connector.height);
} }
} }
mo->apply_cut_connectors(_u8L("Connector")); mo->apply_cut_connectors(_u8L("Connector"));
@ -2255,7 +2255,7 @@ void GLGizmoCut3D::reset_connectors()
void GLGizmoCut3D::init_connector_shapes() void GLGizmoCut3D::init_connector_shapes()
{ {
for (const CutConnectorType& type : {CutConnectorType::Dowel, CutConnectorType::Plug}) for (const CutConnectorType& type : {CutConnectorType::Dowel, CutConnectorType::Plug})
for (const CutConnectorStyle& style : {CutConnectorStyle::Frustum, CutConnectorStyle::Prizm}) for (const CutConnectorStyle& style : {CutConnectorStyle::Frustum, CutConnectorStyle::Prism})
for (const CutConnectorShape& shape : {CutConnectorShape::Circle, CutConnectorShape::Hexagon, CutConnectorShape::Square, CutConnectorShape::Triangle}) { for (const CutConnectorShape& shape : {CutConnectorShape::Circle, CutConnectorShape::Hexagon, CutConnectorShape::Square, CutConnectorShape::Triangle}) {
const CutConnectorAttributes attribs = { type, style, shape }; const CutConnectorAttributes attribs = { type, style, shape };
const indexed_triangle_set its = ModelObject::get_connector_mesh(attribs); const indexed_triangle_set its = ModelObject::get_connector_mesh(attribs);

View file

@ -1528,7 +1528,7 @@ void GLGizmoEmboss::draw_window()
if (m_is_unknown_font && m_is_advanced_edit_style) if (m_is_unknown_font && m_is_advanced_edit_style)
ImGui::SetNextTreeNodeOpen(false); ImGui::SetNextTreeNodeOpen(false);
if (ImGui::TreeNode(_u8L("advanced").c_str())) { if (ImGui::TreeNode(_u8L("Advanced").c_str())) {
if (!m_is_advanced_edit_style) { if (!m_is_advanced_edit_style) {
set_minimal_window_size(true); set_minimal_window_size(true);
} else { } else {
@ -1592,8 +1592,8 @@ void GLGizmoEmboss::draw_window()
m_set_window_offset = priv::calc_fine_position(m_parent.get_selection(), get_minimal_window_size(), m_parent.get_canvas_size()); m_set_window_offset = priv::calc_fine_position(m_parent.get_selection(), get_minimal_window_size(), m_parent.get_canvas_size());
} else if (ImGui::IsItemHovered()) { } else if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s", ((m_allow_open_near_volume) ? ImGui::SetTooltip("%s", ((m_allow_open_near_volume) ?
_u8L("Fix settings possition"): "Fix settings position":
_u8L("Allow floating window near text")).c_str()); "Allow floating window near text").c_str());
} }
#endif // ALLOW_FLOAT_WINDOW #endif // ALLOW_FLOAT_WINDOW
} }
@ -1654,9 +1654,9 @@ void GLGizmoEmboss::draw_text_input()
auto &ff = m_style_manager.get_font_file_with_cache(); auto &ff = m_style_manager.get_font_file_with_cache();
float imgui_size = StyleManager::get_imgui_font_size(prop, *ff.font_file, scale); float imgui_size = StyleManager::get_imgui_font_size(prop, *ff.font_file, scale);
if (imgui_size > StyleManager::max_imgui_font_size) if (imgui_size > StyleManager::max_imgui_font_size)
append_warning(_u8L("To tall"), _u8L("Diminished font height inside text input.")); append_warning(_u8L("Too tall"), _u8L("Diminished font height inside text input."));
if (imgui_size < StyleManager::min_imgui_font_size) if (imgui_size < StyleManager::min_imgui_font_size)
append_warning(_u8L("To small"), _u8L("Enlarged font height inside text input.")); append_warning(_u8L("Too small"), _u8L("Enlarged font height inside text input."));
if (!who.empty()) warning = GUI::format(_L("%1% is NOT shown."), who); if (!who.empty()) warning = GUI::format(_L("%1% is NOT shown."), who);
} }
@ -3175,7 +3175,8 @@ void GLGizmoEmboss::draw_advanced()
{ {
const auto &ff = m_style_manager.get_font_file_with_cache(); const auto &ff = m_style_manager.get_font_file_with_cache();
if (!ff.has_value()) { if (!ff.has_value()) {
ImGui::Text("%s", _u8L("Advanced font options could be change only for corect font.\nStart with select correct font.").c_str()); ImGui::Text("%s", _u8L("Advanced font options could be changed only for correct font.\n"
"Start with select correct font.").c_str());
return; return;
} }
@ -3301,7 +3302,7 @@ void GLGizmoEmboss::draw_advanced()
bool use_inch = wxGetApp().app_config->get_bool("use_inches"); bool use_inch = wxGetApp().app_config->get_bool("use_inches");
const std::string undo_move_tooltip = _u8L("Undo translation"); const std::string undo_move_tooltip = _u8L("Undo translation");
const wxString move_tooltip = _L("Distance center of text from model surface"); const wxString move_tooltip = _L("Distance of the center of text from model surface");
bool is_moved = false; bool is_moved = false;
if (use_inch) { if (use_inch) {
std::optional<float> distance_inch; std::optional<float> distance_inch;
@ -3597,7 +3598,7 @@ void GLGizmoEmboss::create_notification_not_valid_font(
std::string text = std::string text =
GUI::format(_L("Can't load exactly same font(\"%1%\"), " GUI::format(_L("Can't load exactly same font(\"%1%\"), "
"Aplication select similar one(\"%2%\"). " "Aplication selected a similar one(\"%2%\"). "
"You have to specify font for enable edit text."), "You have to specify font for enable edit text."),
face_name_3mf, face_name); face_name_3mf, face_name);
auto notification_manager = wxGetApp().plater()->get_notification_manager(); auto notification_manager = wxGetApp().plater()->get_notification_manager();

View file

@ -521,7 +521,7 @@ void GLGizmoFdmSupports::auto_generate()
{ {
std::string err = wxGetApp().plater()->fff_print().validate(); std::string err = wxGetApp().plater()->fff_print().validate();
if (!err.empty()) { if (!err.empty()) {
MessageDialog dlg(GUI::wxGetApp().plater(), _L("Automatic painting requires valid print setup. \n") + from_u8(err), _L("Warning"), wxOK); MessageDialog dlg(GUI::wxGetApp().plater(), _L("Automatic painting requires valid print setup.") + " \n" + from_u8(err), _L("Warning"), wxOK);
dlg.ShowModal(); dlg.ShowModal();
return; return;
} }

View file

@ -238,13 +238,7 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection)
selection.get_bounding_box_in_reference_system(ECoordinatesType::Local) : selection.get_bounding_box_in_current_reference_system(); selection.get_bounding_box_in_reference_system(ECoordinatesType::Local) : selection.get_bounding_box_in_current_reference_system();
m_bounding_box = box; m_bounding_box = box;
m_center = box_trafo.translation(); m_center = box_trafo.translation();
m_orient_matrix = Geometry::translation_transform(m_center); m_orient_matrix = box_trafo;
if (!wxGetApp().obj_manipul()->is_world_coordinates() || m_force_local_coordinate) {
const GLVolume& v = *selection.get_first_volume();
m_orient_matrix = m_orient_matrix * v.get_instance_transformation().get_rotation_matrix();
if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates() || m_force_local_coordinate)
m_orient_matrix = m_orient_matrix * v.get_volume_transformation().get_rotation_matrix();
}
m_radius = Offset + m_bounding_box.radius(); m_radius = Offset + m_bounding_box.radius();
m_snap_coarse_in_radius = m_radius / 3.0f; m_snap_coarse_in_radius = m_radius / 3.0f;

View file

@ -463,7 +463,7 @@ TriangleMesh priv::create_mesh(DataBase &input, Fnc was_canceled, Job::Ctl& ctl)
// only info // only info
ctl.call_on_main_thread([]() { ctl.call_on_main_thread([]() {
create_message(_u8L("It is used default volume for embossed " create_message(_u8L("It is used default volume for embossed "
"text, try to change text or font for fix it.")); "text, try to change text or font to fix it."));
}); });
} }

View file

@ -3520,7 +3520,7 @@ bool Plater::priv::replace_volume_with_stl(int object_idx, int volume_idx, const
ModelObject* old_model_object = model.objects[object_idx]; ModelObject* old_model_object = model.objects[object_idx];
ModelVolume* old_volume = old_model_object->volumes[volume_idx]; ModelVolume* old_volume = old_model_object->volumes[volume_idx];
bool sinking = old_model_object->bounding_box().min.z() < SINKING_Z_THRESHOLD; bool sinking = old_model_object->min_z() < SINKING_Z_THRESHOLD;
ModelObject* new_model_object = new_model.objects.front(); ModelObject* new_model_object = new_model.objects.front();
old_model_object->add_volume(*new_model_object->volumes.front()); old_model_object->add_volume(*new_model_object->volumes.front());
@ -3835,7 +3835,7 @@ void Plater::priv::reload_from_disk()
ModelObject* old_model_object = model.objects[obj_idx]; ModelObject* old_model_object = model.objects[obj_idx];
ModelVolume* old_volume = old_model_object->volumes[vol_idx]; ModelVolume* old_volume = old_model_object->volumes[vol_idx];
bool sinking = old_model_object->bounding_box().min.z() < SINKING_Z_THRESHOLD; bool sinking = old_model_object->min_z() < SINKING_Z_THRESHOLD;
bool has_source = !old_volume->source.input_file.empty() && boost::algorithm::iequals(fs::path(old_volume->source.input_file).filename().string(), fs::path(path).filename().string()); bool has_source = !old_volume->source.input_file.empty() && boost::algorithm::iequals(fs::path(old_volume->source.input_file).filename().string(), fs::path(path).filename().string());
bool has_name = !old_volume->name.empty() && boost::algorithm::iequals(old_volume->name, fs::path(path).filename().string()); bool has_name = !old_volume->name.empty() && boost::algorithm::iequals(old_volume->name, fs::path(path).filename().string());
@ -4816,7 +4816,7 @@ bool Plater::priv::layers_height_allowed() const
return false; return false;
int obj_idx = get_selected_object_idx(); int obj_idx = get_selected_object_idx();
return 0 <= obj_idx && obj_idx < (int)model.objects.size() && model.objects[obj_idx]->bounding_box().max.z() > SINKING_Z_THRESHOLD && return 0 <= obj_idx && obj_idx < (int)model.objects.size() && model.objects[obj_idx]->max_z() > SINKING_Z_THRESHOLD &&
config->opt_bool("variable_layer_height") && view3D->is_layers_editing_allowed(); config->opt_bool("variable_layer_height") && view3D->is_layers_editing_allowed();
} }
@ -4988,7 +4988,16 @@ bool Plater::priv::can_decrease_instances(int obj_idx /*= -1*/) const
if (obj_idx < 0) if (obj_idx < 0)
obj_idx = get_selected_object_idx(); obj_idx = get_selected_object_idx();
return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) &&
if (obj_idx < 0) {
if (const auto obj_ids = get_selection().get_object_idxs(); !obj_ids.empty())
for (const size_t obj_id : obj_ids)
if (can_decrease_instances(obj_id))
return true;
return false;
}
return obj_idx < (int)model.objects.size() &&
(model.objects[obj_idx]->instances.size() > 1) && (model.objects[obj_idx]->instances.size() > 1) &&
!sidebar->obj_list()->has_selected_cut_object(); !sidebar->obj_list()->has_selected_cut_object();
} }
@ -6254,6 +6263,13 @@ void Plater::increase_instances(size_t num, int obj_idx/* = -1*/)
if (obj_idx < 0) if (obj_idx < 0)
obj_idx = p->get_selected_object_idx(); obj_idx = p->get_selected_object_idx();
if (obj_idx < 0) {
if (const auto obj_idxs = get_selection().get_object_idxs(); !obj_idxs.empty())
for (const size_t obj_id : obj_idxs)
increase_instances(1, int(obj_id));
return;
}
ModelObject* model_object = p->model.objects[obj_idx]; ModelObject* model_object = p->model.objects[obj_idx];
ModelInstance* model_instance = model_object->instances.back(); ModelInstance* model_instance = model_object->instances.back();
@ -6289,6 +6305,13 @@ void Plater::decrease_instances(size_t num, int obj_idx/* = -1*/)
if (obj_idx < 0) if (obj_idx < 0)
obj_idx = p->get_selected_object_idx(); obj_idx = p->get_selected_object_idx();
if (obj_idx < 0) {
if (const auto obj_ids = get_selection().get_object_idxs(); !obj_ids.empty())
for (const size_t obj_id : obj_ids)
decrease_instances(1, int(obj_id));
return;
}
ModelObject* model_object = p->model.objects[obj_idx]; ModelObject* model_object = p->model.objects[obj_idx];
if (model_object->instances.size() > num) { if (model_object->instances.size() > num) {
for (size_t i = 0; i < num; ++ i) for (size_t i = 0; i < num; ++ i)
@ -7412,7 +7435,7 @@ void Plater::changed_objects(const std::vector<size_t>& object_idxs)
for (size_t obj_idx : object_idxs) { for (size_t obj_idx : object_idxs) {
if (obj_idx < p->model.objects.size()) { if (obj_idx < p->model.objects.size()) {
if (p->model.objects[obj_idx]->bounding_box().min.z() >= SINKING_Z_THRESHOLD) if (p->model.objects[obj_idx]->min_z() >= SINKING_Z_THRESHOLD)
// re - align to Z = 0 // re - align to Z = 0
p->model.objects[obj_idx]->ensure_on_bed(); p->model.objects[obj_idx]->ensure_on_bed();
} }

View file

@ -598,7 +598,7 @@ void PreferencesDialog::build()
append_bool_option(m_optgroup_other, "downloader_url_registered", append_bool_option(m_optgroup_other, "downloader_url_registered",
L("Allow downloads from Printables.com"), L("Allow downloads from Printables.com"),
L("If enabled, PrusaSlicer will allow to download from Printables.com"), L("If enabled, PrusaSlicer will be allowed to download from Printables.com"),
app_config->get_bool("downloader_url_registered")); app_config->get_bool("downloader_url_registered"));
activate_options_tab(m_optgroup_other); activate_options_tab(m_optgroup_other);
@ -645,7 +645,7 @@ void PreferencesDialog::build()
{ {
append_bool_option(m_optgroup_dark_mode, "sys_menu_enabled", append_bool_option(m_optgroup_dark_mode, "sys_menu_enabled",
L("Use system menu for application"), L("Use system menu for application"),
L("If enabled, application will use the standart Windows system menu,\n" L("If enabled, application will use the standard Windows system menu,\n"
"but on some combination od display scales it can look ugly. If disabled, old UI will be used."), "but on some combination od display scales it can look ugly. If disabled, old UI will be used."),
app_config->get_bool("sys_menu_enabled")); app_config->get_bool("sys_menu_enabled"));
} }

View file

@ -918,7 +918,7 @@ void Selection::translate(const Vec3d& displacement, TransformationType transfor
v.set_instance_offset(inst_trafo.get_offset() + inst_trafo.get_rotation_matrix() * displacement); v.set_instance_offset(inst_trafo.get_offset() + inst_trafo.get_rotation_matrix() * displacement);
} }
else else
transform_instance_relative_world(v, volume_data, transformation_type, Geometry::translation_transform(displacement), m_cache.dragging_center); transform_instance_relative(v, volume_data, transformation_type, Geometry::translation_transform(displacement), m_cache.dragging_center);
} }
else { else {
if (transformation_type.local() && transformation_type.absolute()) { if (transformation_type.local() && transformation_type.absolute()) {
@ -998,24 +998,32 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_
assert(transformation_type.relative() || (transformation_type.absolute() && transformation_type.local())); assert(transformation_type.relative() || (transformation_type.absolute() && transformation_type.local()));
Transform3d rotation_matrix = Geometry::rotation_transform(rotation);
for (unsigned int i : m_list) { for (unsigned int i : m_list) {
Transform3d rotation_matrix = Geometry::rotation_transform(rotation);
GLVolume& v = *(*m_volumes)[i]; GLVolume& v = *(*m_volumes)[i];
const VolumeCache& volume_data = m_cache.volumes_data[i]; const VolumeCache& volume_data = m_cache.volumes_data[i];
const Geometry::Transformation& inst_trafo = volume_data.get_instance_transform(); const Geometry::Transformation& inst_trafo = volume_data.get_instance_transform();
if (m_mode == Instance && !is_wipe_tower()) { if (m_mode == Instance && !is_wipe_tower()) {
assert(is_from_fully_selected_instance(i)); assert(is_from_fully_selected_instance(i));
if (transformation_type.instance()) { if (transformation_type.instance()) {
const Vec3d world_inst_pivot = m_cache.dragging_center - inst_trafo.get_offset(); // ensure that the instance rotates as a rigid body
const Vec3d local_inst_pivot = inst_trafo.get_matrix_no_offset().inverse() * world_inst_pivot; Transform3d inst_rotation_matrix = inst_trafo.get_rotation_matrix();
Matrix3d inst_rotation, inst_scale; if (inst_trafo.is_left_handed()) {
inst_trafo.get_matrix().computeRotationScaling(&inst_rotation, &inst_scale); Geometry::TransformationSVD inst_svd(inst_trafo);
const Transform3d trafo = inst_trafo.get_rotation_matrix() * rotation_matrix; inst_rotation_matrix = inst_svd.u * inst_svd.v.transpose();
v.set_instance_transformation(Geometry::translation_transform(world_inst_pivot) * inst_trafo.get_offset_matrix() * trafo * Transform3d(inst_scale) * Geometry::translation_transform(-local_inst_pivot)); // ensure the rotation has the proper direction
if (!rotation.normalized().cwiseAbs().isApprox(Vec3d::UnitX()))
rotation_matrix = rotation_matrix.inverse();
}
const Transform3d inst_matrix_no_offset = inst_trafo.get_matrix_no_offset();
rotation_matrix = inst_matrix_no_offset.inverse() * inst_rotation_matrix * rotation_matrix * inst_rotation_matrix.inverse() * inst_matrix_no_offset;
// rotate around selection center
const Vec3d inst_pivot = inst_trafo.get_matrix_no_offset().inverse() * (m_cache.dragging_center - inst_trafo.get_offset());
rotation_matrix = Geometry::translation_transform(inst_pivot) * rotation_matrix * Geometry::translation_transform(-inst_pivot);
} }
else transform_instance_relative(v, volume_data, transformation_type, rotation_matrix, m_cache.dragging_center);
transform_instance_relative_world(v, volume_data, transformation_type, rotation_matrix, m_cache.dragging_center);
} }
else { else {
if (!is_single_volume_or_modifier()) { if (!is_single_volume_or_modifier()) {
@ -1024,27 +1032,26 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_
} }
else { else {
if (transformation_type.instance()) { if (transformation_type.instance()) {
// ensure proper sign of rotation for mirrored objects
if (inst_trafo.is_left_handed() && !rotation.normalized().isApprox(Vec3d::UnitX()))
rotation_matrix = rotation_matrix.inverse();
// ensure that the volume rotates as a rigid body // ensure that the volume rotates as a rigid body
const Transform3d inst_scale_matrix = inst_trafo.get_scaling_factor_matrix(); const Transform3d inst_scale_matrix = inst_trafo.get_scaling_factor_matrix();
rotation_matrix = inst_scale_matrix.inverse() * rotation_matrix * inst_scale_matrix; rotation_matrix = inst_scale_matrix.inverse() * rotation_matrix * inst_scale_matrix;
} }
else { else {
if (transformation_type.local()) { if (transformation_type.local()) {
const Geometry::Transformation& vol_trafo = volume_data.get_volume_transform();
const Geometry::Transformation world_trafo = inst_trafo * vol_trafo;
// ensure proper sign of rotation for mirrored objects
if (world_trafo.is_left_handed() && !rotation.normalized().isApprox(Vec3d::UnitX()))
rotation_matrix = rotation_matrix.inverse();
// ensure that the volume rotates as a rigid body // ensure that the volume rotates as a rigid body
if (Geometry::TransformationSVD(world_trafo).anisotropic_scale) { const Geometry::Transformation& vol_trafo = volume_data.get_volume_transform();
const Transform3d inst_scale_matrix = inst_trafo.get_scaling_factor_matrix(); const Transform3d vol_matrix_no_offset = vol_trafo.get_matrix_no_offset();
rotation_matrix = inst_scale_matrix.inverse() * rotation_matrix * inst_scale_matrix; const Transform3d inst_scale_matrix = inst_trafo.get_scaling_factor_matrix();
Transform3d vol_rotation_matrix = vol_trafo.get_rotation_matrix();
if (vol_trafo.is_left_handed()) {
Geometry::TransformationSVD vol_svd(vol_trafo);
vol_rotation_matrix = vol_svd.u * vol_svd.v.transpose();
// ensure the rotation has the proper direction
if (!rotation.normalized().cwiseAbs().isApprox(Vec3d::UnitX()))
rotation_matrix = rotation_matrix.inverse();
} }
rotation_matrix = vol_matrix_no_offset.inverse() * inst_scale_matrix.inverse() * vol_rotation_matrix * rotation_matrix *
vol_rotation_matrix.inverse() * inst_scale_matrix * vol_matrix_no_offset;
} }
} }
transform_volume_relative(v, volume_data, transformation_type, rotation_matrix, m_cache.dragging_center); transform_volume_relative(v, volume_data, transformation_type, rotation_matrix, m_cache.dragging_center);
@ -1056,7 +1063,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_
if (m_mode == Instance) { if (m_mode == Instance) {
int rot_axis_max = 0; int rot_axis_max = 0;
rotation.cwiseAbs().maxCoeff(&rot_axis_max); rotation.cwiseAbs().maxCoeff(&rot_axis_max);
synchronize_unselected_instances((transformation_type.world() && rot_axis_max == 2) ? SyncRotationType::NONE : SyncRotationType::GENERAL); synchronize_unselected_instances((rot_axis_max == 2) ? SyncRotationType::NONE : SyncRotationType::GENERAL);
} }
else if (m_mode == Volume) else if (m_mode == Volume)
synchronize_unselected_volumes(); synchronize_unselected_volumes();
@ -1458,7 +1465,7 @@ void Selection::scale_and_translate(const Vec3d& scale, const Vec3d& translation
v.set_instance_transformation(Geometry::translation_transform(world_inst_pivot) * offset_trafo * Transform3d(inst_rotation) * scale_trafo * Geometry::translation_transform(-local_inst_pivot)); v.set_instance_transformation(Geometry::translation_transform(world_inst_pivot) * offset_trafo * Transform3d(inst_rotation) * scale_trafo * Geometry::translation_transform(-local_inst_pivot));
} }
else else
transform_instance_relative_world(v, volume_data, transformation_type, Geometry::translation_transform(translation) * Geometry::scale_transform(relative_scale), m_cache.dragging_center); transform_instance_relative(v, volume_data, transformation_type, Geometry::translation_transform(translation) * Geometry::scale_transform(relative_scale), m_cache.dragging_center);
} }
else { else {
if (!is_single_volume_or_modifier()) { if (!is_single_volume_or_modifier()) {
@ -3331,16 +3338,21 @@ void Selection::paste_objects_from_clipboard()
} }
#if ENABLE_WORLD_COORDINATE #if ENABLE_WORLD_COORDINATE
void Selection::transform_instance_relative_world(GLVolume& volume, const VolumeCache& volume_data, TransformationType transformation_type, void Selection::transform_instance_relative(GLVolume& volume, const VolumeCache& volume_data, TransformationType transformation_type,
const Transform3d& transform, const Vec3d& world_pivot) const Transform3d& transform, const Vec3d& world_pivot)
{ {
assert(transformation_type.relative()); assert(transformation_type.relative());
assert(transformation_type.world());
const Geometry::Transformation& inst_trafo = volume_data.get_instance_transform(); const Geometry::Transformation& inst_trafo = volume_data.get_instance_transform();
const Vec3d inst_pivot = transformation_type.independent() && !is_from_single_instance() ? inst_trafo.get_offset() : world_pivot; if (transformation_type.world()) {
const Transform3d trafo = Geometry::translation_transform(inst_pivot) * transform * Geometry::translation_transform(-inst_pivot); const Vec3d inst_pivot = transformation_type.independent() && !is_from_single_instance() ? inst_trafo.get_offset() : world_pivot;
volume.set_instance_transformation(trafo * inst_trafo.get_matrix()); const Transform3d trafo = Geometry::translation_transform(inst_pivot) * transform * Geometry::translation_transform(-inst_pivot);
volume.set_instance_transformation(trafo * inst_trafo.get_matrix());
}
else if (transformation_type.instance())
volume.set_instance_transformation(inst_trafo.get_matrix() * transform);
else
assert(false);
} }
void Selection::transform_volume_relative(GLVolume& volume, const VolumeCache& volume_data, TransformationType transformation_type, void Selection::transform_volume_relative(GLVolume& volume, const VolumeCache& volume_data, TransformationType transformation_type,

View file

@ -516,7 +516,7 @@ private:
void paste_objects_from_clipboard(); void paste_objects_from_clipboard();
#if ENABLE_WORLD_COORDINATE #if ENABLE_WORLD_COORDINATE
void transform_instance_relative_world(GLVolume& volume, const VolumeCache& volume_data, TransformationType transformation_type, void transform_instance_relative(GLVolume& volume, const VolumeCache& volume_data, TransformationType transformation_type,
const Transform3d& transform, const Vec3d& world_pivot); const Transform3d& transform, const Vec3d& world_pivot);
void transform_volume_relative(GLVolume& volume, const VolumeCache& volume_data, TransformationType transformation_type, void transform_volume_relative(GLVolume& volume, const VolumeCache& volume_data, TransformationType transformation_type,
const Transform3d& transform, const Vec3d& world_pivot); const Transform3d& transform, const Vec3d& world_pivot);

View file

@ -1593,7 +1593,7 @@ void DiffPresetDialog::create_buttons()
}); });
m_transfer_btn->Bind(wxEVT_ENTER_WINDOW, [this, show_in_bottom_info](wxMouseEvent& e) { m_transfer_btn->Bind(wxEVT_ENTER_WINDOW, [this, show_in_bottom_info](wxMouseEvent& e) {
show_in_bottom_info(_L("Transfer the selected options from left preset to the right.\n" show_in_bottom_info(_L("Transfer the selected options from left preset to the right.\n"
"Note: New modified presets will be selected in setting stabs after close this dialog."), e); }); "Note: New modified presets will be selected in settings tabs after close this dialog."), e); });
// Save // Save
m_save_btn = new ScalableButton(this, wxID_ANY, "save", _L("Save"), wxDefaultSize, wxDefaultPosition, wxBORDER_DEFAULT, 24); m_save_btn = new ScalableButton(this, wxID_ANY, "save", _L("Save"), wxDefaultSize, wxDefaultPosition, wxBORDER_DEFAULT, 24);

View file

@ -1168,7 +1168,7 @@ void PresetUpdater::slic3r_update_notify()
static bool reload_configs_update_gui() static bool reload_configs_update_gui()
{ {
wxString header = _L("Configuration Updates causes a loss of preset modification.\n" wxString header = _L("Configuration Update will cause the preset modification to be lost.\n"
"So, check unsaved changes and save them if necessary."); "So, check unsaved changes and save them if necessary.");
if (!GUI::wxGetApp().check_and_save_current_preset_changes(_L("Updating"), header, false )) if (!GUI::wxGetApp().check_and_save_current_preset_changes(_L("Updating"), header, false ))
return false; return false;