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") {
const Vec2d &p = m_config.option<ConfigOptionPoint>("align_xy")->value;
for (auto &model : m_models) {
BoundingBoxf3 bb = model.bounding_box();
BoundingBoxf3 bb = model.bounding_box_exact();
// this affects volumes:
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") {
std::vector<Model> new_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();
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); }
Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2)
{ 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)
{ 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
BoundingBoxf3 Model::bounding_box() const
BoundingBoxf3 Model::bounding_box_approx() const
{
BoundingBoxf3 bb;
for (ModelObject *o : this->objects)
bb.merge(o->bounding_box());
bb.merge(o->bounding_box_approx());
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 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();
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 y_copy = 1; y_copy <= y; ++y_copy) {
@ -548,13 +564,13 @@ void Model::adjust_min_z()
if (objects.empty())
return;
if (bounding_box().min(2) < 0.0)
if (this->bounding_box_exact().min.z() < 0.0)
{
for (ModelObject* obj : objects)
{
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)
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->origin_translation = rhs.origin_translation;
this->cut_id.copy(rhs.cut_id);
m_bounding_box = rhs.m_bounding_box;
m_bounding_box_valid = rhs.m_bounding_box_valid;
m_bounding_box_approx = rhs.m_bounding_box_approx;
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_valid = rhs.m_raw_bounding_box_valid;
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->printable = std::move(rhs.printable);
this->origin_translation = std::move(rhs.origin_translation);
m_bounding_box = std::move(rhs.m_bounding_box);
m_bounding_box_valid = std::move(rhs.m_bounding_box_valid);
m_bounding_box_approx = std::move(rhs.m_bounding_box_approx);
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_valid = rhs.m_raw_bounding_box_valid;
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.
// 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) {
m_bounding_box_valid = true;
if (! m_bounding_box_approx_valid) {
m_bounding_box_approx_valid = true;
BoundingBoxf3 raw_bbox = this->raw_mesh_bounding_box();
m_bounding_box.reset();
m_bounding_box_approx.reset();
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.
@ -1031,19 +1110,19 @@ void ModelObject::ensure_on_bed(bool allow_negative_z)
if (allow_negative_z) {
if (parts_count() == 1) {
const double min_z = get_min_z();
const double max_z = get_max_z();
const double min_z = this->min_z();
const double max_z = this->max_z();
if (min_z >= SINKING_Z_THRESHOLD || max_z < 0.0)
z_offset = -min_z;
}
else {
const double max_z = get_max_z();
const double max_z = this->max_z();
if (max_z < SINKING_MIN_Z_THRESHOLD)
z_offset = SINKING_MIN_Z_THRESHOLD - max_z;
}
}
else
z_offset = -get_min_z();
z_offset = -this->min_z();
if (z_offset != 0.0)
translate_instances(z_offset * Vec3d::UnitZ());
@ -1070,8 +1149,10 @@ void ModelObject::translate(double x, double y, double z)
v->translate(x, y, z);
}
if (m_bounding_box_valid)
m_bounding_box.translate(x, y, z);
if (m_bounding_box_approx_valid)
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)
@ -1241,7 +1322,7 @@ indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes conn
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));
else if (connector_attributes.type == CutConnectorType::Plug)
connector_mesh = its_make_cone(1.0, 1.0, (2 * PI / sectorCount));
@ -1347,12 +1428,7 @@ void ModelVolume::apply_tolerance()
return;
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
sf[X] += 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);
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)
{
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.
// This transformation is already there
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
ModelVolume* vol = upper->add_volume(*volume);
vol->set_transformation(volume_matrix);
vol->apply_tolerance();
}
if (attributes.has(ModelObjectCutAttribute::KeepLower)) {
ModelVolume* vol = lower->add_volume(*volume);
vol->set_transformation(volume_matrix);
if (volume->cut_info.connector_type == CutConnectorType::Dowel)
if (volume->cut_info.connector_type != CutConnectorType::Dowel) {
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
ModelVolume* vol = upper->add_volume(*volume);
vol->set_transformation(volume_matrix);
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
vol->set_type(ModelVolumeType::MODEL_PART);
}
}
if (volume->cut_info.connector_type == CutConnectorType::Dowel &&
attributes.has(ModelObjectCutAttribute::CreateDowels)) {
ModelObject* dowel{ nullptr };
// Clone the object to duplicate instances, materials etc.
clone_for_cut(&dowel);
else {
if (attributes.has(ModelObjectCutAttribute::CreateDowels)) {
ModelObject* dowel{ nullptr };
// Clone the object to duplicate instances, materials etc.
clone_for_cut(&dowel);
// add one more solid part same as connector if this connector is a dowel
ModelVolume* vol = dowel->add_volume(*volume);
vol->set_type(ModelVolumeType::MODEL_PART);
// add one more solid part same as connector if this connector is a dowel
ModelVolume* vol = dowel->add_volume(*volume);
vol->set_type(ModelVolumeType::MODEL_PART);
// But discard rotation and Z-offset for this volume
vol->set_rotation(Vec3d::Zero());
vol->set_offset(Z, 0.0);
// But discard rotation and Z-offset for this volume
vol->set_rotation(Vec3d::Zero());
vol->set_offset(Z, 0.0);
// 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));
// 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));
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);
}
static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelVolume* src_volume, const Transform3d& cut_matrix, const std::string& suffix = {})
{
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)
void ModelObject::process_volume_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh)
{
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());
mesh.transform(invert_cut_matrix * instance_matrix * volume_matrix, true);
volume->reset_mesh();
// Reset volume transformation except for offset
const Vec3d offset = volume->get_offset();
volume->set_transformation(Geometry::Transformation());
volume->set_offset(offset);
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);
}
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
TriangleMesh 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);
}
process_volume_cut(volume, instance_matrix, cut_matrix, attributes, upper_mesh, lower_mesh);
// 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)
process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, attributes, upper, lower);
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())
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();
}
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 min_z = DBL_MAX;
@ -2249,7 +2323,7 @@ void ModelVolume::scale(const Vec3d& scaling_factors)
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(
size.x() / orig_size.x(),
std::min(
@ -2354,37 +2428,6 @@ void ModelInstance::transform_mesh(TriangleMesh* mesh, bool dont_translate) cons
#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
{
#if ENABLE_WORLD_COORDINATE

View File

@ -168,7 +168,7 @@ private:
friend class cereal::access;
friend class UndoRedo::StackImpl;
// 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) {
assert(this->id().invalid()); assert(this->config.id().invalid());
Internal::StaticSerializationWrapper<ModelConfigObject> config_wrapper(config);
@ -228,7 +228,7 @@ enum class CutConnectorType : int {
};
enum class CutConnectorStyle : int {
Prizm
Prism
, Frustum
, Undef
//,Claw
@ -246,7 +246,7 @@ enum class CutConnectorShape : int {
struct CutConnectorAttributes
{
CutConnectorType type{ CutConnectorType::Plug };
CutConnectorStyle style{ CutConnectorStyle::Prizm };
CutConnectorStyle style{ CutConnectorStyle::Prism };
CutConnectorShape shape{ CutConnectorShape::Circle };
CutConnectorAttributes() {}
@ -343,7 +343,7 @@ public:
// The pairs of <z, layer_height> are packed into a 1D array.
LayerHeightProfile layer_height_profile;
// 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
// saved in mesh coordinates to allow using them for several instances.
@ -397,11 +397,22 @@ public:
void delete_last_instance();
void clear_instances();
// Returns the bounding box of the transformed instances.
// This bounding box is approximate and not snug.
// This bounding box is being cached.
const BoundingBoxf3& bounding_box() const;
void invalidate_bounding_box() { m_bounding_box_valid = false; m_raw_bounding_box_valid = false; m_raw_mesh_bounding_box_valid = false; }
// Returns the bounding box of the transformed instances. This bounding box is approximate and not snug, it is being cached.
const BoundingBoxf3& bounding_box_approx() const;
// Returns an exact bounding box of the transformed instances. The result it is being cached.
const BoundingBoxf3& bounding_box_exact() const;
// 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.
TriangleMesh mesh() const;
@ -459,10 +470,13 @@ public:
void synchronize_model_after_cut();
void apply_cut_attributes(ModelObjectCutAttributes attributes);
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);
void process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix,
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,
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, Vec3d& local_displace);
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.
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_max_z(size_t instance_idx) const;
@ -497,14 +509,13 @@ public:
private:
friend class Model;
// This constructor assigns new ID to this ModelObject and its config.
explicit ModelObject(Model* model) : m_model(model), 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(Model* model) : m_model(model), origin_translation(Vec3d::Zero())
{
assert(this->id().valid());
assert(this->config.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->config.id().invalid());
@ -582,15 +593,18 @@ private:
OBJECTBASE_DERIVED_COPY_MOVE_CLONE(ModelObject)
// 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.
mutable BoundingBoxf3 m_bounding_box;
mutable bool m_bounding_box_valid;
mutable BoundingBoxf3 m_bounding_box_approx;
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 bool m_raw_bounding_box_valid;
mutable bool m_raw_bounding_box_valid { false };
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.
friend class Print;
@ -602,8 +616,7 @@ private:
friend class UndoRedo::StackImpl;
// Used for deserialization -> Don't allocate any IDs for the ModelObject or its config.
ModelObject() :
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) {
ObjectBase(-1), config(-1), layer_height_profile(-1) {
assert(this->id().invalid());
assert(this->config.id().invalid());
assert(this->layer_height_profile.id().invalid());
@ -614,12 +627,17 @@ private:
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,
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);
}
// Called by Print::validate() from the UI thread.
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 {
@ -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())
ModelInstanceEPrintVolumeState print_volume_state;
// Whether or not this instance is printable
bool printable;
bool printable { true };
ModelObject* get_object() const { return this->object; }
@ -1153,9 +1171,7 @@ public:
// To be called on an external mesh
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.
BoundingBoxf3 transform_mesh_bounding_box(const TriangleMesh& mesh, bool dont_translate = false) const;
// Transform an external bounding box.
// Transform an external bounding box, thus the resulting bounding box is no more snug.
BoundingBoxf3 transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate = false) const;
// Transform an external vector.
Vec3d transform_vector(const Vec3d& v, bool dont_translate = false) const;
@ -1198,7 +1214,7 @@ private:
ModelObject* object;
// 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.
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()); }
@ -1313,8 +1329,12 @@ public:
void delete_material(t_model_material_id material_id);
void clear_materials();
bool add_default_instances();
// Returns approximate axis aligned bounding box of this model
BoundingBoxf3 bounding_box() const;
// Returns approximate axis aligned bounding box of this model.
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,
// return total number of printable objects.
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->category = L("Support material");
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. "
"Use Support Roof for very high values or ensure support density is similarly high at the top.");
"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.");
def->sidetext = L("%");
def->min = 5;
def->max_literal = 35;
@ -3654,7 +3655,7 @@ void PrintConfigDef::init_sla_params()
def = this->add_nullable("idle_temperature", coInts);
def->label = L("Idle temperature");
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->min = 0;
def->max = max_temp;

View File

@ -77,7 +77,7 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, const Transfor
Vec3d bbox_center = bbox.center();
// We may need to rotate the bbox / bbox_center from the original instance to the current instance.
double z_diff = Geometry::rotation_diff_z(model_object->instances.front()->get_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());
bbox = bbox.transformed(Transform3d(z_rot));
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());
// 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.z() = model_object->max_z();
this->set_instances(std::move(instances));
}
@ -1736,7 +1737,7 @@ void PrintObject::update_slicing_parameters()
{
if (!m_slicing_params.valid)
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)
@ -2226,9 +2227,11 @@ void PrintObject::combine_infill()
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(); }));
} 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);
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->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->raft_interface_flow = support_material_interface_flow;
// 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);
@ -377,13 +378,14 @@ SupportParameters::SupportParameters(const PrintObject &object)
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_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->support_spacing = object_config.support_material_spacing.value + this->support_material_flow.spacing();
this->support_density = std::min(1., this->support_material_flow.spacing() / this->support_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() / interface_spacing);
double raft_interface_spacing = object_config.support_material_interface_spacing.value + this->raft_interface_flow.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) {
// No interface layers allowed, print everything with the base support pattern.
this->interface_spacing = this->support_spacing;
this->interface_density = this->support_density;
}
@ -393,6 +395,7 @@ SupportParameters::SupportParameters(const PrintObject &object)
support_pattern == smpHoneycomb ? ipHoneycomb :
this->support_density > 0.95 || this->with_sheath ? 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 =
(object_config.support_material_interface_pattern == smipAuto && slicing_params.soluble_interface) ||
object_config.support_material_interface_pattern == smipConcentric ?
@ -799,10 +802,6 @@ public:
)
{
switch (m_style) {
case smsTree:
case smsOrganic:
assert(false);
[[fallthrough]];
case smsGrid:
{
#ifdef SUPPORT_USE_AGG_RASTERIZER
@ -893,6 +892,10 @@ public:
polygons_rotate(out, m_support_angle);
return out;
}
case smsTree:
case smsOrganic:
// assert(false);
[[fallthrough]];
case smsSnug:
// Merge the support polygons by applying morphological closing and inwards smoothing.
auto closing_distance = scaled<float>(m_support_material_closing_radius);
@ -1763,7 +1766,7 @@ static inline void fill_contact_layer(
#endif // SLIC3R_DEBUG
));
// 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) {
// 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());
@ -2011,11 +2014,11 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::top_contact_layers(
// Find the bottom contact layers above the top surfaces of this layer.
static inline SupportGeneratorLayer* detect_bottom_contacts(
const SlicingParameters &slicing_params,
const SupportParameters &support_params,
const SupportParameters &support_params,
const PrintObject &object,
const Layer &layer,
// 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.
size_t contact_idx,
// 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.
Polygons brim;
if (object.has_brim()) {
// The object does not have a raft.
// Calculate the area covered by the brim.
const BrimType brim_type = object.config().brim_type;
const bool brim_outer = brim_type == btOuterOnly || brim_type == btOuterAndInner;
@ -2948,12 +2952,20 @@ SupportGeneratorLayersPtr generate_raft_base(
if (slicing_params.raft_layers() > 1) {
Polygons base;
Polygons columns;
Polygons first_layer;
if (columns_base != nullptr) {
base = columns_base->polygons;
columns = base;
if (! interface_polygons.empty())
// Trim the 1st layer columns with the inflated interface polygons.
columns = diff(columns, interface_polygons);
if (columns_base->print_z > slicing_params.raft_contact_top_z - EPSILON) {
// Classic supports with colums above the raft interface.
base = columns_base->polygons;
columns = base;
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()) {
// 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.height = slicing_params.first_print_layer_height;
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.
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) &&
// 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));
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_bottom = m_object_config->support_material_bottom_interface_layers;
if (num_interface_layers_bottom < 0)
@ -3492,12 +3505,7 @@ static inline void fill_expolygons_with_sheath_generate_paths(
if (polygons.empty())
return;
if (with_sheath) {
if (density == 0) {
tree_supports_generate_paths(dst, polygons, flow);
return;
}
} else {
if (! with_sheath) {
fill_expolygons_generate_paths(dst, closing_ex(polygons, float(SCALED_EPSILON)), filler, density, role, flow);
return;
}
@ -4227,33 +4235,41 @@ void generate_support_toolpaths(
}
// 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),
[&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]
(const tbb::blocked_range<size_t>& range) {
for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id)
{
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());
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));
filler_interface->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.
if (support_layer_id > 0) {
const Polygons &to_infill_polygons = (support_layer_id < slicing_params.base_raft_layers) ?
raft_layer.polygons :
//FIXME misusing contact_polygons for support columns.
((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()) {
assert(! raft_layer.bridging);
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();
Fill *filler = filler_support.get();
filler->angle = raft_angle_base;
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));
@ -4261,13 +4277,15 @@ void generate_support_toolpaths(
// Destination
support_layer.support_fills.entities,
// Regions to fill
to_infill_polygons,
tree_polygons.empty() ? to_infill_polygons : diff(to_infill_polygons, tree_polygons),
// Filler and its parameters
filler, float(support_params.support_density),
// Extrusion parameters
ExtrusionRole::SupportMaterial, flow,
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();
@ -4284,8 +4302,8 @@ void generate_support_toolpaths(
// value that guarantees that all layers are correctly aligned.
filler->spacing = support_params.support_material_flow.spacing();
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());
density = float(support_params.interface_density);
flow = Flow(float(support_params.raft_interface_flow.width()), float(raft_layer.height), support_params.raft_interface_flow.nozzle_diameter());
density = float(support_params.raft_interface_density);
} else
continue;
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density));
@ -4293,7 +4311,7 @@ void generate_support_toolpaths(
// Destination
support_layer.support_fills.entities,
// Regions to fill
raft_layer.polygons,
tree_polygons.empty() ? raft_layer.polygons : diff(raft_layer.polygons, tree_polygons),
// Filler and its parameters
filler, density,
// Extrusion parameters
@ -4328,7 +4346,7 @@ void generate_support_toolpaths(
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,
&bbox_object, &angles, link_max_length_factor]
&bbox_object, &angles, n_raft_layers, link_max_length_factor]
(const tbb::blocked_range<size_t>& range) {
// Indices of the 1st layer in their respective container at the support layer height.
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);
// Pointer to the 1st layer interface filler.
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).
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));
@ -4349,6 +4372,8 @@ void generate_support_toolpaths(
filler_interface->set_bounding_box(bbox_object);
if (filler_first_layer_ptr)
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)
filler_base_interface->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];
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.) :
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)
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 no top interface layers were requested, we treat the contact layer exactly as a generic base layer.
if (support_params.can_merge_support_regions) {
if (base_layer.could_merge(top_contact_layer))
// Don't merge the raft contact layer though.
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));
else if (base_layer.empty())
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.
// 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.
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));
}
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));
else if (base_layer.empty() && ! bottom_contact_layer.empty() && ! bottom_contact_layer.layer->bridging)
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));
else if (bottom_contact_layer.could_merge(interface_layer))
bottom_contact_layer.merge(std::move(interface_layer));
@ -4426,35 +4453,44 @@ void generate_support_toolpaths(
#endif
// Top and bottom contacts, interface layers.
for (size_t i = 0; i < 3; ++ i) {
SupportGeneratorLayerExtruded &layer_ex = (i == 0) ? top_contact_layer : (i == 1 ? bottom_contact_layer : interface_layer);
if (layer_ex.empty() || layer_ex.polygons_to_extrude().empty())
continue;
bool interface_as_base = config.support_material_interface_layers.value == 0 ||
(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
// the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b)
auto interface_flow = layer_ex.layer->bridging ?
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));
filler_interface->angle = interface_as_base ?
// If zero interface layers are configured, use the same angle as for the base layers.
angles[support_layer_id % angles.size()] :
// Use interface angle for the interface layers.
support_params.interface_angle + interface_angle_delta;
double density = interface_as_base ? support_params.support_density : support_params.interface_density;
filler_interface->spacing = interface_as_base ? support_params.support_material_flow.spacing() : support_params.support_material_interface_flow.spacing();
filler_interface->link_max_length = coord_t(scale_(filler_interface->spacing * link_max_length_factor / density));
fill_expolygons_generate_paths(
// Destination
layer_ex.extrusions,
// Regions to fill
union_safety_offset_ex(layer_ex.polygons_to_extrude()),
// Filler and its parameters
filler_interface.get(), float(density),
// Extrusion parameters
ExtrusionRole::SupportMaterialInterface, interface_flow);
}
enum class InterfaceLayerType { TopContact, BottomContact, RaftContact, Interface, InterfaceAsBase };
auto extrude_interface = [&](SupportGeneratorLayerExtruded &layer_ex, InterfaceLayerType interface_layer_type) {
if (! layer_ex.empty() && ! layer_ex.polygons_to_extrude().empty()) {
bool interface_as_base = interface_layer_type == InterfaceLayerType::InterfaceAsBase;
bool raft_contact = interface_layer_type == InterfaceLayerType::RaftContact;
//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)
auto *filler = raft_contact ? filler_raft_contact : filler_interface.get();
auto interface_flow = layer_ex.layer->bridging ?
Flow::bridging_flow(layer_ex.layer->height, support_params.support_material_bottom_interface_flow.nozzle_diameter()) :
(raft_contact ? &support_params.raft_interface_flow :
interface_as_base ? &support_params.support_material_flow : &support_params.support_material_interface_flow)
->with_height(float(layer_ex.layer->height));
filler->angle = interface_as_base ?
// If zero interface layers are configured, use the same angle as for the base layers.
angles[support_layer_id % angles.size()] :
// Use interface angle for the interface layers.
support_params.interface_angle + interface_angle_delta;
double density = raft_contact ? support_params.raft_interface_density : interface_as_base ? support_params.support_density : support_params.interface_density;
filler->spacing = raft_contact ? support_params.raft_interface_flow.spacing() :
interface_as_base ? support_params.support_material_flow.spacing() : support_params.support_material_interface_flow.spacing();
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density));
fill_expolygons_generate_paths(
// Destination
layer_ex.extrusions,
// Regions to fill
union_safety_offset_ex(layer_ex.polygons_to_extrude()),
// 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
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);
bool sheath = support_params.with_sheath;
bool no_sort = false;
bool done = false;
if (base_layer.layer->bottom_z < EPSILON) {
// Base flange (the 1st 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));
sheath = 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(
// Destination
base_layer.extrusions,
// Regions to fill
base_layer.polygons_to_extrude(),
// Filler and its parameters
filler, density,
// Extrusion parameters
ExtrusionRole::SupportMaterial, flow,
sheath, no_sort);
if (! done)
fill_expolygons_with_sheath_generate_paths(
// Destination
base_layer.extrusions,
// Regions to fill
base_layer.polygons_to_extrude(),
// Filler and its parameters
filler, density,
// Extrusion parameters
ExtrusionRole::SupportMaterial, flow,
sheath, no_sort);
}
// Merge base_interface_layers to base_layers to avoid unneccessary retractions
@ -4708,3 +4748,4 @@ sub clip_with_shape {
*/
} // 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
// about the support layer type than the final support layers stored in a PrintObject.
enum SupporLayerType {
enum class SupporLayerType {
Unknown = 0,
// Ratft base layer, to be printed with the support material.
RaftBase,
@ -122,10 +122,17 @@ using SupportGeneratorLayersPtr = std::vector<SupportGeneratorLayer*>;
struct SupportParameters {
SupportParameters(const PrintObject &object);
// Flow at the 1st print layer.
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 at the top interface and contact layers.
Flow support_material_interface_flow;
// Flow at the bottom interfaces and contacts.
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?
bool can_merge_support_regions;
@ -136,14 +143,23 @@ struct SupportParameters {
float base_angle;
float interface_angle;
coordf_t interface_spacing;
// Density of the top / bottom interface and contact layers.
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;
// Pattern of the sparse infill including sparse raft layers.
InfillPattern base_fill_pattern;
// Pattern of the top / bottom interface and contact layers.
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;
// Shall the sparse (base) layers be printed with a single perimeter line (sheath) for robustness?
bool with_sheath;
};

View File

@ -158,14 +158,24 @@ TreeModelVolumes::TreeModelVolumes(
{
m_anti_overhang = print_object.slice_support_blockers();
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_layer_outlines.emplace_back(mesh_settings, std::vector<Polygons>{});
std::vector<Polygons> &outlines = m_layer_outlines.front().second;
outlines.assign(print_object.layer_count(), Polygons{});
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_raft_layers = m_raft_layers.size();
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) {
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
@ -177,13 +187,6 @@ TreeModelVolumes::TreeModelVolumes(
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
for (size_t mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++) {
SliceMeshStorage mesh = storage.meshes[mesh_idx];
@ -214,7 +217,7 @@ TreeModelVolumes::TreeModelVolumes(
#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();
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.
// 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.
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.

View File

@ -179,6 +179,10 @@ struct TreeSupportMeshGroupSettings {
// 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,
// 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%
double support_tree_top_rate { 15. };
// 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.
* 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.
@ -614,6 +618,9 @@ private:
*/
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
* at given radius and layer indices.

View File

@ -59,6 +59,91 @@ namespace Slic3r
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
{
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);
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.
#if 0
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
}
[[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 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!
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),
[&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) {
for (LayerIndex layer_id = range.begin(); layer_id < range.end(); ++ 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");
}
}
out[layer_id] = std::move(overhangs);
out[layer_id + num_raft_layers] = std::move(overhangs);
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;
}
@ -333,16 +446,18 @@ void tree_supports_show_error(std::string_view message, bool critical)
// calculate top most layer that is relevant for support
LayerIndex max_layer = 0;
for (size_t object_id : object_ids) {
const PrintObject &print_object = *print.get_object(object_id);
int max_support_layer_id = 0;
for (int layer_id = 1; layer_id < int(print_object.layer_count()); ++ layer_id)
const PrintObject &print_object = *print.get_object(object_id);
const int num_raft_layers = int(config.raft_layers.size());
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())
max_support_layer_id = layer_id;
max_layer = std::max(max_support_layer_id - int(config.z_distance_top_layers), 0);
}
if (max_layer > 0)
// 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;
}
@ -815,29 +930,38 @@ static std::optional<std::pair<Point, size_t>> polyline_sample_next_point_at_dis
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(
SupportGeneratorLayer &layer_new,
const SupporLayerType layer_type,
const SlicingParameters &slicing_params,
const TreeSupportSettings &config,
const size_t layer_idx)
{
layer_new.layer_type = layer_type;
layer_new.print_z = layer_z(slicing_params, 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 ? slicing_params.object_print_z_min : layer_new.print_z - layer_new.height;
layer_new.print_z = layer_z(slicing_params, config, layer_idx);
layer_new.bottom_z = layer_idx > 0 ? layer_z(slicing_params, config, layer_idx - 1) : 0;
layer_new.height = layer_new.print_z - layer_new.bottom_z;
return layer_new;
}
@ -846,11 +970,12 @@ inline SupportGeneratorLayer& layer_allocate(
std::deque<SupportGeneratorLayer> &layer_storage,
SupporLayerType layer_type,
const SlicingParameters &slicing_params,
const TreeSupportSettings &config,
size_t layer_idx)
{
//FIXME take raft into account.
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(
@ -858,11 +983,12 @@ inline SupportGeneratorLayer& layer_allocate(
tbb::spin_mutex& layer_storage_mutex,
SupporLayerType layer_type,
const SlicingParameters &slicing_params,
const TreeSupportSettings &config,
size_t layer_idx)
{
tbb::spin_mutex::scoped_lock lock(layer_storage_mutex);
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>;
@ -890,7 +1016,7 @@ static void generate_initial_areas(
static constexpr const auto base_radius = scaled<int>(0.01);
const Polygon base_circle = make_circle(base_radius, SUPPORT_TREE_CIRCLE_RESOLUTION);
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);
support_params.with_sheath = true;
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) :
0;
//FIXME
size_t num_support_layers = print_object.layer_count();
std::vector<std::unordered_set<Point, PointHash>> already_inserted(num_support_layers - z_distance_delta);
const size_t num_raft_layers = config.raft_layers.size();
const size_t num_support_layers = size_t(std::max(0, int(print_object.layer_count()) + int(num_raft_layers) - int(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;
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,
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,
@ -1060,7 +1205,7 @@ static void generate_initial_areas(
std::lock_guard<std::mutex> lock(mutex_layer_storage);
SupportGeneratorLayer *&l = top_contacts[insert_layer_idx - dtt_roof_tip];
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));
}
}
@ -1242,7 +1387,7 @@ static void generate_initial_areas(
if (! added_roofs[idx].empty()) {
SupportGeneratorLayer *&l = top_contacts[layer_idx - idx];
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()
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);
SupportGeneratorLayer*& l = top_contacts[0];
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));
} 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);
@ -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())
@ -3047,7 +3235,7 @@ static void finalize_interface_and_support_areas(
}
if (! floor_layer.empty()) {
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);
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 == 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);
}
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);
}
@ -3391,8 +3579,8 @@ static void extrude_branch(
const SupportElement &prev = *path[ipath - 1];
const SupportElement &current = *path[ipath];
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));
p2 = to_3d(unscaled<double>(current.state.result_on_layer), layer_z(slicing_params, current.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, config, current.state.layer_idx));
v1 = (p2 - p1).normalized();
if (ipath == 1) {
nprev = v1;
@ -3439,7 +3627,7 @@ static void extrude_branch(
} else {
const SupportElement &next = *path[ipath + 1];
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();
ncurrent = (v1 + v2).normalized();
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),
unscaled<float>(config.getRadius(element.state)),
// 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.
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.
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.layer_begin = std::min(collision_sphere.element.state.layer_idx, layer_idx_ceil(slicing_params, 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;
collision_sphere.layer_begin = std::min(collision_sphere.element.state.layer_idx, layer_idx_ceil(slicing_params, config, collision_sphere.min_z));
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();
@ -3596,7 +3785,7 @@ static void organic_smooth_branches_avoid_collisions(
collision_sphere.prev_position = collision_sphere.position;
std::atomic<size_t> num_moved{ 0 };
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)
if (CollisionSphere &collision_sphere = collision_spheres[collision_sphere_id]; ! collision_sphere.locked) {
// 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;
if (collision_depth > collision_sphere.last_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<float> distances;
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() });
}
@ -3918,9 +4107,9 @@ static void slice_branches(
const SlicingParameters &slicing_params = print_object.slicing_parameters();
std::vector<float> slice_z;
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;
double layer_height = layer_idx == 0 ? slicing_params.first_object_layer_height : slicing_params.layer_height;
slice_z.emplace_back(float(print_z - layer_height * 0.5));
const double print_z = layer_z(print_object.slicing_parameters(), config, layer_idx);
const double bottom_z = layer_idx > 0 ? layer_z(print_object.slicing_parameters(), config, layer_idx - 1) : 0.;
slice_z.emplace_back(float(0.5 * (bottom_z + print_z)));
}
// Remove the trailing slices.
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.
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.
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.
// Used by both classic and tree supports.
SupportParameters support_params(print_object);
support_params.with_sheath = true;
support_params.support_density = 0;
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);
#if 1 //#ifdef SLIC3R_DEBUG

View File

@ -40,6 +40,7 @@ namespace Slic3r
class Print;
class PrintObject;
class SupportGeneratorLayer;
struct SlicingParameters;
using SupportGeneratorLayerStorage = std::deque<SupportGeneratorLayer>;
using SupportGeneratorLayersPtr = std::vector<SupportGeneratorLayer*>;
@ -255,64 +256,7 @@ struct SupportElement
struct TreeSupportSettings
{
TreeSupportSettings() = default; // required for the definition of the config variable in the TreeSupportGenerator class.
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;
}
explicit TreeSupportSettings(const TreeSupportMeshGroupSettings &mesh_group_settings, const SlicingParameters &slicing_params);
private:
double angle;
@ -466,6 +410,9 @@ public:
*/
coord_t min_feature_size;
// Extra raft layers below the object.
std::vector<coordf_t> raft_layers;
public:
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"))
)
#endif
&& raft_layers == other.raft_layers
;
}

View File

@ -140,8 +140,8 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
}
} else {
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"
"if they are printed with the current extruder without triggering a tool change.\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. "
"(both support_material_extruder and support_material_interface_extruder need to be set to 0)."));
if (is_global_config)
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);
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.
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);

View File

@ -2246,7 +2246,7 @@ void GCodeViewer::load_shells(const Print& print)
if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF) {
// 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 size_t extruders_count = config.nozzle_diameter.size();
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.
// 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.
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 ||
(model_object_new != nullptr && m_model_object->id() != model_object_new->id())) {
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) {
// 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 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;
if (model.objects.size() < v.object_idx()) {
if (v.object_idx() < model.objects.size()) {
const ModelObject *obj = model.objects[v.object_idx()];
if (v.volume_idx() < obj->volumes.size()) {
ret = obj->volumes[v.volume_idx()];
}
if (v.object_idx() < model.objects.size()) {
const ModelObject *obj = model.objects[v.object_idx()];
if (v.volume_idx() < obj->volumes.size()) {
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_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,
_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"
"\n"
"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_size = volume->get_instance_scaling_factor().cwiseProduct(wxGetApp().model().objects[volume->object_idx()]->raw_mesh_bounding_box().size());
#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;

View File

@ -200,7 +200,7 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename,
: GLGizmoBase(parent, icon_filename, sprite_id)
, m_connectors_group_id (GrabberID::Count)
, 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_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_styles = { _u8L("Prizm"), _u8L("Frustum")
m_connector_styles = { _u8L("Prism"), _u8L("Frustum")
// , _u8L("Claw")
};
@ -245,15 +245,17 @@ std::string GLGizmoCut3D::get_tooltip() const
double koef = m_imperial_units ? ObjectManipulation::mm_to_in : 1.0;
std::string unit_str = " " + (m_imperial_units ? _u8L("inch") : _u8L("mm"));
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) {
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)
tooltip += "\n";
}
if (tbb.min.z() <= 0.0) {
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;
}
@ -362,11 +364,8 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event)
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);
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;
}
@ -405,18 +404,15 @@ bool GLGizmoCut3D::is_looking_forward() const
void GLGizmoCut3D::update_clipper()
{
// update cut_normal
Vec3d beg, end = beg = m_bb_center;
beg[Z] -= m_radius;
end[Z] += m_radius;
rotate_vec3d_around_plane_center(beg);
rotate_vec3d_around_plane_center(end);
// calculate normal for cut plane
Vec3d normal = m_cut_normal = end - beg;
Vec3d normal = m_rotation_m * Vec3d::UnitZ();
normal.normalize();
m_cut_normal = normal;
// 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;
double offset = normal.dot(m_plane_center);
double dist = normal.dot(beg);
@ -425,17 +421,13 @@ void GLGizmoCut3D::update_clipper()
if (!is_looking_forward()) {
// recalculate normal and offset for clipping plane, if camera is looking downward to cut plane
end = beg = m_bb_center;
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 = m_rotation_m * (-1. * Vec3d::UnitZ());
normal.normalize();
beg = m_bb_center;
beg[Z] += m_radius;
rotate_vec3d_around_plane_center(beg);
m_clp_normal = normal;
offset = normal.dot(m_plane_center);
dist = normal.dot(beg);
@ -910,7 +902,7 @@ void GLGizmoCut3D::on_set_state()
update_bb();
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
m_ar_plane_center = m_plane_center;
@ -1011,9 +1003,11 @@ void GLGizmoCut3D::update_raycasters_for_picking_transform()
// recalculate connector position to world position
Vec3d pos = connector.pos + instance_offset;
if (connector.attribs.type == CutConnectorType::Dowel &&
connector.attribs.style == CutConnectorStyle::Prizm) {
pos -= height * m_clp_normal;
height *= 2;
connector.attribs.style == CutConnectorStyle::Prism) {
if (is_looking_forward())
pos -= static_cast<double>(height - 0.05f) * m_clp_normal;
else
pos += 0.05 * m_clp_normal;
}
pos[Z] += sla_shift;
@ -1344,7 +1338,8 @@ void GLGizmoCut3D::update_bb()
on_unregister_raycasters_for_picking();
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);
}
}
@ -1559,7 +1554,7 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors)
m_imgui->disabled_begin(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); });
}
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;
ImGui::AlignTextToFramePadding();
m_imgui->text(_L("Build size"));
m_imgui->text(_L("Build Volume"));
ImGui::SameLine(m_label_width);
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, size);
}
@ -1860,7 +1855,7 @@ void GLGizmoCut3D::validate_connector_settings()
if (m_connector_type == CutConnectorType::Undef)
m_connector_type = CutConnectorType::Plug;
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))
m_connector_shape_id = size_t(CutConnectorShape::Circle);
}
@ -2096,12 +2091,20 @@ void GLGizmoCut3D::render_connectors()
const Camera& camera = wxGetApp().plater()->get_camera();
if (connector.attribs.type == CutConnectorType::Dowel &&
connector.attribs.style == CutConnectorStyle::Prizm) {
if (is_looking_forward())
pos -= height * m_clp_normal;
else
pos += height * m_clp_normal;
height *= 2;
connector.attribs.style == CutConnectorStyle::Prism) {
if (m_connectors_editing) {
if (is_looking_forward())
pos -= static_cast<double>(height-0.05f) * m_clp_normal;
else
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())
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;
if (connector.attribs.type == CutConnectorType::Dowel) {
if (connector.attribs.style == CutConnectorStyle::Prizm)
if (connector.attribs.style == CutConnectorStyle::Prism)
connector.height *= 2;
create_dowels_as_separate_object = true;
}
else {
// calculate shift of the connector center regarding to the position on the cut plane
Vec3d shifted_center = m_plane_center + Vec3d::UnitZ();
rotate_vec3d_around_plane_center(shifted_center);
Vec3d norm = (shifted_center - m_plane_center).normalized();
connector.pos += norm * 0.5 * double(connector.height);
connector.pos += m_cut_normal * 0.5 * double(connector.height);
}
}
mo->apply_cut_connectors(_u8L("Connector"));
@ -2255,7 +2255,7 @@ void GLGizmoCut3D::reset_connectors()
void GLGizmoCut3D::init_connector_shapes()
{
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}) {
const CutConnectorAttributes attribs = { type, style, shape };
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)
ImGui::SetNextTreeNodeOpen(false);
if (ImGui::TreeNode(_u8L("advanced").c_str())) {
if (ImGui::TreeNode(_u8L("Advanced").c_str())) {
if (!m_is_advanced_edit_style) {
set_minimal_window_size(true);
} 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());
} else if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s", ((m_allow_open_near_volume) ?
_u8L("Fix settings possition"):
_u8L("Allow floating window near text")).c_str());
"Fix settings position":
"Allow floating window near text").c_str());
}
#endif // ALLOW_FLOAT_WINDOW
}
@ -1654,9 +1654,9 @@ void GLGizmoEmboss::draw_text_input()
auto &ff = m_style_manager.get_font_file_with_cache();
float imgui_size = StyleManager::get_imgui_font_size(prop, *ff.font_file, scale);
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)
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);
}
@ -3175,7 +3175,8 @@ void GLGizmoEmboss::draw_advanced()
{
const auto &ff = m_style_manager.get_font_file_with_cache();
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;
}
@ -3301,7 +3302,7 @@ void GLGizmoEmboss::draw_advanced()
bool use_inch = wxGetApp().app_config->get_bool("use_inches");
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;
if (use_inch) {
std::optional<float> distance_inch;
@ -3597,7 +3598,7 @@ void GLGizmoEmboss::create_notification_not_valid_font(
std::string text =
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."),
face_name_3mf, face_name);
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();
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();
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();
m_bounding_box = box;
m_center = box_trafo.translation();
m_orient_matrix = Geometry::translation_transform(m_center);
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_orient_matrix = box_trafo;
m_radius = Offset + m_bounding_box.radius();
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
ctl.call_on_main_thread([]() {
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];
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();
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];
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_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;
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();
}
@ -4988,7 +4988,16 @@ bool Plater::priv::can_decrease_instances(int obj_idx /*= -1*/) const
if (obj_idx < 0)
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) &&
!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)
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];
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)
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];
if (model_object->instances.size() > num) {
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) {
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
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",
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"));
activate_options_tab(m_optgroup_other);
@ -645,7 +645,7 @@ void PreferencesDialog::build()
{
append_bool_option(m_optgroup_dark_mode, "sys_menu_enabled",
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."),
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);
}
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 {
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()));
Transform3d rotation_matrix = Geometry::rotation_transform(rotation);
for (unsigned int i : m_list) {
Transform3d rotation_matrix = Geometry::rotation_transform(rotation);
GLVolume& v = *(*m_volumes)[i];
const VolumeCache& volume_data = m_cache.volumes_data[i];
const Geometry::Transformation& inst_trafo = volume_data.get_instance_transform();
if (m_mode == Instance && !is_wipe_tower()) {
assert(is_from_fully_selected_instance(i));
if (transformation_type.instance()) {
const Vec3d world_inst_pivot = m_cache.dragging_center - inst_trafo.get_offset();
const Vec3d local_inst_pivot = inst_trafo.get_matrix_no_offset().inverse() * world_inst_pivot;
Matrix3d inst_rotation, inst_scale;
inst_trafo.get_matrix().computeRotationScaling(&inst_rotation, &inst_scale);
const Transform3d trafo = inst_trafo.get_rotation_matrix() * rotation_matrix;
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 that the instance rotates as a rigid body
Transform3d inst_rotation_matrix = inst_trafo.get_rotation_matrix();
if (inst_trafo.is_left_handed()) {
Geometry::TransformationSVD inst_svd(inst_trafo);
inst_rotation_matrix = inst_svd.u * inst_svd.v.transpose();
// 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_world(v, volume_data, transformation_type, rotation_matrix, m_cache.dragging_center);
transform_instance_relative(v, volume_data, transformation_type, rotation_matrix, m_cache.dragging_center);
}
else {
if (!is_single_volume_or_modifier()) {
@ -1024,27 +1032,26 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_
}
else {
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
const Transform3d inst_scale_matrix = inst_trafo.get_scaling_factor_matrix();
rotation_matrix = inst_scale_matrix.inverse() * rotation_matrix * inst_scale_matrix;
}
else {
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
if (Geometry::TransformationSVD(world_trafo).anisotropic_scale) {
const Transform3d inst_scale_matrix = inst_trafo.get_scaling_factor_matrix();
rotation_matrix = inst_scale_matrix.inverse() * rotation_matrix * inst_scale_matrix;
const Geometry::Transformation& vol_trafo = volume_data.get_volume_transform();
const Transform3d vol_matrix_no_offset = vol_trafo.get_matrix_no_offset();
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);
@ -1056,7 +1063,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_
if (m_mode == Instance) {
int rot_axis_max = 0;
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)
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));
}
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 {
if (!is_single_volume_or_modifier()) {
@ -3331,16 +3338,21 @@ void Selection::paste_objects_from_clipboard()
}
#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)
{
assert(transformation_type.relative());
assert(transformation_type.world());
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;
const Transform3d trafo = Geometry::translation_transform(inst_pivot) * transform * Geometry::translation_transform(-inst_pivot);
volume.set_instance_transformation(trafo * inst_trafo.get_matrix());
if (transformation_type.world()) {
const Vec3d inst_pivot = transformation_type.independent() && !is_from_single_instance() ? inst_trafo.get_offset() : world_pivot;
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,

View File

@ -516,7 +516,7 @@ private:
void paste_objects_from_clipboard();
#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);
void transform_volume_relative(GLVolume& volume, const VolumeCache& volume_data, TransformationType transformation_type,
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) {
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
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()
{
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.");
if (!GUI::wxGetApp().check_and_save_current_preset_changes(_L("Updating"), header, false ))
return false;