mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-16 23:42:11 +00:00
Belt Printing: Bugfix: Solid Organic Tree Base, Slim Tree Skirt, Renderer (#14395)
* fix tree support brim * treesupport3d part 1: more diagnostic logging. (todo once things are fixed: remove this / gate it properly) * make area under Z=0 in rotated slice pipeline not solid * fix solid Z=0 layer for belt printers * fix renderer * clean up logging * final review pass
This commit is contained in:
@@ -1775,7 +1775,14 @@ void generate_support_toolpaths(
|
||||
bool sheath = support_params.with_sheath;
|
||||
bool no_sort = false;
|
||||
bool done = false;
|
||||
if (base_layer.layer->bottom_z < EPSILON) {
|
||||
// Belt printers have no flat bed first layer — the belt is the tilted
|
||||
// build surface — so the dense raft_first_layer_density flange must not
|
||||
// fire anywhere, including the layer at z=0 (the belt-surface line).
|
||||
// (belt_floor_shear_factor is non-zero only when belt_printer is on.)
|
||||
// For every other printer type, support z is never negative, so this
|
||||
// matches the original "first layer at z=0" behaviour unchanged.
|
||||
const bool is_belt_printer = std::abs(slicing_params.belt_floor_shear_factor) > EPSILON;
|
||||
if (! is_belt_printer && base_layer.layer->bottom_z < EPSILON) {
|
||||
// Base flange (the 1st layer).
|
||||
filler = filler_first_layer;
|
||||
filler->angle = Geometry::deg2rad(float(config.support_angle.value + 90.));
|
||||
|
||||
@@ -172,11 +172,17 @@ TreeModelVolumes::TreeModelVolumes(
|
||||
if (ctx.is_active()
|
||||
&& pcfg2.belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly) {
|
||||
m_belt_floor = ctx.compute_per_layer_floors(num_layers, [&](size_t layer_idx) -> double {
|
||||
// Use local print_z (subtract global offset from object layer).
|
||||
return (layer_idx >= num_raft_layers)
|
||||
? print_object.get_layer(layer_idx - num_raft_layers)->print_z
|
||||
- print_object.belt_global_z_offset()
|
||||
: 0.;
|
||||
// Object layers: local print_z (subtract global offset).
|
||||
if (layer_idx >= num_raft_layers)
|
||||
return print_object.get_layer(layer_idx - num_raft_layers)->print_z
|
||||
- print_object.belt_global_z_offset();
|
||||
// Belt raft layers (below the object): each carries its own
|
||||
// local print_z in m_raft_layers. The belt floor is a tilted
|
||||
// plane, so the half-plane to clip grows as print_z drops —
|
||||
// using 0 here clipped every below-object layer against the
|
||||
// Z=0 belt surface, under-clipping the raft region and leaving
|
||||
// a dense support mass below the floor.
|
||||
return (layer_idx < m_raft_layers.size()) ? m_raft_layers[layer_idx] : 0.;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1506,6 +1506,16 @@ void TreeSupport::generate_toolpaths()
|
||||
// ORCA: base angle used for explicit interlaced interface orientation.
|
||||
const float base_support_angle = Geometry::deg2rad(object_config.support_angle.value);
|
||||
|
||||
// Belt floor: the lowest support layer rests on the moving, tilted belt, not
|
||||
// on a flat bed — so it must NOT get the bed first-layer treatment (a brim on
|
||||
// interface areas, a first-layer-flow sheath at raft_first_layer_density on
|
||||
// base areas). That treatment draws a loop along the Z=0 belt-floor line that
|
||||
// reads as a stray brim/skirt. Gate those layer_id==0 special cases off when
|
||||
// the belt floor is active; false on non-belt printers so behavior is unchanged.
|
||||
BeltFloorContext belt_ctx;
|
||||
const bool belt_floor_active = belt_ctx.init(m_slicing_params, *m_print_config)
|
||||
&& m_print_config->belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly;
|
||||
|
||||
// generate tree support tool paths
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(m_raft_layers, m_object->support_layer_count()),
|
||||
@@ -1539,7 +1549,7 @@ void TreeSupport::generate_toolpaths()
|
||||
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) {
|
||||
if (layer_id == 0 && !belt_floor_active) {
|
||||
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;
|
||||
@@ -1623,7 +1633,7 @@ void TreeSupport::generate_toolpaths()
|
||||
}
|
||||
else {
|
||||
// base_areas
|
||||
bool support_base_on_bed = (layer_id == 0 && m_raft_layers == 0);
|
||||
bool support_base_on_bed = (layer_id == 0 && m_raft_layers == 0 && !belt_floor_active);
|
||||
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)
|
||||
@@ -1644,7 +1654,7 @@ void TreeSupport::generate_toolpaths()
|
||||
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) {
|
||||
if (layer_id == 0 && !belt_floor_active) {
|
||||
float density = float(m_object_config->raft_first_layer_density.value * 0.01);
|
||||
fill_expolygons_with_sheath_generate_paths(base_dst, loops, filler_support.get(), density, erSupportMaterial, flow,
|
||||
m_support_params, true, false);
|
||||
@@ -1916,9 +1926,6 @@ void TreeSupport::generate()
|
||||
if (!belt_ext_layers.empty()) {
|
||||
auto &sl_vec = m_object->support_layers();
|
||||
sl_vec.insert(sl_vec.begin(), belt_ext_layers.begin(), belt_ext_layers.end());
|
||||
BOOST_LOG_TRIVIAL(debug) << "[BELT-CALIB] wedge ext layers=" << belt_ext_layers.size()
|
||||
<< " z=" << belt_ext_layers.front()->print_z << ".." << belt_ext_layers.back()->print_z
|
||||
<< " seeded=" << seeded;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2177,6 +2184,16 @@ void TreeSupport::draw_circles()
|
||||
coordf_t support_extrusion_width = m_support_params.support_extrusion_width;
|
||||
const float tree_brim_width = config.tree_support_brim_width.value;
|
||||
|
||||
// Belt floor: the first object layer is not on a flat bed — it rests on the
|
||||
// tilted, moving belt. So the first-object-layer adhesion features (the tree
|
||||
// support brim, the hybrid first-layer base expansion) must be suppressed:
|
||||
// their expanded contact rings project to a stray brim/skirt loop sitting in
|
||||
// the Z=0 belt plane around the support footprint. false on non-belt printers,
|
||||
// so behavior there is unchanged.
|
||||
BeltFloorContext belt_ctx;
|
||||
const bool belt_floor_active = belt_ctx.init(m_slicing_params, *m_print_config)
|
||||
&& m_print_config->belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly;
|
||||
|
||||
if (m_object->support_layer_count() <= m_raft_layers)
|
||||
return;
|
||||
BOOST_LOG_TRIVIAL(info) << "draw_circles for object: " << m_object->model_object()->name;
|
||||
@@ -2287,7 +2304,7 @@ void TreeSupport::draw_circles()
|
||||
circle.points[i] = circle.points[i] * scale + node.position;
|
||||
}
|
||||
}
|
||||
if (obj_layer_nr == 0 && m_raft_layers == 0) {
|
||||
if (obj_layer_nr == 0 && m_raft_layers == 0 && !belt_floor_active) {
|
||||
double brim_width = !config.tree_support_auto_brim ? tree_brim_width : std::max(MIN_BRANCH_RADIUS_FIRST_LAYER, std::min(node.radius + node.dist_mm_to_top / (scale * branch_radius) * 0.5, MAX_BRANCH_RADIUS_FIRST_LAYER) - node.radius);
|
||||
auto tmp=offset(circle, scale_(brim_width));
|
||||
if(!tmp.empty())
|
||||
@@ -2352,12 +2369,19 @@ void TreeSupport::draw_circles()
|
||||
base_areas = intersection_ex(base_areas, m_machine_border);
|
||||
|
||||
// Belt floor: clip tree support polygons by the belt surface plane.
|
||||
// ts_layer->print_z is at LOCAL Z (global offset applied later in
|
||||
// _generate_support_material), so use init_local to subtract
|
||||
// belt_global_z_offset from z_shift.
|
||||
// Non-organic tree support layers inherit their print_z from the
|
||||
// (already globally-offset) object layers — see plan_layer_heights()
|
||||
// and add_tree_support_layer(); only ORGANIC layers get the global
|
||||
// Z offset applied later in _generate_support_material(). So here
|
||||
// ts_layer->print_z is in the GLOBAL frame and we must use init()
|
||||
// (global), not init_local(): mixing a local-frame clip plane with
|
||||
// a global print_z displaces the cutoff line by belt_global_z_offset
|
||||
// along the shear axis, leaving an un-clipped wedge of support below
|
||||
// the belt floor. In per-object (non-global) mode belt_global_z_offset
|
||||
// is 0 so init() and init_local() coincide — this is a no-op there.
|
||||
{
|
||||
BeltFloorContext ctx;
|
||||
if (ctx.init_local(m_slicing_params, *m_print_config, m_object->belt_global_z_offset())
|
||||
if (ctx.init(m_slicing_params, *m_print_config)
|
||||
&& m_print_config->belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly) {
|
||||
Polygons belt_surface = ctx.surface_polygon(ts_layer->print_z);
|
||||
base_areas = diff_ex(base_areas, belt_surface);
|
||||
@@ -2500,7 +2524,7 @@ void TreeSupport::draw_circles()
|
||||
// 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 &&
|
||||
if (layer_nr == 0 && m_raft_layers == 0 && !belt_floor_active && 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));
|
||||
|
||||
@@ -4016,12 +4016,20 @@ void organic_draw_branches(
|
||||
std::vector<BottomExtraSlice> bottom_extra_slices;
|
||||
Polygons rest_support;
|
||||
coord_t bottom_radius = support_element_radius(config, *branch.path.front());
|
||||
// Belt printer (GeneratorOnly belt floor): the tilted belt surface is the
|
||||
// build surface, so a branch should terminate ON the belt with a thin tip,
|
||||
// not stamp its full footprint straight down to Z=0 and weld neighbouring
|
||||
// branches into a solid floor slab. m_belt_floor is only populated in that
|
||||
// mode, so it doubles as the gate (no effect on other printer types).
|
||||
const bool belt_mode = !volumes.m_belt_floor.empty();
|
||||
// Don't propagate further than 1.5 * bottom radius.
|
||||
//LayerIndex layers_propagate_max = 2 * bottom_radius / config.layer_height;
|
||||
LayerIndex layers_propagate_max = 5 * bottom_radius / config.layer_height;
|
||||
LayerIndex layer_bottommost = branch.path.front()->state.verylost ?
|
||||
LayerIndex layer_bottommost = (branch.path.front()->state.verylost && !belt_mode) ?
|
||||
// If the tree bottom is hanging in the air, bring it down to some surface.
|
||||
0 :
|
||||
// In belt mode never force-drop to Z=0 (the belt clip below handles
|
||||
// termination); otherwise the "verylost" branch welds into the slab.
|
||||
//FIXME the "verylost" branches should stop when crossing another support.
|
||||
std::max(0, layer_begin - layers_propagate_max);
|
||||
double support_area_min_radius = M_PI * sqr(double(config.branch_radius));
|
||||
@@ -4033,14 +4041,28 @@ void organic_draw_branches(
|
||||
Polygons collision = volumes.getCollision(0, collision_layer, false);
|
||||
rest_support = diff_clipped(rest_support.empty() ? slice_front_contact : rest_support, collision, ApplySafetyOffset::Yes);
|
||||
// Belt floor: clip propagated support at belt surface.
|
||||
if (layer_idx < LayerIndex(volumes.m_belt_floor.size()) && !volumes.m_belt_floor[layer_idx].empty())
|
||||
bool belt_cut = false;
|
||||
if (layer_idx < LayerIndex(volumes.m_belt_floor.size()) && !volumes.m_belt_floor[layer_idx].empty()) {
|
||||
double area_before = area(rest_support);
|
||||
rest_support = diff(rest_support, volumes.m_belt_floor[layer_idx]);
|
||||
// The belt counts as "reached" only when it actually removes part
|
||||
// of this branch's footprint. The belt half-plane is non-empty at
|
||||
// every near-belt layer, so testing non-emptiness alone would
|
||||
// terminate a laterally-distant branch ~1 layer above true contact,
|
||||
// leaving a gap. Require a real area reduction instead.
|
||||
belt_cut = belt_mode && area(rest_support) < area_before - tiny_area;
|
||||
}
|
||||
remove_small(rest_support, tiny_area);
|
||||
double rest_support_area = area(rest_support);
|
||||
if (rest_support_area < support_area_stop)
|
||||
// Don't propagate a fraction of the tree contact surface.
|
||||
break;
|
||||
bottom_extra_slices.push_back({ rest_support, rest_support_area });
|
||||
// Belt mode: once the belt surface actually starts cutting this branch
|
||||
// it has reached the belt — keep this last (belt-clipped) slice as the
|
||||
// contact and stop, rather than stamping the footprint further down.
|
||||
if (belt_cut)
|
||||
break;
|
||||
}
|
||||
// Now remove those bottom slices that are not supported at all.
|
||||
#if 0
|
||||
@@ -4058,7 +4080,10 @@ void organic_draw_branches(
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (config.settings.support_floor_layers > 0) {
|
||||
// Belt mode: no solid support-floor pad under these branches — it is what
|
||||
// welds neighbouring belt-terminating branches into the dense Z=0 slab.
|
||||
// They simply taper out onto the tilted belt as distributed thin contacts.
|
||||
if (!belt_mode && config.settings.support_floor_layers > 0) {
|
||||
Polygons contacts;
|
||||
if (!bottom_extra_slices.empty()) {
|
||||
const int contact_idx = int(bottom_extra_slices.size()) - 1; // Use the lowest contact slice as the footprint.
|
||||
@@ -4097,7 +4122,10 @@ void organic_draw_branches(
|
||||
}
|
||||
|
||||
// ORCA: retain bottom contacts even when no placeable areas intersect.
|
||||
if (branch.has_root && config.support_rests_on_model && branch.path.front()->state.layer_idx > 0 &&
|
||||
// Skipped in belt mode (m_belt_floor populated) so we don't re-introduce a
|
||||
// solid floor pad for branches that terminate on the tilted belt surface.
|
||||
if (volumes.m_belt_floor.empty() &&
|
||||
branch.has_root && config.support_rests_on_model && branch.path.front()->state.layer_idx > 0 &&
|
||||
config.settings.support_floor_layers > 0 && config.z_distance_bottom_layers > 0 &&
|
||||
bottom_contacts.empty() && !slice_front_contact.empty())
|
||||
bottom_contacts.emplace_back(slice_front_contact);
|
||||
|
||||
@@ -1289,16 +1289,41 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
|
||||
size_t n_filtered = 0, n_clip = 0;
|
||||
const double y_lo = model_bb.defined ? model_bb.min.y() - ANCHOR_CLIP_MARGIN_MM : -1e30;
|
||||
const double y_hi = model_bb.defined ? model_bb.max.y() + ANCHOR_CLIP_MARGIN_MM : 1e30;
|
||||
for (const GCodeProcessorResult::MoveVertex& mv : gcode_result.moves)
|
||||
if (mv.type == EMoveType::Extrude && mv.layer_id >= 1) { // skip layer-0 prime/skirt
|
||||
++n_filtered;
|
||||
const Vec3d p = belt_inv * mv.position.cast<double>();
|
||||
tp_bb_full.merge(p);
|
||||
if (p.y() >= y_lo && p.y() <= y_hi) {
|
||||
tp_bb_clip.merge(p);
|
||||
++n_clip;
|
||||
// The anchor aligns the toolpath body onto the object-only model bbox, so it must be
|
||||
// built from OBJECT-body toolpaths only. Support, skirt, brim and wipe-tower extrusions
|
||||
// are not part of the model mesh and extend past it (support especially reaches well
|
||||
// beyond the object on a belt), so including them drags tp_bb.min and shifts the whole
|
||||
// back-transformed g-code off the mesh — but only when those features are present. That
|
||||
// is the "g-code shifts vs mesh as soon as supports are enabled" bug. Filter them out.
|
||||
auto is_object_body = [](ExtrusionRole r) {
|
||||
return r != erSupportMaterial && r != erSupportMaterialInterface
|
||||
&& r != erSupportTransition
|
||||
&& r != erSkirt && r != erBrim && r != erWipeTower;
|
||||
};
|
||||
auto accumulate = [&](bool body_only) {
|
||||
tp_bb_clip = BoundingBoxf3();
|
||||
tp_bb_full = BoundingBoxf3();
|
||||
n_filtered = 0;
|
||||
n_clip = 0;
|
||||
for (const GCodeProcessorResult::MoveVertex& mv : gcode_result.moves)
|
||||
if (mv.type == EMoveType::Extrude && mv.layer_id >= 1 // skip layer-0 prime/skirt
|
||||
&& (!body_only || is_object_body(mv.extrusion_role))) {
|
||||
++n_filtered;
|
||||
const Vec3d p = belt_inv * mv.position.cast<double>();
|
||||
tp_bb_full.merge(p);
|
||||
if (p.y() >= y_lo && p.y() <= y_hi) {
|
||||
tp_bb_clip.merge(p);
|
||||
++n_clip;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
accumulate(/*body_only=*/true);
|
||||
// Fallback: a plate with no object-body extrusions above layer 0 (e.g. a
|
||||
// support-only object) would leave tp_bb undefined and silently skip the
|
||||
// anchor. Re-accumulate over ALL extrude moves so the min-corner anchor is
|
||||
// still computed rather than letting the preview float by the placement offset.
|
||||
if (n_filtered == 0)
|
||||
accumulate(/*body_only=*/false);
|
||||
// Use the clipped bbox when it still holds the bulk of the moves (outliers removed).
|
||||
// If the object sits far from the belt entry the toolpaths are grossly offset and the
|
||||
// clip drops most of them — fall back to the full bbox so the min-corner anchor still
|
||||
|
||||
Reference in New Issue
Block a user