Organic supports: restore the per-slice collision, bed and belt trim

dda58b07cd stripped debug instrumentation from TreeSupport3D.cpp with a
script, and that script also deleted the loop in organic_draw_branches()
that trims every branch slice against the collision volume, the bed and,
on a belt, the belt plane.  This is the generator every printer uses, not
a belt code path, and it is the one place where raistlin7447's export
fixtures differed from main with belt printing off.  Restore the loop as
it was on main, with the belt-floor clip.

The new test prints a cube carrying a 60 mm plate with organic supports
on a flat-bed printer and checks on every support layer that no support
extrusion comes within 0.2 mm of the part's slice.  It guards that
invariant; on this fixture the loop's own effect is a sub-millimetre
reshaping of one branch (verified by slicing the fixture with and
without the loop), below the asserted gap, so the test does not by
itself fail without the loop.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
harrierpigeon
2026-10-07 18:24:27 -05:00
co-authored by Claude Fable 5.1
parent d0c2ada32a
commit 54e079ae25
2 changed files with 101 additions and 0 deletions
+7
View File
@@ -3952,6 +3952,13 @@ void organic_draw_branches(
const double tiny_area = tiny_area_threshold();
//FIXME parallelize?
for (LayerIndex i = 0; i < LayerIndex(slices.size()); ++i) {
// ORCA: safety offset when trimming collision/bed to improve robustness.
slices[i] = diff_clipped(slices[i], volumes.getCollision(0, layer_begin + i, true), ApplySafetyOffset::Yes); // FIXME parent_uses_min || draw_area.element->state.use_min_xy_dist);
slices[i] = intersection(slices[i], volumes.m_bed_area, ApplySafetyOffset::Yes);
// Belt floor: clip branch slices against the belt surface plane.
LayerIndex belt_idx = layer_begin + i;
if (belt_idx < LayerIndex(volumes.m_belt_floor.size()) && !volumes.m_belt_floor[belt_idx].empty())
slices[i] = diff(slices[i], volumes.m_belt_floor[belt_idx]);
remove_small(slices[i], tiny_area);
}
+94
View File
@@ -31,10 +31,12 @@
#include "libslic3r/Support/TreeModelVolumes.hpp"
#include "libslic3r/Support/TreeSupportCommon.hpp"
#include "libslic3r/Support/BeltFloorContext.hpp"
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/ExtrusionEntity.hpp"
#include "libslic3r/Polyline.hpp"
#include <limits>
#include <cmath>
#include <map>
#include "libslic3r/Polygon.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/GCodeReader.hpp"
@@ -1244,6 +1246,98 @@ TEST_CASE("Organic tree supports place a support blocker at its own height above
CHECK(collides(last + num_raft));
}
// organic_draw_branches() trims every branch slice against the collision volume (the
// part grown by the support XY distance), the bed and, on a belt, the belt plane before
// it becomes support, so a branch never runs into the part it supports. Not a belt
// feature: this is the generator every printer uses.
TEST_CASE("Organic tree supports keep their distance from the part", "[Print][Support]")
{
// A 20 mm cube carrying a 60 x 60 mm plate: a 20 mm wide ceiling all around the
// cube, 16 mm above the bed, with the cube's four corners in the way of the branches
// that drop from it. The plate reaches into the cube so the two shells overlap
// instead of sharing a face.
indexed_triangle_set its = its_make_cube(20., 20., 20.);
indexed_triangle_set plate = its_make_cube(60., 60., 4.);
its_translate(its, Vec3f(20.f, 20.f, 0.f));
its_translate(plate, Vec3f(0.f, 0.f, 16.f));
its_merge(its, plate);
TriangleMesh mesh(std::move(its));
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
config.set_deserialize_strict({
{ "layer_height", 0.2 },
{ "initial_layer_print_height", 0.2 },
{ "skirt_loops", 0 },
{ "enable_support", 1 },
{ "support_type", "tree(auto)" },
{ "support_style", "organic" },
{ "support_threshold_angle", 30 },
});
Print print;
Model model;
init_print({ mesh }, print, model, config);
// On the bed, not at its corner (the fixture leaves the object at the origin).
model.objects.front()->instances.front()->set_offset(Vec3d(100., 100., 0.));
print.apply(model, config);
print.set_status_silent();
print.process();
const PrintObject &object = *print.objects().front();
INFO("object layers " << object.layers().size() << ", support layers " << object.support_layers().size());
REQUIRE(! object.support_layers().empty());
// Support exists under the plate at all.
size_t support_layers_with_fills = 0;
for (const SupportLayer *layer : object.support_layers())
if (! layer->support_fills.empty())
++ support_layers_with_fills;
INFO("support layers with extrusions " << support_layers_with_fills);
CHECK(support_layers_with_fills > 20);
// Object layers by print_z, to look up the part's slice at a support layer's height.
std::map<coord_t, const Layer *> object_layers;
for (const Layer *layer : object.layers())
object_layers[scaled<coord_t>(layer->print_z)] = layer;
auto contains = [](const ExPolygons &expolys, const Point &pt) {
for (const ExPolygon &ex : expolys)
if (ex.contains(pt))
return true;
return false;
};
// No support extrusion may run closer to the part's slice than half a line width:
// the generator keeps the support XY distance (0.35 mm by default) plus the line's
// own half width away from it.
const float min_gap = scaled<float>(0.2);
size_t too_close = 0, points = 0, layers_checked = 0, layers_unmatched = 0;
for (const SupportLayer *layer : object.support_layers()) {
if (layer->support_fills.empty())
continue;
// The object layer whose slab spans this support layer's height.
auto it = object_layers.lower_bound(scaled<coord_t>(layer->print_z - EPSILON));
if (it == object_layers.end()) {
++ layers_unmatched;
continue;
}
++ layers_checked;
const ExPolygons grown = offset_ex(it->second->lslices, min_gap);
for (const ExtrusionEntity *entity : layer->support_fills.flatten().entities)
for (const Slic3r::Polyline &pl : entity->as_polylines())
for (size_t i = 0; i < pl.points.size(); ++ i) {
// The vertices and the midpoints of the segments between them.
++ points;
if (contains(grown, pl.points[i]))
++ too_close;
if (i + 1 < pl.points.size() && contains(grown, (pl.points[i] + pl.points[i + 1]) / 2))
++ too_close;
}
}
INFO("support layers checked " << layers_checked << " (unmatched " << layers_unmatched << "), support points " << points
<< ", within 0.2 mm of the part " << too_close);
CHECK(layers_checked > 20);
CHECK(layers_unmatched == 0);
REQUIRE(points > 0);
CHECK(too_close == 0);
}
// Two parts along the belt: the second part's slicing frame starts at the belt
// below its leading end, so its first layers are empty and interleave with the
// first part's printing layers. Those must not reach the G-code as layer changes