Merge branch 'main' into pr/Noisyfox/13712

This commit is contained in:
SoftFever
2026-06-22 10:56:11 +08:00
2782 changed files with 331956 additions and 215963 deletions

View File

@@ -1733,8 +1733,8 @@ void generate_support_toolpaths(
interface_as_base ? ExtrusionRole::erSupportMaterial : ExtrusionRole::erSupportMaterialInterface, interface_flow);
}
};
const bool top_interfaces = config.support_interface_top_layers.value != 0;
const bool bottom_interfaces = top_interfaces && config.support_interface_bottom_layers != 0;
const bool top_interfaces = support_params.num_top_interface_layers > 0;
const bool bottom_interfaces = top_interfaces && support_params.num_bottom_interface_layers > 0;
extrude_interface(top_contact_layer, raft_layer ? InterfaceLayerType::RaftContact : top_interfaces ? InterfaceLayerType::TopContact : InterfaceLayerType::InterfaceAsBase);
if (!organic_tree)
extrude_interface(bottom_contact_layer, bottom_interfaces ? InterfaceLayerType::BottomContact : InterfaceLayerType::InterfaceAsBase);

View File

@@ -1226,7 +1226,7 @@ namespace SupportMaterialInternal {
// Surface supporting this layer, expanded by 0.5 * nozzle_diameter, as we consider this kind of overhang to be sufficiently supported.
Polygons lower_grown_slices = expand(lower_layer_polygons,
//FIXME to mimic the decision in the perimeter generator, we should use half the external perimeter width.
0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm.region().config().wall_filament-1))),
0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm.region().config().outer_wall_filament_id-1))),
SUPPORT_SURFACES_OFFSET_PARAMETERS);
// Collect perimeters of this layer.
//FIXME split_at_first_point() could split a bridge mid-way

View File

@@ -6,6 +6,14 @@
#include "../Flow.hpp"
namespace Slic3r {
inline int number_of_support_interface_bottom_layers(const PrintObjectConfig& object_config)
{
return object_config.support_interface_bottom_layers.value < 0 ?
object_config.support_interface_top_layers.value :
object_config.support_interface_bottom_layers.value;
}
struct SupportParameters {
SupportParameters() = delete;
SupportParameters(const PrintObject& object)
@@ -26,8 +34,7 @@ struct SupportParameters {
{
this->num_top_interface_layers = std::max(0, object_config.support_interface_top_layers.value);
this->num_bottom_interface_layers = object_config.support_interface_bottom_layers < 0 ?
num_top_interface_layers : object_config.support_interface_bottom_layers;
this->num_bottom_interface_layers = number_of_support_interface_bottom_layers(object_config);
this->has_top_contacts = num_top_interface_layers > 0;
this->has_bottom_contacts = num_bottom_interface_layers > 0;
// BBS: if support interface and support base do not use the same filament, add a base layer to improve their adhesion

View File

@@ -1332,7 +1332,12 @@ static void make_perimeter_and_infill(ExtrusionEntitiesPtr& dst, const ExPolygon
dst = std::move(loops_entities);
}
}
dst.erase(std::remove_if(dst.begin(), dst.end(), [](ExtrusionEntity *entity) { return static_cast<ExtrusionEntityCollection *>(entity)->empty(); }), dst.end());
// Orca: Some entities are direct paths, so check the type before testing for an empty collection.
dst.erase(std::remove_if(dst.begin(), dst.end(), [](ExtrusionEntity *entity) {
return entity != nullptr && entity->is_collection() && static_cast<ExtrusionEntityCollection *>(entity)->empty();
}), dst.end());
if (infill_first) {
// sort regions to reduce travel
Points ordering_points;
@@ -1610,80 +1615,71 @@ void TreeSupport::generate_toolpaths()
filler_support->angle = Geometry::deg2rad(object_config.support_angle.value);
Polygons loops = to_polygons(poly);
//ORCA: Group base per area as no_sort to keep outline->fill together.
std::unique_ptr<ExtrusionEntityCollection> base_eec = std::make_unique<ExtrusionEntityCollection>();
base_eec->no_sort = true;
ExtrusionEntitiesPtr &base_dst = base_eec->entities;
if (layer_id == 0) {
float density = float(m_object_config->raft_first_layer_density.value * 0.01);
fill_expolygons_with_sheath_generate_paths(ts_layer->support_fills.entities, loops, filler_support.get(), density, erSupportMaterial, flow,
fill_expolygons_with_sheath_generate_paths(base_dst, loops, filler_support.get(), density, erSupportMaterial, flow,
m_support_params, true, false);
}
else {
//ORCA: Force base walls before infill to keep outline->fill order.
if (need_infill && m_support_params.base_fill_pattern != ipLightning) {
// allow infill-only mode if support is thick enough (so min_wall_count is 0);
// otherwise must draw 1 wall
// Don't need extra walls if we have infill. Extra walls may overlap with the infills.
size_t min_wall_count = offset(poly, -scale_(support_spacing * 1.5)).empty() ? 1 : 0;
make_perimeter_and_infill(ts_layer->support_fills.entities, poly, std::max(min_wall_count, wall_count), flow,
erSupportMaterial, filler_support.get(), support_density);
make_perimeter_and_infill(base_dst, poly, std::max(min_wall_count, wall_count), flow,
erSupportMaterial, filler_support.get(), support_density, false);
}
else {
SupportParameters support_params = m_support_params;
if (area_group.need_extra_wall && object_config.tree_support_wall_count.value == 0)
support_params.tree_branch_diameter_double_wall_area_scaled = 0.1;
tree_supports_generate_paths(ts_layer->support_fills.entities, loops, flow, support_params);
tree_supports_generate_paths(base_dst, loops, flow, support_params);
}
}
}
}
if (m_support_params.base_fill_pattern == ipLightning)
{
double print_z = ts_layer->print_z;
if (printZ_to_lightninglayer.find(print_z) == printZ_to_lightninglayer.end())
continue;
//TODO:
//1.the second parameter of convertToLines seems to decide how long the lightning should be trimmed from its root, so that the root wont overlap/detach the support contour.
// whether current value works correctly remained to be tested
//2.related to previous one, that lightning roots need to be trimed more when support has multiple walls
//3.function connect_infill() and variable 'params' helps create connection pattern along contours between two lightning roots,
// strengthen lightnings while it may make support harder. decide to enable it or not. if yes, proper values for params are remained to be tested
auto& lightning_layer = generator->getTreesForLayer(printZ_to_lightninglayer[print_z]);
Flow flow = (layer_id == 0 && m_raft_layers == 0) ? m_support_params.first_layer_flow : support_flow;
ExPolygons areas = offset_ex(ts_layer->base_areas, -flow.scaled_spacing());
for (auto& area : areas)
{
Polylines polylines = lightning_layer.convertToLines(to_polygons(area), 0);
for (auto itr = polylines.begin(); itr != polylines.end();)
{
if (itr->length() < scale_(1.0))
itr = polylines.erase(itr);
else
itr++;
}
Polylines opt_polylines;
#if 1
//this wont create connection patterns along contours
append(opt_polylines, chain_polylines(std::move(polylines)));
#else
//this will create connection patterns along contours
FillParams params;
params.anchor_length = float(Fill::infill_anchor * 0.01 * flow.spacing());
params.anchor_length_max = Fill::infill_anchor_max;
params.anchor_length = std::min(params.anchor_length, params.anchor_length_max);
Fill::connect_infill(std::move(polylines), area, opt_polylines, flow.spacing(), params);
#endif
extrusion_entities_append_paths(ts_layer->support_fills.entities, opt_polylines, erSupportMaterial,
float(flow.mm3_per_mm()), float(flow.width()), float(flow.height()));
//ORCA: Emit lightning infill per base area to avoid interleaving across islands.
if (m_support_params.base_fill_pattern == ipLightning) {
double print_z = ts_layer->print_z;
auto lightning_layer_mapping = printZ_to_lightninglayer.find(print_z);
if (lightning_layer_mapping != printZ_to_lightninglayer.end()) {
auto &lightning_layer = generator->getTreesForLayer(lightning_layer_mapping->second);
ExPolygons areas;
areas.emplace_back(poly);
areas = offset_ex(areas, -flow.scaled_spacing());
for (auto &area : areas) {
Polylines polylines = lightning_layer.convertToLines(to_polygons(area), 0);
for (auto itr = polylines.begin(); itr != polylines.end();) {
if (itr->length() < scale_(1.0))
itr = polylines.erase(itr);
else
itr++;
}
Polylines opt_polylines;
append(opt_polylines, chain_polylines(std::move(polylines)));
extrusion_entities_append_paths(base_dst, opt_polylines, erSupportMaterial,
float(flow.mm3_per_mm()), float(flow.width()), float(flow.height()));
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
std::string name = debug_out_path("trees_polyline_%.2f.svg", ts_layer->print_z);
BoundingBox bbox = get_extents(ts_layer->base_areas);
SVG svg(name, bbox);
if (svg.is_opened()) {
svg.draw(ts_layer->base_areas, "blue");
svg.draw(generator->Overhangs()[printZ_to_lightninglayer[print_z]], "red");
for (auto &line : opt_polylines) svg.draw(line, "yellow");
}
std::string name = debug_out_path("trees_polyline_%.2f.svg", ts_layer->print_z);
BoundingBox bbox = get_extents(ts_layer->base_areas);
SVG svg(name, bbox);
if (svg.is_opened()) {
svg.draw(ts_layer->base_areas, "blue");
svg.draw(generator->Overhangs()[lightning_layer_mapping->second], "red");
for (auto &line : opt_polylines) svg.draw(line, "yellow");
}
#endif
}
}
}
//ORCA: Keep per-area base paths grouped for outline->fill preservation.
if (!base_eec->empty())
ts_layer->support_fills.entities.push_back(base_eec.release());
}
}
@@ -1696,13 +1692,6 @@ void TreeSupport::generate_toolpaths()
);
}
void deleteDirectoryContents(const std::filesystem::path& dir)
{
for (const auto& entry : std::filesystem::directory_iterator(dir))
std::filesystem::remove_all(entry.path());
}
void TreeSupport::move_bounds_to_contact_nodes(std::vector<TreeSupport3D::SupportElements> &move_bounds,
PrintObject &print_object,
const TreeSupport3D::TreeSupportSettings &config)
@@ -2024,8 +2013,8 @@ void TreeSupport::draw_circles()
// generate areas
const coordf_t layer_height = config.layer_height.value;
const size_t top_interface_layers = config.support_interface_top_layers.value;
const size_t bottom_interface_layers = config.support_interface_bottom_layers.value < 0 ? top_interface_layers : config.support_interface_bottom_layers.value;
const size_t top_interface_layers = m_support_params.num_top_interface_layers;
const size_t bottom_interface_layers = number_of_support_interface_bottom_layers(config);
const double nozzle_diameter = m_object->print()->config().nozzle_diameter.get_at(0);
const coordf_t line_width = config.get_abs_value("support_line_width", nozzle_diameter);
const coordf_t line_width_scaled = scale_(line_width);
@@ -2155,13 +2144,9 @@ void TreeSupport::draw_circles()
if (!area.empty()) has_circle_node = true;
if (node.need_extra_wall) need_extra_wall = true;
// Merge the overhang into the roof area so tree tips can still produce
// a continuous support interface. Suppressing this for build-plate-only
// support drops the roof polygons entirely in valid tree branches.
// ORCA: Only keep top interface polygons that fully fit in the mm height cap.
if (top_interface_layers > 0 && node.support_roof_layers_below > 0 &&
(node.dist_mm_to_top - this->top_z_distance) < top_interface_height + EPSILON &&
!node.is_sharp_tail) {
// merge overhang to get a smoother interface surface
// Do not merge when buildplate_only is on, because some underneath nodes may have been deleted.
if (top_interface_layers > 0 && node.support_roof_layers_below > 0 && !on_buildplate_only && !node.is_sharp_tail) {
ExPolygons overhang_expanded;
if (node.overhang.contour.size() > 100 || node.overhang.holes.size()>1)
overhang_expanded.emplace_back(node.overhang);
@@ -2207,16 +2192,6 @@ void TreeSupport::draw_circles()
roof_1st_layer = diff_ex(roof_1st_layer, ClipperUtils::clip_clipper_polygons_with_subject_bbox(roof_areas,get_extents(roof_1st_layer)));
roof_1st_layer = intersection_ex(roof_1st_layer, m_machine_border);
// Build-plate-only pruning can collapse the roof stack down to a single
// printable layer. In that case we still need to emit an interface layer
// instead of downgrading the last roof-adjacent layer to base support.
if (on_buildplate_only && top_interface_layers > 0 && roof_areas.empty() && !roof_1st_layer.empty()) {
append(roof_areas, roof_1st_layer);
roof_1st_layer.clear();
max_layers_above_roof = std::max(max_layers_above_roof, max_layers_above_roof1);
max_layers_above_roof1 = 0;
}
ExPolygons roofs; append(roofs, roof_1st_layer); append(roofs, roof_areas);append(roofs, roof_gap_areas);
base_areas = diff_ex(base_areas, ClipperUtils::clip_clipper_polygons_with_subject_bbox(roofs, get_extents(base_areas)));
base_areas = intersection_ex(base_areas, m_machine_border);
@@ -2374,6 +2349,15 @@ void TreeSupport::draw_circles()
ts_layer->base_areas = std::move(expanded_base_areas);
}
// Orca: Final tree base polygons may be too close above model surfaces.
// Enforce bottom Z clearance for non-contact support layers as well.
if (!ts_layer->base_areas.empty()) {
const Polygons trimming = get_trim_support_regions(
*m_object, ts_layer, 0., m_slicing_params.gap_object_support, 0);
if (!trimming.empty())
ts_layer->base_areas = diff_ex(ts_layer->base_areas, trimming);
}
auto &area_groups = ts_layer->area_groups;
for (auto& expoly : ts_layer->base_areas) {
@@ -2678,8 +2662,7 @@ void TreeSupport::drop_nodes()
const size_t tip_layers = base_radius / layer_height; //The number of layers to be shrinking the circle to create a tip. This produces a 45 degree angle.
const coordf_t radius_sample_resolution = m_ts_data->m_radius_sample_resolution;
const bool support_on_buildplate_only = config.support_on_build_plate_only.value;
const size_t top_interface_layers = config.support_interface_top_layers.value;
const size_t bottom_interface_layers = config.support_interface_bottom_layers.value < 0 ? top_interface_layers : config.support_interface_bottom_layers.value;
const size_t bottom_interface_layers = number_of_support_interface_bottom_layers(config);
SupportNode::diameter_angle_scale_factor = diameter_angle_scale_factor;
float DO_NOT_MOVER_UNDER_MM = is_slim ? 0 : 5; // do not move contact points under 5mm
@@ -3576,7 +3559,14 @@ void TreeSupport::generate_contact_points()
}
// add supports along contours
libnest2d::placers::EdgeCache<ExPolygon> edge_cache(overhang);
ExPolygon closed_overhang = overhang; // make a copy to add closing point for edge cache
if (closed_overhang.contour.points.size() > 1)
closed_overhang.contour.points.emplace_back(closed_overhang.contour.points.front());
for (Polygon &hole : closed_overhang.holes)
if (hole.points.size() > 1)
hole.points.emplace_back(hole.points.front());
libnest2d::placers::EdgeCache<ExPolygon> edge_cache(closed_overhang);
for (size_t i = 0; i < edge_cache.holeCount() + 1; i++) {
double step = point_spread / (i == 0 ? edge_cache.circumference() : edge_cache.circumference(i - 1));
double distance = 0;

View File

@@ -124,6 +124,9 @@ static std::vector<std::pair<TreeSupportSettings, std::vector<size_t>>> group_me
{
std::vector<std::pair<TreeSupportSettings, std::vector<size_t>>> grouped_meshes;
// Orca: Recompute static mesh-group state for this support generation pass.
TreeSupportSettings::zero_top_z_gap = false;
//FIXME this is ugly, it does not belong here.
for (size_t object_id : print_object_ids) {
const PrintObject &print_object = *print.get_object(object_id);
@@ -1194,7 +1197,7 @@ void sample_overhang_area(
point_count += poly.size();
const size_t min_support_points = std::max(coord_t(1), std::min(coord_t(3), coord_t(total_length(overhang_area) / connect_length)));
if (point_count <= min_support_points) {
// add the outer wall (of the overhang) to ensure it is correct supported instead. Try placing the support points in a way that they fully support the outer wall, instead of just the with half of the the support line width.
// add the outer wall (of the overhang) to ensure it is correct supported instead. Try placing the support points in a way that they fully support the outer wall, instead of just the with half of the support line width.
// I assume that even small overhangs are over one line width wide, so lets try to place the support points in a way that the full support area generated from them
// will support the overhang (if this is not done it may only be half). This WILL NOT be the case when supporting an angle of about < 60 degrees so there is a fallback,
// as some support is better than none.
@@ -1375,7 +1378,7 @@ static void generate_initial_areas(
}
#if 0
// If the xy distance overrides the z distance, some support needs to be inserted further down.
//=> Analyze which support points do not fit on this layer and check if they will fit a few layers down (while adding them an infinite amount of layers down would technically be closer the the setting description, it would not produce reasonable results. )
//=> Analyze which support points do not fit on this layer and check if they will fit a few layers down (while adding them an infinite amount of layers down would technically be closer the setting description, it would not produce reasonable results. )
if (! min_xy_dist) {
LineInformations overhang_lines;
{
@@ -1600,13 +1603,19 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di
if (settings.increase_radius)
current_elem.effective_radius_height += 1;
coord_t radius = support_element_collision_radius(config, current_elem);
const auto _tiny_area_threshold = tiny_area_threshold();
if (settings.move) {
increased = relevant_offset;
if (overspeed > 0) {
const coord_t safe_movement_distance =
coord_t safe_movement_distance =
(current_elem.use_min_xy_dist ? config.xy_min_distance : config.xy_distance) +
(std::min(config.z_distance_top_layers, config.z_distance_bottom_layers) > 0 ? config.min_feature_size : 0);
// Orca:
// safe_movement_distance is used as the safe_offset_inc() step, so keep it non-zero
// to preserve branch movement with zero-clearance support settings.
if (safe_movement_distance == 0)
safe_movement_distance = scaled<coord_t>(0.1);
// The difference to ensure that the result not only conforms to wall_restriction, but collision/avoidance is done later.
// The higher last_safe_step_movement_distance comes exactly from the fact that the collision will be subtracted later.
increased = safe_offset_inc(increased, overspeed, volumes.getWallRestriction(support_element_collision_radius(config, parent.state), layer_idx, parent.state.use_min_xy_dist),
@@ -1817,9 +1826,15 @@ static void increase_areas_one_layer(
* layer z-1:dddddxxxxxxxxxx
* For more detailed visualisation see calculateWallRestrictions
*/
const coord_t safe_movement_distance =
coord_t safe_movement_distance =
(elem.use_min_xy_dist ? config.xy_min_distance : config.xy_distance) +
(std::min(config.z_distance_top_layers, config.z_distance_bottom_layers) > 0 ? config.min_feature_size : 0);
// safe_movement_distance is used as a divisor and as the safe_offset_inc() step,
// so keep it non-zero to avoid division by zero and preserve branch movement.
if (safe_movement_distance == 0)
safe_movement_distance = scaled<coord_t>(0.1);
if (ceiled_parent_radius == volumes.ceilRadius(projected_radius_increased, parent.state.use_min_xy_dist) ||
projected_radius_increased < config.increase_radius_until_radius)
// If it is guaranteed possible to increase the radius, the maximum movement speed can be increased, as it is assumed that the maximum movement speed is the one of the slower moving wall
@@ -2902,6 +2917,7 @@ static std::pair<float, float> extrude_branch(
const TreeSupportSettings &config,
const SlicingParameters &slicing_params,
const std::vector<SupportElements> &move_bounds,
bool has_root,
indexed_triangle_set &result)
{
Vec3d p1, p2, p3;
@@ -2923,24 +2939,38 @@ static std::pair<float, float> extrude_branch(
v1 = (p2 - p1).normalized();
if (ipath == 1) {
nprev = v1;
// Extrude the bottom half sphere.
float radius = unscaled<float>(support_element_radius(config, prev));
float angle_step = 2. * acos(1. - eps / radius);
auto nsteps = int(ceil(M_PI / (2. * angle_step)));
angle_step = M_PI / (2. * nsteps);
int ifan = int(result.vertices.size());
result.vertices.emplace_back((p1 - nprev * radius).cast<float>());
zmin = result.vertices.back().z();
float angle = angle_step;
for (int i = 1; i < nsteps; ++ i, angle += angle_step) {
std::pair<int, int> strip = discretize_circle((p1 - nprev * radius * cos(angle)).cast<float>(), nprev.cast<float>(), radius * sin(angle), eps, result.vertices);
if (i == 1)
triangulate_fan<false>(result, ifan, strip.first, strip.second);
else
triangulate_strip(result, prev_strip.first, prev_strip.second, strip.first, strip.second);
// sprintf(fname, "d:\\temp\\meshes\\tree-partial-%d.obj", ++ irun);
// its_write_obj(result, fname);
prev_strip = strip;
if (has_root && prev.state.layer_idx == 0) {
// Orca: Buildplate roots need a flat foot. A rounded cap can extend far
// below the bed and make the first layer slice cut unrelated trunk geometry.
const Vec3f normal(0.f, 0.f, 1.f);
const Vec3f bottom_center(float(p1.x()), float(p1.y()), 0.f);
const Vec3f top_center(float(p1.x()), float(p1.y()), float(p1.z()));
int ifan = int(result.vertices.size());
result.vertices.emplace_back(bottom_center);
std::pair<int, int> bottom_strip = discretize_circle(bottom_center, normal, radius, eps, result.vertices);
triangulate_fan<false>(result, ifan, bottom_strip.first, bottom_strip.second);
prev_strip = discretize_circle(top_center, normal, radius, eps, result.vertices);
triangulate_strip(result, bottom_strip.first, bottom_strip.second, prev_strip.first, prev_strip.second);
zmin = 0.f;
} else {
// Extrude the bottom half sphere.
float angle_step = 2. * acos(1. - eps / radius);
auto nsteps = int(ceil(M_PI / (2. * angle_step)));
angle_step = M_PI / (2. * nsteps);
int ifan = int(result.vertices.size());
result.vertices.emplace_back((p1 - nprev * radius).cast<float>());
zmin = result.vertices.back().z();
float angle = angle_step;
for (int i = 1; i < nsteps; ++ i, angle += angle_step) {
std::pair<int, int> strip = discretize_circle((p1 - nprev * radius * cos(angle)).cast<float>(), nprev.cast<float>(), radius * sin(angle), eps, result.vertices);
if (i == 1)
triangulate_fan<false>(result, ifan, strip.first, strip.second);
else
triangulate_strip(result, prev_strip.first, prev_strip.second, strip.first, strip.second);
prev_strip = strip;
}
}
}
if (ipath + 1 == path.size()) {
@@ -3123,13 +3153,60 @@ static void organic_smooth_branches_avoid_collisions(
static constexpr const double max_nudge_collision_avoidance = 0.5;
static constexpr const double max_nudge_smoothing = 0.2;
static constexpr const size_t num_iter = 100; // 1000;
// Orca:
// Collision and Laplacian smoothing run iteratively; keep each candidate reachable from linked upper/lower layers to avoid accumulated drift.
auto limit_candidate_to_linked_layers = [&collision_spheres, &linear_data_layers, &config](const size_t collision_sphere_id, Vec2d candidate) {
auto constrain_to_anchor = [](Vec2d candidate, const Vec2d &current_pos, const Vec2d &anchor, double allowed_shift) {
const Vec2d delta = candidate - anchor;
const double candidate_dist = delta.norm();
const double current_dist = (current_pos - anchor).norm();
allowed_shift = std::max(allowed_shift, current_dist);
return candidate_dist > allowed_shift && candidate_dist > EPSILON ?
anchor + delta * (allowed_shift / candidate_dist) :
candidate;
};
const CollisionSphere &sphere = collision_spheres[collision_sphere_id];
const LayerIndex layer_idx = sphere.element.state.layer_idx;
const Vec2d current_pos = to_2d(sphere.position).cast<double>();
const double current_radius = double(support_element_radius(config, sphere.element));
const double maximum_move_distance_slow = double(config.maximum_move_distance_slow);
if (sphere.element_below_id != -1 && layer_idx > 0) {
const size_t lower_id = linear_data_layers[layer_idx - 1] + size_t(sphere.element_below_id);
if (lower_id < collision_spheres.size()) {
const CollisionSphere &lower = collision_spheres[lower_id];
const double lower_radius = double(support_element_radius(config, lower.element));
const double allowed_shift = unscaled<double>(std::max(0., lower_radius - current_radius) + maximum_move_distance_slow);
candidate = constrain_to_anchor(candidate, current_pos, to_2d(lower.prev_position).cast<double>(), allowed_shift);
}
}
const LayerIndex upper_layer_idx = layer_idx + 1;
if (!sphere.element.parents.empty() && upper_layer_idx < LayerIndex(linear_data_layers.size())) {
const size_t upper_offset = linear_data_layers[upper_layer_idx];
for (int32_t parent_idx : sphere.element.parents) {
const size_t upper_id = upper_offset + size_t(parent_idx);
if (upper_id >= collision_spheres.size())
continue;
const CollisionSphere &upper = collision_spheres[upper_id];
const double upper_radius = double(support_element_radius(config, upper.element));
const double allowed_shift = unscaled<double>(std::max(0., current_radius - upper_radius) + maximum_move_distance_slow);
candidate = constrain_to_anchor(candidate, current_pos, to_2d(upper.prev_position).cast<double>(), allowed_shift);
}
}
return candidate;
};
for (size_t iter = 0; iter < num_iter; ++ iter) {
// Back up prev position before Laplacian smoothing.
for (CollisionSphere &collision_sphere : collision_spheres)
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, &config, &linear_data_layers, &num_moved, &throw_on_cancel](const tbb::blocked_range<size_t> range) {
[&collision_spheres, &layer_collision_cache, &slicing_params, &config, &linear_data_layers, &num_moved, &throw_on_cancel, &limit_candidate_to_linked_layers](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.
@@ -3158,10 +3235,12 @@ static void organic_smooth_branches_avoid_collisions(
if (collision_sphere.last_collision_depth > EPSILON)
// a little bit of hysteresis to detect end of
++ num_moved;
// Shift by maximum 2mm.
// Limit collision-avoidance nudge per iteration.
double nudge_dist = std::min(std::max(0., collision_sphere.last_collision_depth + collision_extra_gap), max_nudge_collision_avoidance);
Vec2d nudge_vector = (to_2d(collision_sphere.position) - to_2d(collision_sphere.last_collision)).cast<double>().normalized() * nudge_dist;
collision_sphere.position.head<2>() += (nudge_vector * nudge_dist).cast<float>();
Vec2d candidate = to_2d(collision_sphere.position).cast<double>() + nudge_vector * nudge_dist;
candidate = limit_candidate_to_linked_layers(collision_sphere_id, candidate);
collision_sphere.position.head<2>() = candidate.cast<float>();
}
// Laplacian smoothing
Vec2d avg{ 0, 0 };
@@ -3185,9 +3264,13 @@ static void organic_smooth_branches_avoid_collisions(
Vec2d new_pos = (1. - smoothing_factor) * old_pos + smoothing_factor * avg;
Vec2d shift = new_pos - old_pos;
double nudge_dist_max = shift.norm();
// Shift by maximum 1mm, less than the collision avoidance factor.
// Limit Laplacian smoothing nudge per iteration.
double nudge_dist = std::min(std::max(0., nudge_dist_max), max_nudge_smoothing);
collision_sphere.position.head<2>() += (shift.normalized() * nudge_dist).cast<float>();
if (nudge_dist > 0.) {
Vec2d candidate = old_pos + shift * (nudge_dist / nudge_dist_max);
candidate = limit_candidate_to_linked_layers(collision_sphere_id, candidate);
collision_sphere.position.head<2>() = candidate.cast<float>();
}
throw_on_cancel();
}
@@ -3454,6 +3537,7 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons
// value is the area where support may be placed. As this is calculated in CreateLayerPathing it is saved and reused in draw_areas
std::vector<SupportElements> move_bounds(num_support_layers);
// ### Place tips of the support tree
for (size_t mesh_idx : processing.second)
generate_initial_areas(*print.get_object(mesh_idx), volumes, config, overhangs,
@@ -3764,6 +3848,7 @@ void organic_draw_branches(
// ++ ielement;
}
}
const SlicingParameters &slicing_params = print_object.slicing_parameters();
MeshSlicingParams mesh_slicing_params;
mesh_slicing_params.mode = MeshSlicingParams::SlicingMode::Positive;
@@ -3778,7 +3863,7 @@ void organic_draw_branches(
for (const Branch &branch : tree.branches) {
// Triangulate the tube.
partial_mesh.clear();
std::pair<float, float> zspan = extrude_branch(branch.path, config, slicing_params, move_bounds, partial_mesh);
std::pair<float, float> zspan = extrude_branch(branch.path, config, slicing_params, move_bounds, branch.has_root, partial_mesh);
LayerIndex layer_begin = branch.has_root ?
branch.path.front()->state.layer_idx :
std::min(branch.path.front()->state.layer_idx, layer_idx_ceil(slicing_params, config, zspan.first));
@@ -3945,19 +4030,7 @@ void organic_draw_branches(
}
// ORCA: bottom contacts provide the footprint; interface layers are built later.
#if 0
//FIXME branch.has_tip seems to not be reliable.
if (branch.has_tip && interface_placer.support_parameters.has_top_contacts)
// Add top slices to top contacts / interfaces / base interfaces.
for (int i = int(branch.path.size()) - 1; i >= 0; -- i) {
const SupportElement &el = *branch.path[i];
if (el.state.missing_roof_layers == 0)
break;
//FIXME Move or not?
interface_placer.add_roof(std::move(slices[int(slices.size()) - i - 1]), el.state.layer_idx,
interface_placer.support_parameters.num_top_interface_layers + 1 - el.state.missing_roof_layers);
}
#endif
recover_pending_branch_roofs(interface_placer, branch.path, layer_begin, slices);
while (! slices.empty() && slices.back().empty()) {
slices.pop_back();

View File

@@ -13,6 +13,7 @@
#include "../Polygon.hpp"
#include "SupportCommon.hpp"
#include <algorithm>
#include <string_view>
namespace Slic3r
@@ -60,12 +61,10 @@ struct TreeSupportMeshGroupSettings {
this->support_angle = 0.5 * M_PI - std::clamp<double>((config.support_threshold_angle + 1) * M_PI / 180., 0., 0.5 * M_PI);
this->support_line_width = support_material_flow(&print_object, config.layer_height).scaled_width();
this->support_roof_line_width = support_material_interface_flow(&print_object, config.layer_height).scaled_width();
//FIXME add it to SlicingParameters and reuse in both tree and normal supports?
this->support_bottom_enable = config.support_interface_top_layers.value > 0 && config.support_interface_bottom_layers.value != 0;
const int bottom_interface_layers = number_of_support_interface_bottom_layers(config);
this->support_bottom_enable = config.support_interface_top_layers.value > 0 && bottom_interface_layers > 0;
this->support_bottom_height = this->support_bottom_enable ?
(config.support_interface_bottom_layers.value > 0 ?
config.support_interface_bottom_layers.value :
config.support_interface_top_layers.value) * this->layer_height :
bottom_interface_layers * this->layer_height :
0;
this->support_material_buildplate_only = config.support_on_build_plate_only;
this->support_xy_distance = scaled<coord_t>(config.support_object_xy_distance.value);
@@ -76,8 +75,8 @@ struct TreeSupportMeshGroupSettings {
this->support_bottom_distance = scaled<coord_t>(slicing_params.gap_object_support);
this->support_roof_enable = config.support_interface_top_layers.value > 0;
this->support_roof_layers = config.support_interface_top_layers.value;
this->support_floor_enable = config.support_interface_bottom_layers.value > 0;
this->support_floor_layers = config.support_interface_bottom_layers.value;
this->support_floor_enable = bottom_interface_layers > 0;
this->support_floor_layers = bottom_interface_layers;
this->support_roof_pattern = config.support_interface_pattern;
this->support_pattern = config.support_base_pattern;
this->support_line_spacing = scaled<coord_t>(config.support_base_pattern_spacing.value);
@@ -614,19 +613,35 @@ inline double layer_z(const SlicingParameters &slicing_params, const TreeSupport
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];
}
inline double first_object_support_layer_z(const SlicingParameters &slicing_params)
{
return slicing_params.object_print_z_min + slicing_params.first_object_layer_height;
}
// Orca: Reverse layer_z() for support layers below the first object layer.
// config.raft_layers may include raft/contact/intermediate support Zs, so do not collapse them to the first object support layer.
// Lowest collision layer
inline LayerIndex layer_idx_ceil(const SlicingParameters &slicing_params, const TreeSupportSettings &config, const double z)
{
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));
const double first_object_z = first_object_support_layer_z(slicing_params);
if (!config.raft_layers.empty() && z < first_object_z - EPSILON) {
auto it = std::lower_bound(config.raft_layers.begin(), config.raft_layers.end(), z - EPSILON);
return LayerIndex(it == config.raft_layers.end() ? config.raft_layers.size() : std::distance(config.raft_layers.begin(), it));
}
return LayerIndex(config.raft_layers.size()) +
std::max<LayerIndex>(0, LayerIndex(std::ceil((z - first_object_z) / slicing_params.layer_height)));
}
// Highest collision layer
inline LayerIndex layer_idx_floor(const SlicingParameters &slicing_params, const TreeSupportSettings &config, const double z)
{
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));
const double first_object_z = first_object_support_layer_z(slicing_params);
if (!config.raft_layers.empty() && z < first_object_z - EPSILON) {
auto it = std::upper_bound(config.raft_layers.begin(), config.raft_layers.end(), z + EPSILON);
return LayerIndex(it == config.raft_layers.begin() ? 0 : std::distance(config.raft_layers.begin(), it) - 1);
}
return LayerIndex(config.raft_layers.size()) +
std::max<LayerIndex>(0, LayerIndex(std::floor((z - first_object_z) / slicing_params.layer_height)));
}
inline SupportGeneratorLayer& layer_initialize(