mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-05 09:50:41 +08:00
Merge branch 'et_transformations' of https://github.com/Prusa-Development/PrusaSlicerPrivate into et_transformations
This commit is contained in:
commit
673a7ccff9
File diff suppressed because it is too large
Load Diff
@ -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) {
|
||||
|
||||
|
@ -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); }
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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 ¤t_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 ¤t = *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
|
||||
|
@ -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
|
||||
;
|
||||
}
|
||||
|
||||
|
@ -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?"));
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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()];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 ),
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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."));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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"));
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user