Merge upstream/main into belt/rebase/may-18

Reconciles the belt-printer branch with upstream PRs through #13723. Six
files had conflicts; three additional files needed manual follow-up fixes
where the auto-merge produced code that referenced upstream-renamed fields
or changed function signatures.

Notable reconciliations:
- TreeSupport.cpp: kept belt-floor early-exit branches around HEAD's
  drop-down logic, folded upstream's `(distance_to_top > 0 ? 1 : 0)`
  formula into the non-belt-floor path (upstream PR #11812). Dropped dead
  `roof_enabled`/`force_tip_to_roof` locals.
- TreeSupport3D.cpp: combined upstream's safety-offset + remove_small
  changes with HEAD's belt-floor clip in the per-slice trim loop. Dropped
  HEAD's `else` block (superseded by upstream's rewritten bottom-contact
  propagation) and re-added the belt-floor clip into the new propagation
  loop. Gated the propagation on belt printers to prevent OOM when
  belt-floor clipping produces empty initial slices.
- TriangleSelector.{cpp,hpp}: merged both new `select_patch` parameters
  (HEAD's `up_direction` and upstream's `select_partially`); body uses
  `dot(up_direction)` for the overhang angle check and forwards
  `select_partially` to `select_triangle`.
- SupportMaterial.cpp: `slicing_params.soluble_interface` →
  `zero_gap_interface_bottom` in HEAD's `detect_belt_floor_bottom_contacts`,
  matching upstream's same-purpose rename at line 2495.
- Custom.json, GCodeWriter.cpp: simple additive merges (kept entries /
  includes from both sides).

Verified by building OrcaSlicer (RelWithDebInfo) after a full deps
rebuild (Eigen v5.0.1, libigl v2.6.0 are now managed deps) and slicing
a scaled Benchy on the NORMALIZER belt-printer profile without OOM.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
harrierpigeon
2026-05-18 21:53:24 -05:00
2143 changed files with 130163 additions and 174147 deletions

View File

@@ -26,6 +26,7 @@
#include <tbb/parallel_for_each.h>
#include <boost/log/trivial.hpp>
#include <algorithm>
#ifndef M_PI
#define M_PI 3.1415926535897932384626433832795
@@ -74,6 +75,32 @@ inline Point normal(Point pt, double scale)
return pt * (scale / length);
}
// ORCA:
// Collect all polygons of a given SurfaceType from all regions of a layer.
// Used for top-contact probing across region/modifier boundaries.
static Polygons collect_region_slices_by_type(const Layer &layer, SurfaceType surface_type)
{
size_t n_polygons_new = 0;
for (const LayerRegion *region : layer.regions()) {
for (const Surface &surface : region->slices.surfaces) {
if (surface.surface_type == surface_type)
n_polygons_new += surface.expolygon.holes.size() + 1;
}
}
Polygons out;
out.reserve(n_polygons_new);
for (const LayerRegion *region : layer.regions()) {
for (const Surface &surface : region->slices.surfaces) {
if (surface.surface_type == surface_type)
polygons_append(out, surface.expolygon);
}
}
return out;
}
enum TreeSupportStage {
STAGE_DETECT_OVERHANGS,
@@ -1441,7 +1468,7 @@ void TreeSupport::generate_toolpaths()
Flow support_flow(support_extrusion_width, ts_layer->height, nozzle_diameter);
Fill* filler_interface = Fill::new_from_type(ipRectilinear);
filler_interface->angle = PI / 2; // interface should be perpendicular to base
filler_interface->angle = M_PI_2; // interface should be perpendicular to base
filler_interface->spacing = support_flow.spacing();
FillParams fill_params;
@@ -1461,7 +1488,7 @@ void TreeSupport::generate_toolpaths()
SupportLayer *ts_layer = m_object->get_support_layer(layer_nr);
Flow support_flow(support_extrusion_width, ts_layer->height, nozzle_diameter);
Fill* filler_raft = Fill::new_from_type(ipRectilinear);
filler_raft->angle = PI / 2;
filler_raft->angle = M_PI_2;
filler_raft->spacing = support_flow.spacing();
for (auto& poly : first_non_raft_base)
make_perimeter_and_infill(ts_layer->support_fills.entities, poly, std::min(size_t(1), wall_count), support_flow, erSupportMaterial, filler_raft, interface_density, false);
@@ -1471,13 +1498,8 @@ void TreeSupport::generate_toolpaths()
return;
BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.)));
std::shared_ptr<Fill> filler_interface = std::shared_ptr<Fill>(Fill::new_from_type(m_support_params.contact_fill_pattern));
std::shared_ptr<Fill> filler_Roof1stLayer = std::shared_ptr<Fill>(Fill::new_from_type(ipRectilinear));
filler_interface->set_bounding_box(bbox_object);
filler_Roof1stLayer->set_bounding_box(bbox_object);
filler_interface->angle = Geometry::deg2rad(object_config.support_angle.value + 90.);
filler_Roof1stLayer->angle = Geometry::deg2rad(object_config.support_angle.value + 90.);
// ORCA: base angle used for explicit interlaced interface orientation.
const float base_support_angle = Geometry::deg2rad(object_config.support_angle.value);
// generate tree support tool paths
tbb::parallel_for(
@@ -1496,17 +1518,28 @@ void TreeSupport::generate_toolpaths()
coordf_t support_spacing = object_config.support_base_pattern_spacing.value + support_flow.spacing();
coordf_t support_density = std::min(1., support_flow.spacing() / support_spacing);
ts_layer->support_fills.no_sort = false;
// ORCA: per-layer Fill instances to avoid shared-state races during interlaced interfaces.
std::shared_ptr<Fill> filler_interface = std::shared_ptr<Fill>(Fill::new_from_type(m_support_params.contact_fill_pattern));
std::shared_ptr<Fill> filler_Roof1stLayer = std::shared_ptr<Fill>(Fill::new_from_type(ipRectilinear));
filler_interface->set_bounding_box(bbox_object);
filler_Roof1stLayer->set_bounding_box(bbox_object);
for (auto& area_group : ts_layer->area_groups) {
ExPolygon& poly = *area_group.area;
ExPolygons polys;
FillParams fill_params;
// ORCA: reset interface Fill state per area group to keep angles deterministic.
filler_interface->fixed_angle = false;
filler_interface->layer_id = size_t(-1);
filler_interface->angle = base_support_angle + M_PI_2; // default interface angle is perpendicular to support angle
if (area_group.type != SupportLayer::BaseType) {
// interface
if (layer_id == 0) {
Flow flow = m_raft_layers == 0 ? m_object->print()->brim_flow() : support_flow;
ExtrusionRole brim_role = (area_group.type == SupportLayer::RoofType && !area_group.interface_as_base) ?
erSupportMaterialInterface : erSupportMaterial;
make_perimeter_and_inner_brim(ts_layer->support_fills.entities, poly, wall_count, flow,
area_group.type == SupportLayer::RoofType ? erSupportMaterialInterface : erSupportMaterial);
brim_role);
polys = std::move(offset_ex(poly, -flow.scaled_spacing()));
} else if (area_group.type == SupportLayer::Roof1stLayer) {
polys = std::move(offset_ex(poly, 0.5*support_flow.scaled_width()));
@@ -1519,12 +1552,18 @@ void TreeSupport::generate_toolpaths()
}
if (area_group.type == SupportLayer::Roof1stLayer) {
// roof_1st_layer
// ORCA: Roof1stLayer may be printed with base material when it acts as a contact layer.
bool interface_as_base = area_group.interface_as_base;
fill_params.density = interface_density;
// Note: spacing means the separation between two lines as if they are tightly extruded
filler_Roof1stLayer->spacing = interface_flow.spacing();
filler_Roof1stLayer->angle = base_support_angle;
fill_params.dont_sort = true;
Flow interface_base_flow = interface_as_base ? support_flow : interface_flow;
ExtrusionRole interface_role = interface_as_base ? erSupportMaterial : erSupportMaterialInterface;
// generate a perimeter first to support interface better
ExtrusionEntityCollection* temp_support_fills = new ExtrusionEntityCollection();
make_perimeter_and_infill(temp_support_fills->entities, poly, 1, interface_flow, erSupportMaterial,
make_perimeter_and_infill(temp_support_fills->entities, poly, 1, interface_base_flow, interface_role,
filler_Roof1stLayer.get(), interface_density, false);
temp_support_fills->no_sort = true; // make sure loops are first
if (!temp_support_fills->entities.empty())
@@ -1533,33 +1572,66 @@ void TreeSupport::generate_toolpaths()
delete temp_support_fills;
} else if (area_group.type == SupportLayer::FloorType) {
// floor_areas
bool interface_as_base = area_group.interface_as_base;
fill_params.density = bottom_interface_density;
filler_interface->spacing = interface_flow.spacing();
fill_expolygons_generate_paths(ts_layer->support_fills.entities, polys,
filler_interface.get(), fill_params, erSupportMaterialInterface, interface_flow);
} else if (area_group.type == SupportLayer::RoofType) {
// roof_areas
fill_params.density = interface_density;
filler_interface->spacing = interface_flow.spacing();
if (m_object_config->support_interface_pattern == smipGrid) {
filler_interface->angle = Geometry::deg2rad(object_config.support_angle.value);
filler_interface->angle = base_support_angle;
fill_params.dont_sort = true;
}
if (m_object_config->support_interface_pattern == smipRectilinearInterlaced)
filler_interface->layer_id = area_group.interface_id;
fill_expolygons_generate_paths(ts_layer->support_fills.entities, polys, filler_interface.get(), fill_params, erSupportMaterialInterface,
interface_flow);
if (m_object_config->support_interface_pattern == smipRectilinearInterlaced) {
// ORCA: explicit 0/90 alternation for rectilinear interlaced interfaces.
filler_interface->fixed_angle = true;
filler_interface->angle = base_support_angle + ((area_group.interface_id & 1) * M_PI_2);
fill_params.dont_sort = true;
}
Flow interface_base_flow = interface_as_base ? support_flow : interface_flow;
ExtrusionRole interface_role = interface_as_base ? erSupportMaterial : erSupportMaterialInterface;
fill_expolygons_generate_paths(ts_layer->support_fills.entities, polys,
filler_interface.get(), fill_params, interface_role, interface_base_flow);
} else if (area_group.type == SupportLayer::RoofType) {
// roof_areas
bool interface_as_base = area_group.interface_as_base;
fill_params.density = interface_density;
filler_interface->spacing = interface_flow.spacing();
if (m_object_config->support_interface_pattern == smipGrid) {
filler_interface->angle = base_support_angle;
fill_params.dont_sort = true;
}
if (m_object_config->support_interface_pattern == smipRectilinearInterlaced) {
// ORCA: explicit 0/90 alternation for rectilinear interlaced interfaces.
filler_interface->fixed_angle = true;
filler_interface->angle = base_support_angle + ((area_group.interface_id & 1) * M_PI_2);
fill_params.dont_sort = true;
}
Flow interface_base_flow = interface_as_base ? support_flow : interface_flow;
ExtrusionRole interface_role = interface_as_base ? erSupportMaterial : erSupportMaterialInterface;
fill_expolygons_generate_paths(ts_layer->support_fills.entities, polys, filler_interface.get(), fill_params, interface_role,
interface_base_flow);
}
else {
// base_areas
Flow flow = (layer_id == 0 && m_raft_layers == 0) ? m_object->print()->brim_flow() : support_flow;
bool support_base_on_bed = (layer_id == 0 && m_raft_layers == 0);
Flow flow = support_base_on_bed ? m_support_params.first_layer_flow : support_flow;
bool need_infill = with_infill;
if(m_object_config->support_base_pattern==smpDefault)
need_infill &= area_group.need_infill;
std::shared_ptr<Fill> filler_support = std::shared_ptr<Fill>(Fill::new_from_type(layer_id == 0 ? ipConcentric : m_support_params.base_fill_pattern));
// Orca: Use rectilinear for support base on the bed
const InfillPattern base_fill_pattern = support_base_on_bed ? ipRectilinear : m_support_params.base_fill_pattern;
std::shared_ptr<Fill> filler_support = std::shared_ptr<Fill>(Fill::new_from_type(base_fill_pattern));
filler_support->set_bounding_box(bbox_object);
filler_support->spacing = support_spacing * support_density; // constant spacing to align support infill lines
filler_support->spacing =
support_base_on_bed ?
flow.spacing() : // Orca: On the bed-contacting support base layer, use first-layer flow spacing directly.
support_spacing * support_density; // constant spacing to align support infill lines
filler_support->angle = Geometry::deg2rad(object_config.support_angle.value);
Polygons loops = to_polygons(poly);
@@ -1599,7 +1671,7 @@ void TreeSupport::generate_toolpaths()
// 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_object->print()->brim_flow() :support_flow;
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)
@@ -1991,7 +2063,7 @@ Polygons TreeSupport::get_trim_support_regions(
polygons_append(polygons_trimming, offset({ expoly }, trimming_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS));
}
}
if (!m_slicing_params.soluble_interface && m_object_config->thick_bridges) {
if (!m_slicing_params.zero_gap_interface_top && m_object_config->thick_bridges) {
// Collect all bottom surfaces, which will be extruded with a bridging flow.
for (; i < object.layers().size(); ++i) {
const Layer& object_layer = *object.layers()[i];
@@ -2020,7 +2092,7 @@ void TreeSupport::draw_circles()
const PrintObjectConfig &config = m_object->config();
const Print* print = m_object->print();
bool has_brim = print->has_brim();
int bottom_gap_layers = round(m_slicing_params.gap_object_support / m_slicing_params.layer_height);
const coordf_t bottom_gap_height = m_slicing_params.gap_object_support;
const coordf_t branch_radius = config.tree_support_branch_diameter.value / 2;
const coordf_t branch_radius_scaled = scale_(branch_radius);
bool on_buildplate_only = m_object_config->support_on_build_plate_only.value;
@@ -2036,7 +2108,7 @@ void TreeSupport::draw_circles()
{
double angle;
if (SQUARE_SUPPORT)
angle = (double) i / CIRCLE_RESOLUTION * TAU + PI / 4.0 + nodes_angle;
angle = (double) i / CIRCLE_RESOLUTION * TAU + M_PI_4 + nodes_angle;
else
angle = (double) i / CIRCLE_RESOLUTION * TAU;
branch_circle.append(Point(cos(angle) * branch_radius_scaled, sin(angle) * branch_radius_scaled));
@@ -2100,7 +2172,7 @@ void TreeSupport::draw_circles()
coordf_t max_layers_above_base = 0;
coordf_t max_layers_above_roof = 0;
coordf_t max_layers_above_roof1 = 0;
int interface_id = 0;
bool floor_interface_as_base = false;
bool has_circle_node = false;
bool need_extra_wall = false;
ExPolygons collision_sharp_tails;
@@ -2134,6 +2206,8 @@ void TreeSupport::draw_circles()
break;
const SupportNode& node = *p_node;
// ORCA: Cap top interface height in mm based on per-node support layer height.
const coordf_t top_interface_height = coordf_t(top_interface_layers) * node.height;
ExPolygons area;
// Generate directly from overhang polygon if one of the following is true:
// 1) node is a normal part of hybrid support
@@ -2185,7 +2259,10 @@ void TreeSupport::draw_circles()
// 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.
if (top_interface_layers > 0 && node.support_roof_layers_below > 0 && !node.is_sharp_tail) {
// 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) {
ExPolygons overhang_expanded;
if (node.overhang.contour.size() > 100 || node.overhang.holes.size()>1)
overhang_expanded.emplace_back(node.overhang);
@@ -2198,16 +2275,19 @@ void TreeSupport::draw_circles()
if (obj_layer_nr>0 && node.distance_to_top < 0)
append(roof_gap_areas, area);
else if (obj_layer_nr > 0 && node.support_roof_layers_below == 1 && node.is_sharp_tail==false)
// ORCA: Roof1stLayer must also fit inside the mm cap.
else if (obj_layer_nr > 0 && node.support_roof_layers_below == 1 &&
(node.dist_mm_to_top - this->top_z_distance) < top_interface_height + EPSILON && node.is_sharp_tail==false)
{
append(roof_1st_layer, area);
max_layers_above_roof1 = std::max(max_layers_above_roof1, node.dist_mm_to_top);
}
else if (obj_layer_nr > 0 && node.support_roof_layers_below > 0 && node.is_sharp_tail == false)
// ORCA: Roof layers must also fit inside the mm cap.
else if (obj_layer_nr > 0 && node.support_roof_layers_below > 1 &&
(node.dist_mm_to_top - this->top_z_distance) < top_interface_height + EPSILON && node.is_sharp_tail == false)
{
append(roof_areas, area);
max_layers_above_roof = std::max(max_layers_above_roof, node.dist_mm_to_top);
interface_id = node.obj_layer_nr % top_interface_layers;
}
else
{
@@ -2236,7 +2316,6 @@ void TreeSupport::draw_circles()
roof_1st_layer.clear();
max_layers_above_roof = std::max(max_layers_above_roof, max_layers_above_roof1);
max_layers_above_roof1 = 0;
interface_id = obj_layer_nr % top_interface_layers;
}
ExPolygons roofs; append(roofs, roof_1st_layer); append(roofs, roof_areas);append(roofs, roof_gap_areas);
@@ -2266,37 +2345,155 @@ void TreeSupport::draw_circles()
for (auto &area : base_areas) { area.simplify(scale_(line_width / 2), &base_areas_simplified); }
base_areas = std::move(base_areas_simplified);
}
//Subtract support floors. We can only compute floor_areas here instead of with roof_areas,
// or we'll get much wider floor than necessary.
if (bottom_interface_layers + bottom_gap_layers > 0)
// ORCA:
// Bottom interface / bottom gap must be anchored to the *true* support-to-model contact surface.
// Do NOT window the contact search by gap or interface height.
// First find the real contact below, then enforce:
// - an empty gap below (contact_z + gap)
// - exactly N interface layers above that
if (!base_areas.empty() && !m_object_config->support_on_build_plate_only.value &&
(bottom_gap_height > EPSILON || bottom_interface_layers > 0))
{
if (layer_nr >= bottom_interface_layers + bottom_gap_layers)
{
// find the lowest interface layer
// TODO the gap may not be exact when "independent support layer height" is enabled
size_t layer_nr_next = layer_nr - bottom_interface_layers;
size_t obj_layer_nr_next = m_ts_data->layer_heights[layer_nr_next].obj_layer_nr;
for (size_t i = 0; i <= bottom_gap_layers && i <= obj_layer_nr_next; i++)
{
const Layer *below_layer = m_object->get_layer(obj_layer_nr_next - i);
ExPolygons bottom_interface = intersection_ex(base_areas, below_layer->lslices);
floor_areas.insert(floor_areas.end(), bottom_interface.begin(), bottom_interface.end());
const coordf_t interface_height =
bottom_interface_layers > 0 ? coordf_t(bottom_interface_layers) * m_slicing_params.layer_height : 0.0;
const coordf_t layer_top_z = ts_layer->print_z;
const coordf_t layer_bottom_z = ts_layer->bottom_z();
ExPolygons new_base_areas;
ExPolygons new_floor_areas;
struct ContactBand {
coordf_t z = 0.0;
Polygons surfaces;
};
for (const ExPolygon& comp : base_areas) {
ExPolygons comp_poly { comp };
bool found_contact = false;
std::vector<ContactBand> bands;
// Search downward for object layers whose TOP/BOTTOM surfaces intersect this component.
for (size_t idx = obj_layer_nr + 1; idx-- > 0;) {
const Layer* below_layer = m_object->get_layer(idx);
Polygons top_surfaces = collect_region_slices_by_type(*below_layer, stTop);
Polygons bottom_surfaces = collect_region_slices_by_type(*below_layer, stBottom);
Polygons surf_union = top_surfaces;
polygons_append(surf_union, bottom_surfaces);
if (surf_union.empty())
continue;
ExPolygons inter = intersection_ex(comp_poly, surf_union);
if (!inter.empty()) {
bands.push_back(ContactBand{ below_layer->print_z, std::move(surf_union) });
found_contact = true;
}
}
if (found_contact) {
std::sort(bands.begin(), bands.end(), [](const ContactBand &a, const ContactBand &b) {
return a.z < b.z;
});
}
if (!found_contact) {
append(new_base_areas, comp_poly);
continue;
}
bool interface_id_set = false;
bool any_gap_cleared = false;
for (const ContactBand &band : bands) {
const coordf_t band_gap_top = band.z + bottom_gap_height;
const coordf_t band_iface_start = band_gap_top;
const bool band_applies = layer_top_z >= band.z - EPSILON;
if (!band_applies)
continue;
// Inside the gap: remove only the part overlapping the contact surface, keep the rest.
if (bottom_gap_height > EPSILON && layer_bottom_z < band_gap_top - EPSILON) {
any_gap_cleared = true;
comp_poly = std::move(diff_ex(comp_poly, band.surfaces));
}
// Overlaps interface band
if (bottom_interface_layers > 0 &&
layer_bottom_z >= band_iface_start - EPSILON &&
layer_bottom_z < band_iface_start + interface_height - EPSILON) {
if (!interface_id_set) {
size_t first_interface_layer = layer_nr;
while (first_interface_layer > 0) {
if (m_ts_data->layer_heights[first_interface_layer - 1].print_z <= band_iface_start + EPSILON)
break;
--first_interface_layer;
}
// ORCA: Use support-layer index for base-interface selection (robust with independent heights).
if (m_support_params.num_bottom_base_interface_layers > 0) {
const int bottom_interface_idx =
std::max(0, int(layer_nr) - int(first_interface_layer));
const int bottom_base_start_idx =
std::max(0, int(bottom_interface_layers) - int(m_support_params.num_bottom_base_interface_layers));
floor_interface_as_base = bottom_interface_idx >= bottom_base_start_idx;
}
interface_id_set = true;
}
ExPolygons band_ex = union_ex(band.surfaces);
if (!band_ex.empty()) {
const coordf_t margin = scale_(m_support_params.support_extrusion_width);
ExPolygons comp_margin = offset_ex(comp_poly, margin);
ExPolygons band_clipped = intersection_ex(band_ex, comp_margin);
band_ex = std::move(band_clipped);
}
ExPolygons comp_interface = band_ex.empty() ? ExPolygons {} : intersection_ex(comp_poly, band_ex);
if (!comp_interface.empty()) {
append(new_floor_areas, comp_interface);
comp_poly = std::move(diff_ex(comp_poly, offset_ex(comp_interface, 10)));
}
}
}
if (any_gap_cleared && comp_poly.empty()) {
continue;
}
if (!comp_poly.empty())
append(new_base_areas, comp_poly);
}
if (floor_areas.empty() == false) {
//floor_areas = std::move(diff_ex(floor_areas, avoid_region_interface));
//floor_areas = std::move(offset2_ex(floor_areas, contact_dist_scaled, -contact_dist_scaled));
base_areas = std::move(diff_ex(base_areas, offset_ex(floor_areas, 10)));
}
base_areas = std::move(new_base_areas);
floor_areas = std::move(new_floor_areas);
}
if (bottom_gap_layers > 0 && m_ts_data->layer_heights[layer_nr].obj_layer_nr > bottom_gap_layers) {
const Layer* below_layer = m_object->get_layer(m_ts_data->layer_heights[layer_nr].obj_layer_nr - bottom_gap_layers);
ExPolygons bottom_gap_area = intersection_ex(floor_areas, below_layer->lslices);
if (!bottom_gap_area.empty()) {
floor_areas = std::move(diff_ex(floor_areas, bottom_gap_area));
// Orca: Hybrid tree first-layer expansion belongs only to the normal-support
// part. area_poly is collected from ePolygon nodes above, which are the normal
// support nodes in Hybrid mode. Apply the expansion before area_groups and
// lslices are built so toolpaths and brim avoidance use the same footprint.
if (layer_nr == 0 && m_raft_layers == 0 && m_support_params.support_style == smsTreeHybrid &&
m_object_config->raft_first_layer_expansion.value > 0.f) {
ExPolygons expanded_base_areas;
const float inflate_factor_1st_layer = float(scale_(m_object_config->raft_first_layer_expansion.value));
Polygons trimming = offset(m_object->layers().front()->lslices, float(scale_(m_support_params.gap_xy_first_layer)),
SUPPORT_SURFACES_OFFSET_PARAMETERS);
// Orca: Match normal support expansion: grow in steps and re-trim against the object each time.
const int nsteps = std::max(5, int(ceil(inflate_factor_1st_layer / m_support_params.first_layer_flow.scaled_width())));
const float step = inflate_factor_1st_layer / nsteps;
for (const ExPolygon &expoly : ts_layer->base_areas) {
if (overlaps({ expoly }, area_poly)) { // normal support in Hybrid mode
Polygons expanded = to_polygons(expoly);
for (int i = 0; i < nsteps; ++i)
expanded = diff(expand(expanded, step), trimming);
append(expanded_base_areas, union_ex(expanded));
} else
expanded_base_areas.emplace_back(expoly);
}
ts_layer->base_areas = std::move(expanded_base_areas);
}
auto &area_groups = ts_layer->area_groups;
for (auto& expoly : ts_layer->base_areas) {
//if (area(expoly) < SQ(scale_(1))) continue;
area_groups.emplace_back(&expoly, SupportLayer::BaseType, max_layers_above_base);
@@ -2306,11 +2503,11 @@ void TreeSupport::draw_circles()
for (auto& expoly : ts_layer->roof_areas) {
//if (area(expoly) < SQ(scale_(1))) continue;
area_groups.emplace_back(&expoly, SupportLayer::RoofType, max_layers_above_roof);
area_groups.back().interface_id = interface_id;
}
for (auto &expoly : ts_layer->floor_areas) {
//if (area(expoly) < SQ(scale_(1))) continue;
area_groups.emplace_back(&expoly, SupportLayer::FloorType, 10000);
area_groups.back().interface_as_base = floor_interface_as_base;
}
for (auto &expoly : ts_layer->roof_1st_layer) {
//if (area(expoly) < SQ(scale_(1))) continue;
@@ -2334,13 +2531,49 @@ void TreeSupport::draw_circles()
//Must update bounding box which is used in avoid crossing perimeter
ts_layer->lslices_bboxes.clear();
ts_layer->lslices_bboxes.reserve(ts_layer->lslices.size());
for (const ExPolygon& expoly : ts_layer->lslices)
ts_layer->lslices_bboxes.emplace_back(get_extents(expoly));
ts_layer->backup_untyped_slices();
}
});
// ORCA: normalize interface_id sequencing to follow printed interface layers only.
const int top_base_layers = int(m_support_params.num_top_base_interface_layers);
const bool interlaced = m_object_config->support_interface_pattern == smipRectilinearInterlaced;
int roof_interface_id = 0;
int floor_interface_id = 0;
bool has_roof_interface;
bool has_floor_interface;
for (size_t layer_nr = 0; layer_nr < m_ts_data->layer_heights.size(); ++layer_nr) {
SupportLayer *ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers);
if (ts_layer == nullptr)
continue;
has_roof_interface = false;
has_floor_interface = false;
for (auto &area_group : ts_layer->area_groups) {
if (area_group.type == SupportLayer::RoofType || area_group.type == SupportLayer::Roof1stLayer) {
if (interlaced)
area_group.interface_id = roof_interface_id;
area_group.interface_as_base = top_base_layers > 0 && roof_interface_id < top_base_layers;
has_roof_interface = true;
} else if (area_group.type == SupportLayer::FloorType) {
if (interlaced)
area_group.interface_id = floor_interface_id;
has_floor_interface = true;
}
}
if (has_roof_interface)
++roof_interface_id;
if (has_floor_interface)
++floor_interface_id;
}
if (with_lightning_infill)
{
@@ -2609,6 +2842,7 @@ void TreeSupport::drop_nodes()
layer_radius.emplace(calc_radius(node_dist));
}
}
// parallel pre-compute avoidance
tbb::parallel_for(tbb::blocked_range<size_t>(0, contact_nodes.size() - 1), [&](const tbb::blocked_range<size_t> &range) {
for (size_t layer_nr = range.begin(); layer_nr < range.end(); layer_nr++) {
@@ -2806,8 +3040,9 @@ void TreeSupport::drop_nodes()
node_parent->to_buildplate = false;
} else {
const bool to_buildplate = !is_inside_ex(get_collision(0, obj_layer_nr_next), next_position);
SupportNode* next_node = m_ts_data->create_node(next_position, node_parent->distance_to_top + 1, obj_layer_nr_next, node_parent->support_roof_layers_below - 1, to_buildplate, node_parent,
print_z_next, height_next);
SupportNode* next_node = m_ts_data->create_node(next_position, node_parent->distance_to_top + 1, obj_layer_nr_next,
node_parent->support_roof_layers_below - (node_parent->distance_to_top > 0 ? 1 : 0),
to_buildplate, node_parent, print_z_next, height_next);
get_max_move_dist(next_node);
m_ts_data->m_mutex.lock();
contact_nodes[layer_nr_next].push_back(next_node);
@@ -2864,7 +3099,8 @@ void TreeSupport::drop_nodes()
p_node->to_buildplate = false;
continue;
}
SupportNode *next_node = m_ts_data->create_node(next_pt, p_node->distance_to_top + 1, obj_layer_nr_next, p_node->support_roof_layers_below - 1,
SupportNode *next_node = m_ts_data->create_node(next_pt, p_node->distance_to_top + 1, obj_layer_nr_next,
p_node->support_roof_layers_below - (p_node->distance_to_top > 0 ? 1 : 0),
to_buildplate, p_node, print_z_next, height_next);
next_node->max_move_dist = 0;
next_node->overhang = std::move(overhang);
@@ -3016,8 +3252,9 @@ void TreeSupport::drop_nodes()
}
auto next_collision = get_collision(0, obj_layer_nr_next);
const bool to_buildplate = !is_inside_ex(m_ts_data->m_layer_outlines[obj_layer_nr_next], next_layer_vertex);
SupportNode * next_node = m_ts_data->create_node(next_layer_vertex, node.distance_to_top + 1, obj_layer_nr_next, node.support_roof_layers_below - 1, to_buildplate, p_node,
print_z_next, height_next);
SupportNode * next_node = m_ts_data->create_node(next_layer_vertex, node.distance_to_top + 1, obj_layer_nr_next,
node.support_roof_layers_below - (node.distance_to_top > 0 ? 1 : 0),
to_buildplate, p_node, print_z_next, height_next);
// don't increase radius if next node will collide partially with the object (STUDIO-7883)
to_outside = projection_onto(next_collision, next_node->position);
direction_to_outer = to_outside - node.position;
@@ -3188,6 +3425,7 @@ std::vector<LayerHeightData> TreeSupport::plan_layer_heights()
obj_layer_zs.reserve(m_object->layer_count());
for (const Layer *l : m_object->layers()) obj_layer_zs.emplace_back((float) l->print_z);
z_heights[m_object->get_layer(0)->print_z] = m_object->get_layer(0)->height;
const coordf_t min_print_z = m_object->get_layer(0)->print_z;
// Collect top contact layers
for (int layer_nr = 1; layer_nr < contact_nodes.size(); layer_nr++) {
if (!contact_nodes[layer_nr].empty()) {
@@ -3195,7 +3433,7 @@ std::vector<LayerHeightData> TreeSupport::plan_layer_heights()
coordf_t height = contact_nodes[layer_nr].front()->height;
// insertion will fail if there is already a key of print_z, so no need to check
bounds.insert({print_z, height});
bounds.insert({print_z - height, 0}); // the bottom_z of the layer
bounds.insert({std::max(min_print_z, print_z - height), 0}); // the bottom_z of the layer
}
}
@@ -3238,7 +3476,12 @@ std::vector<LayerHeightData> TreeSupport::plan_layer_heights()
// add support layers according to layer_heights
int support_layer_nr = m_raft_layers;
for (size_t i = 0; i < layer_heights.size(); i++, support_layer_nr++) {
SupportLayer *ts_layer = m_object->add_tree_support_layer(support_layer_nr, layer_heights[i].print_z, layer_heights[i].height, layer_heights[i].print_z);
// SupportLayer *ts_layer = m_object->add_tree_support_layer(support_layer_nr, layer_heights[i].print_z, layer_heights[i].height, layer_heights[i].print_z);
// ORCA: add_tree_support_layer() argument order is (id, height, print_z, slice_z).
// Passing print_z as height breaks support layer geometry.
SupportLayer *ts_layer = m_object->add_tree_support_layer(support_layer_nr, layer_heights[i].height, layer_heights[i].print_z, layer_heights[i].print_z);
if (ts_layer->id() > m_raft_layers) {
SupportLayer *lower_layer = m_object->get_support_layer(ts_layer->id() - 1);
if (lower_layer) {
@@ -3287,7 +3530,21 @@ std::vector<LayerHeightData> TreeSupport::plan_layer_heights()
for (SupportNode *node : contact_nodes[layer_nr]) {
node->height = new_height;
node->distance_to_top = -num_layers;
node->support_roof_layers_below += num_layers - 1;
}
}
// ORCA: Recompute support_roof_layers_below from remaining interface height (independent heights).
const int top_layers = m_object->config().support_interface_top_layers.value;
if (m_support_params.independent_layer_height && top_layers > 0) {
const coordf_t interface_height_mm = coordf_t(top_layers) * m_slicing_params.layer_height;
for (int layer_nr = 0; layer_nr < contact_nodes.size(); layer_nr++) {
if (contact_nodes[layer_nr].empty()) continue;
for (SupportNode *node : contact_nodes[layer_nr]) {
if (node->height <= EPSILON) continue;
const coordf_t remaining_mm = interface_height_mm - (node->dist_mm_to_top - this->top_z_distance);
const int layers_fit = remaining_mm < -EPSILON ? 0 : int(std::floor((remaining_mm + EPSILON) / node->height));
node->support_roof_layers_below = std::min(layers_fit, top_layers);
}
}
}
@@ -3306,8 +3563,6 @@ void TreeSupport::generate_contact_points()
const coordf_t max_bridge_length = scale_(config.max_bridge_length.value);
coord_t radius_scaled = scale_(base_radius);
bool on_buildplate_only = m_object_config->support_on_build_plate_only.value;
const bool roof_enabled = config.support_interface_top_layers.value > 0;
const bool force_tip_to_roof = roof_enabled && m_support_params.soluble_interface;
const auto belt_floor_mode = m_print_config->belt_support_floor_mode.value;
const bool has_belt_floor = std::abs(m_slicing_params.belt_floor_shear_factor) > EPSILON
&& belt_floor_mode == BeltSupportFloorMode::GeneratorOnly;
@@ -3343,7 +3598,7 @@ void TreeSupport::generate_contact_points()
// z_distance_top = round(z_distance_top / layer_height) * layer_height;
// // BBS: add extra distance if thick bridge is enabled
// // Note: normal support uses print_z, but tree support uses integer layers, so we need to subtract layer_height
// if (!m_slicing_params.soluble_interface && m_object_config->thick_bridges) {
// if (!m_slicing_params.zero_gap_interface_top && m_object_config->thick_bridges) {
// z_distance_top += m_object->layers()[0]->regions()[0]->region().bridging_height_avg(m_object->print()->config()) - layer_height;
//}
// }
@@ -3351,8 +3606,6 @@ void TreeSupport::generate_contact_points()
int gap_layers = z_distance_top == 0 ? 0 : 1;
size_t support_roof_layers = config.support_interface_top_layers.value;
if (support_roof_layers > 0)
support_roof_layers += 1; // BBS: add a normal support layer below interface (if we have interface)
coordf_t thresh_angle = std::min(89.f, config.support_threshold_angle.value < EPSILON ? 30.f : config.support_threshold_angle.value);
coordf_t half_overhang_distance = scale_(tan(thresh_angle * M_PI / 180.0) * layer_height / 2);
@@ -3410,13 +3663,13 @@ void TreeSupport::generate_contact_points()
if (force_add || !already_inserted.count(hash_pos)) {
already_inserted.emplace(hash_pos);
bool to_buildplate = true;
size_t roof_layers = add_interface ? support_roof_layers : 0;
size_t roof_layers = add_interface ? (support_roof_layers > 0 ? support_roof_layers - 1 : 0) : 0; // subtract 1 because the contact node itself counts as one layer
// add a new node as a virtual node which acts as the invisible gap between support and object
// distance_to_top=-1: it's virtual
// print_z=object_layer->bottom_z: it directly contacts the bottom
// height=z_distance_top: it's height is exactly the gap distance
// dist_mm_to_top=0: it directly contacts the bottom
contact_node = m_ts_data->create_node(pt, -gap_layers, layer_nr-1, roof_layers + 1, to_buildplate, SupportNode::NO_PARENT, bottom_z, z_distance_top, 0,
contact_node = m_ts_data->create_node(pt, -gap_layers, layer_nr-1, roof_layers, to_buildplate, SupportNode::NO_PARENT, bottom_z, z_distance_top, 0,
radius);
contact_node->overhang = overhang;
contact_node->is_sharp_tail = is_sharp_tail;
@@ -3454,7 +3707,7 @@ void TreeSupport::generate_contact_points()
}
for (auto &overhang : overhangs_regular) {
bool add_interface = (force_tip_to_roof || area(overhang) > minimum_roof_area) && !is_sharp_tail;
bool add_interface = area(overhang) > minimum_roof_area && !is_sharp_tail;
BoundingBox overhang_bounds = get_extents(overhang);
double radius = std::clamp(unscale_(overhang_bounds.radius()), MIN_BRANCH_RADIUS, base_radius);
// add supports at corners for both auto and manual overhangs, github #2008