Merge branch 'master' into fs_emboss

This commit is contained in:
Filip Sykala - NTB T15p 2023-02-28 10:17:04 +01:00
commit b5ec9ad882
25 changed files with 2503 additions and 2202 deletions

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1241,7 +1241,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 +1347,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 +1356,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 +1398,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 +1466,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 +1481,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 +1625,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);

View File

@ -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() {}
@ -459,10 +459,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);

View File

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

View File

@ -2226,9 +2226,11 @@ void PrintObject::combine_infill()
void PrintObject::_generate_support_material()
{
if (m_config.support_material_style == smsTree || m_config.support_material_style == smsOrganic) {
if (this->has_support() && (m_config.support_material_style == smsTree || m_config.support_material_style == smsOrganic)) {
fff_tree_support_generate(*this, std::function<void()>([this](){ this->throw_if_canceled(); }));
} else {
// If support style is set to Organic however only raft will be built but no support,
// build snug raft instead.
PrintObjectSupportMaterial support_material(this, m_slicing_params);
support_material.generate(*this);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7100,12 +7100,10 @@ const ModelVolume *get_model_volume(const GLVolume &v, const Model &model)
{
const ModelVolume * ret = nullptr;
if (model.objects.size() < v.object_idx()) {
if (v.object_idx() < model.objects.size()) {
const ModelObject *obj = model.objects[v.object_idx()];
if (v.volume_idx() < obj->volumes.size()) {
ret = obj->volumes[v.volume_idx()];
}
if (v.object_idx() < model.objects.size()) {
const ModelObject *obj = model.objects[v.object_idx()];
if (v.volume_idx() < obj->volumes.size()) {
ret = obj->volumes[v.volume_idx()];
}
}

View File

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

View File

@ -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,10 +364,9 @@ 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();
set_center(m_plane_center + starting_vec * delta, true);
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move cut plane"), UndoRedo::SnapshotType::GizmoAction);
set_center(m_plane_center + m_cut_normal * delta, true);
m_ar_plane_center = m_plane_center;
}
void GLGizmoCut3D::rotate_vec3d_around_plane_center(Vec3d&vec)
@ -402,46 +403,34 @@ bool GLGizmoCut3D::is_looking_forward() const
void GLGizmoCut3D::update_clipper()
{
BoundingBoxf3 box = m_bounding_box;
// update cut_normal
Vec3d beg, end = beg = m_plane_center;
beg[Z] = box.center().z() - m_radius;
end[Z] = box.center().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
double dist = (m_plane_center - beg).norm();
dist = std::clamp(dist, 0.0001, normal.norm());
normal.normalize();
m_clp_normal = normal;
double offset = normal.dot(beg) + dist;
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);
m_parent.set_color_clip_plane(normal, offset);
if (!is_looking_forward()) {
// recalculate normal and offset for clipping plane, if camera is looking downward to cut plane
end = beg = m_plane_center;
beg[Z] = box.center().z() + m_radius;
end[Z] = box.center().z() - m_radius;
rotate_vec3d_around_plane_center(beg);
rotate_vec3d_around_plane_center(end);
normal = end - beg;
if (normal == Vec3d::Zero())
return;
dist = (m_plane_center - beg).norm();
dist = std::clamp(dist, 0.0001, normal.norm());
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(beg) + dist;
offset = normal.dot(m_plane_center);
dist = normal.dot(beg);
}
m_c->object_clipper()->set_range_and_pos(normal, offset, dist);
@ -579,7 +568,9 @@ void GLGizmoCut3D::render_move_center_input(int axis)
if (in_val != val) {
move[axis] = val;
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move cut plane"), UndoRedo::SnapshotType::GizmoAction);
set_center(move, true);
m_ar_plane_center = m_plane_center;
}
}
@ -886,7 +877,8 @@ void GLGizmoCut3D::on_load(cereal::BinaryInputArchive& ar)
ar( m_keep_upper, m_keep_lower, m_rotate_lower, m_rotate_upper, m_hide_cut_plane, m_mode, m_connectors_editing,
m_ar_plane_center, m_rotation_m);
set_center_pos(m_ar_plane_center, true);
m_transformed_bounding_box = transformed_bounding_box(m_ar_plane_center, m_rotation_m);
set_center_pos(m_ar_plane_center);
m_parent.request_extra_frame();
}
@ -910,11 +902,11 @@ void GLGizmoCut3D::on_set_state()
update_bb();
m_connectors_editing = !m_selected.empty();
m_transformed_bounding_box = transformed_bounding_box(m_plane_center, m_rotation_m);
// initiate archived values
m_ar_plane_center = m_plane_center;
m_start_dragging_m = m_rotation_m;
m_transformed_bounding_box = transformed_bounding_box(m_plane_center);
m_parent.request_extra_frame();
}
@ -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;
@ -1179,7 +1173,7 @@ void GLGizmoCut3D::dragging_grabber_xy(const GLGizmoBase::UpdateData &data)
const bool update_tbb = m_rotation_m.rotation() != rotation_tmp.rotation();
m_rotation_m = rotation_tmp;
if (update_tbb)
m_transformed_bounding_box = transformed_bounding_box(m_plane_center);
m_transformed_bounding_box = transformed_bounding_box(m_plane_center, m_rotation_m);
m_angle = theta;
while (m_angle > two_pi)
@ -1246,11 +1240,9 @@ void GLGizmoCut3D::set_center_pos(const Vec3d& center_pos, bool update_tbb /*=fa
Vec3d normal = m_rotation_m.inverse() * Vec3d(m_plane_center - center_pos);
tbb.translate(normal.z() * Vec3d::UnitZ());
}
const Vec3d& instance_offset = m_parent.get_selection().get_first_volume()->get_instance_offset();
const Vec3d trans_center_pos = (m_rotation_m.inverse() * (center_pos - instance_offset)) + tbb.center();
bool can_set_center_pos = tbb.contains(trans_center_pos);
if (!can_set_center_pos) {
bool can_set_center_pos = false;
{
if (tbb.max.z() > -.5 && tbb.min.z() < .5)
can_set_center_pos = true;
else {
@ -1283,12 +1275,12 @@ BoundingBoxf3 GLGizmoCut3D::bounding_box() const
return ret;
}
BoundingBoxf3 GLGizmoCut3D::transformed_bounding_box(const Vec3d& plane_center) const
BoundingBoxf3 GLGizmoCut3D::transformed_bounding_box(const Vec3d& plane_center, const Transform3d& rotation_m/* = Transform3d::Identity()*/) const
{
const Selection& selection = m_parent.get_selection();
const Vec3d& instance_offset = selection.get_first_volume()->get_instance_offset();
const auto cut_matrix = Transform3d::Identity() * m_rotation_m.inverse() * translation_transform(instance_offset - plane_center);
const auto cut_matrix = Transform3d::Identity() * rotation_m.inverse() * translation_transform(instance_offset - plane_center);
const Selection::IndicesList& idxs = selection.get_volume_idxs();
BoundingBoxf3 ret;
@ -1346,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);
}
}
@ -1544,8 +1537,11 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors)
m_imgui->disabled_begin(connectors.empty());
ImGui::SameLine(m_label_width);
if (render_reset_button("connectors", _u8L("Remove connectors")))
const wxString act_name = _L("Remove connectors");
if (render_reset_button("connectors", into_u8(act_name))) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), act_name, UndoRedo::SnapshotType::GizmoAction);
reset_connectors();
}
m_imgui->disabled_end();
render_flip_plane_button(m_connectors_editing && connectors.empty());
@ -1558,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))
@ -1610,17 +1606,18 @@ 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);
}
void GLGizmoCut3D::reset_cut_plane()
{
m_rotation_m = Transform3d::Identity();
m_angle_arc.reset();
m_transformed_bounding_box = transformed_bounding_box(m_bb_center);
set_center(m_bb_center);
m_start_dragging_m = m_rotation_m = Transform3d::Identity();
m_ar_plane_center = m_plane_center;
}
void GLGizmoCut3D::invalidate_cut_plane()
@ -1649,7 +1646,7 @@ void GLGizmoCut3D::set_connectors_editing(bool connectors_editing)
void GLGizmoCut3D::flip_cut_plane()
{
m_rotation_m = m_rotation_m * rotation_transform(PI * Vec3d::UnitX());
m_transformed_bounding_box = transformed_bounding_box(m_plane_center);
m_transformed_bounding_box = transformed_bounding_box(m_plane_center, m_rotation_m);
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Flip cut plane"), UndoRedo::SnapshotType::GizmoAction);
m_start_dragging_m = m_rotation_m;
@ -1724,8 +1721,11 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors)
const bool is_cut_plane_init = m_rotation_m.isApprox(Transform3d::Identity()) && m_bb_center == m_plane_center;
m_imgui->disabled_begin(is_cut_plane_init);
if (render_reset_button("cut_plane", _u8L("Reset cutting plane")))
wxString act_name = _L("Reset cutting plane");
if (render_reset_button("cut_plane", into_u8(act_name))) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), act_name, UndoRedo::SnapshotType::GizmoAction);
reset_cut_plane();
}
m_imgui->disabled_end();
// render_flip_plane_button();
@ -1740,7 +1740,9 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors)
ImGui::SameLine(2.5f * m_label_width);
m_imgui->disabled_begin(is_cut_plane_init && !has_connectors);
if (m_imgui->button(_L("Reset cut"), _L("Reset cutting plane and remove connectors"))) {
act_name = _L("Reset cut");
if (m_imgui->button(act_name, _L("Reset cutting plane and remove connectors"))) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), act_name, UndoRedo::SnapshotType::GizmoAction);
reset_cut_plane();
reset_connectors();
}
@ -1853,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);
}
@ -2089,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;
@ -2129,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"));
@ -2248,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);
@ -2299,20 +2306,26 @@ bool GLGizmoCut3D::process_cut_line(SLAGizmoEventType action, const Vec2d& mouse
Vec3d line_dir = m_line_end - m_line_beg;
if (line_dir.norm() < 3.0)
return true;
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Cut by line"), UndoRedo::SnapshotType::GizmoAction);
Vec3d cross_dir = line_dir.cross(dir).normalized();
Eigen::Quaterniond q;
Transform3d m = Transform3d::Identity();
m.matrix().block(0, 0, 3, 3) = q.setFromTwoVectors(Vec3d::UnitZ(), cross_dir).toRotationMatrix();
m_rotation_m = m;
const Vec3d new_plane_center = m_bb_center + cross_dir * cross_dir.dot(pt - m_bb_center);
// update transformed bb
m_transformed_bounding_box = transformed_bounding_box(m_plane_center);
const auto new_tbb = transformed_bounding_box(new_plane_center, m);
const Vec3d& instance_offset = m_parent.get_selection().get_first_volume()->get_instance_offset();
const Vec3d trans_center_pos = m.inverse() * (new_plane_center - instance_offset) + new_tbb.center();
if (new_tbb.contains(trans_center_pos)) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Cut by line"), UndoRedo::SnapshotType::GizmoAction);
m_transformed_bounding_box = new_tbb;
set_center(new_plane_center);
m_start_dragging_m = m_rotation_m = m;
m_ar_plane_center = m_plane_center;
}
m_angle_arc.reset();
set_center(m_plane_center + cross_dir * (cross_dir.dot(pt - m_plane_center)), true);
discard_cut_line_processing();
}
else if (action == SLAGizmoEventType::Moving)

View File

@ -35,6 +35,7 @@ class GLGizmoCut3D : public GLGizmoBase
// archived values
Vec3d m_ar_plane_center { Vec3d::Zero() };
Transform3d m_start_dragging_m{ Transform3d::Identity() };
Vec3d m_plane_center{ Vec3d::Zero() };
// data to check position of the cut palne center on gizmo activation
@ -57,7 +58,6 @@ class GLGizmoCut3D : public GLGizmoBase
double m_snap_fine_out_radius{ 0.0 };
// dragging angel in hovered axes
Transform3d m_start_dragging_m{ Transform3d::Identity() };
double m_angle{ 0.0 };
TriangleMesh m_connector_mesh;
@ -194,7 +194,7 @@ public:
void invalidate_cut_plane();
BoundingBoxf3 bounding_box() const;
BoundingBoxf3 transformed_bounding_box(const Vec3d& plane_center) const;
BoundingBoxf3 transformed_bounding_box(const Vec3d& plane_center, const Transform3d& rotation_m = Transform3d::Identity()) const;
protected:
bool on_init() override;

View File

@ -1419,7 +1419,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 {
@ -1485,8 +1485,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
}
@ -3122,7 +3122,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;
}
@ -3248,7 +3249,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;
@ -3543,7 +3544,7 @@ void GLGizmoEmboss::create_notification_not_valid_font(
std::string text =
GUI::format(_L("Can't load exactly same font(\"%1%\"), "
"Aplication select similar one(\"%2%\"). "
"Aplication selected a similar one(\"%2%\"). "
"You have to specify font for enable edit text."),
face_name_3mf, face_name);
auto notification_manager = wxGetApp().plater()->get_notification_manager();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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