mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-07 15:51:08 +00:00
Belt printer: retire the redundant and unused options (#16236)
Follow-up to #16195 and the review discussion on #14394 (yw4z's note about the third column on the *Belt tilt* row). Removes the belt options that are redundant or unused before the branch ships, so they never need compatibility handling after a release, and fixes supports under a leading overhang. Every removed key is on `handle_legacy()`'s ignore list, so existing profiles and 3MFs load silently. ## Removed - **`belt_slice_rotation_global`**, **`preslice_remap_global`**, **`belt_preslice_global`** (*Global mesh transforms*) and **`gcode_back_transform`** — the global mode and the back-transform are what belt printing is; they are presumed on wherever the flags were consulted (`PrintObjectSlice`, `BeltBackTransform`, `BeltGCode`, `Print::process`, `PrintApply`, `GCodeViewer`). The *Belt tilt* row is axis + angle only; the three `fdm_belt_common.json` drop the keys. - **`preslice_remap_x/y/z`** — no profile used the pre-slice axis remap; the belt tilt axis plus the G-code axis remap cover the machines that exist, and its implementation only agreed with itself for a plain swap. The forward transform is the rotation. - **`belt_support_z_offset_mode`** and **`belt_support_floor_mode`** — the first was never read by a generator; the second's only shipped value (*Generator only*) is now the behaviour. - **`first_layer_plane`**, **`first_layer_plane_offset`**, **`first_layer_plane_thickness`** and `FirstLayerPlane.{cpp,hpp}` — the first-layer band is measured from the belt surface and is one first layer height thick. - `belt_brim_instances_compatible()` and its validation warning: instances along the belt get their brim. ## Supports under a leading overhang (the clipping at the object's local Z = 0) The slicing frame of a belt object started at its lowest vertex, but the belt under the leading end of an overhang lies below that, so no generator could reach it: normal supports stopped at the object's lowest layer, and both tree generators carried extension hacks sized from the pre-rotation bbox and capped at global Z = 0 (right only for the trailing half of the belt). The frame now starts at the lowest belt-floor point under the footprint, less a 10 mm margin along the belt for the base of a support column, and the extensions are gone: - **Normal supports** run in the object frame and get the global belt Z offset shifted onto the result (as organic already did). With the offset on the object layers, a top contact at negative Z turned the intermediate-layer count negative and the generator allocated layers until the kernel killed it — any overhang in the leading half of the belt did this. The first-layer flange expansion is skipped on a belt (the first support layer is the leading tip, not a flange). - **Classic tree** nodes keep dropping until their whole circle is in the belt, so a branch tapers to a tip on the belt instead of stopping a radius above it. - **Organic**: the belt is no longer a support blocker. A blocker is a collision, and a branch descending onto one slides off it, down the belt and ahead of the part; the belt is where branches end, which the per-layer floor clipping already does. Regression test *Belt supports reach the belt under a leading overhang*: a cube with a fin whose underside is parallel to the layers, 20 mm ahead of the cube and up to 41 mm of slicing Z above the belt, for normal, organic and classic tree supports; the lowest support layer must sit on the belt beneath its own lines. The belt object height (the layer range) is now estimated from the box of the mesh as placed on the bed. `raw_bounding_box()` has the instance's Z offset removed, which was harmless for the old rotated-extent estimate but not for one anchored at the belt floor (a point's rotated z and the floor under it move in opposite directions under a Z shift): with the first version of this change every part came out as a wedge, sliced only up to its diagonal, in the GUI and CLI alike. Caught by a GUI test pass; the leading-overhang test now also checks that the whole part is sliced. ## Belt brim after the parallel support step `belt_brim_obstacles()` reads every object's layers and support layers, which another object's support step rebuilds (and now shifts) at the same time. The brim is generated sequentially once the parallel step is over (`PrintObject::generate_belt_brim()`). This is the race behind the Windows arm64 segfault in *Belt brim of each object precedes its perimeters on its own filament*. ## UI - *Belt tilt* is two rows: the angle (Advanced) and the axis (Developer; a profile-level kinematics choice). A shared line is shown by its first option's mode, so they cannot share one. - *Machine frame transforms* is five single-option rows (G-code remap X / Y / Z, Decouple machine-frame tilt, Machine-frame tilt angle — the angle row only appears when decoupled) instead of two multi-column lines; the remap fields got full labels since they stand alone now. - The gravity indicator on the bed is a plain line along the up direction (no cone, 60 % of the axes' length), per yw4z. - The *Show raw G-code (belt only)* legend/canvas toggle and its `B` shortcut are gone; the preview is the designed view. Also carries the two-line `phong.fs` fix from #16226 (merges as a no-op). ## Verification - `libslic3r_tests` 1116 passed (92 648 assertions); `fff_print_tests` 351 passed (561 696 assertions). - `scripts/clang_tidy_diff.py --base upstream/belt-printer`: no findings. - `scripts/orca_profile_tool.py check`: no profile references a removed key. - GUI target builds; a scripted GUI pass (xdotool) checked the settings groups in every mode, slicing, export, instances, the purge tower, calibration dialogs, the wizard, printer switching and 3MF round-trip. The wiki pages (OrcaSlicer/OrcaSlicer_WIKI#374) get a follow-up dropping the removed sections once this is in.
This commit is contained in:
@@ -56,8 +56,6 @@ TEST_CASE("Belt machine coordinates retain a non-45-degree slicing angle", "[GCo
|
||||
config.belt_printer.value = true;
|
||||
config.belt_slice_rotation.value = BeltRotationAxis::X;
|
||||
config.belt_slice_rotation_angle.value = 30.;
|
||||
config.belt_slice_rotation_global.value = true;
|
||||
config.gcode_back_transform.value = true;
|
||||
config.gcode_remap_x.value = RemapAxis::PosX;
|
||||
config.gcode_remap_y.value = RemapAxis::PosZ;
|
||||
config.gcode_remap_z.value = RemapAxis::PosY;
|
||||
@@ -1027,11 +1025,8 @@ SCENARIO("Belt: the first travel does not lift through the uninitialised origin"
|
||||
// Machine-frame + slicer->world back-transform config (X tilt, 45 deg).
|
||||
PrintConfig belt_config;
|
||||
belt_config.belt_printer.value = true;
|
||||
belt_config.gcode_back_transform.value = true;
|
||||
belt_config.belt_slice_rotation.value = BeltRotationAxis::X;
|
||||
belt_config.belt_slice_rotation_angle.value = 45.0;
|
||||
belt_config.belt_slice_rotation_global.value = true;
|
||||
belt_config.belt_preslice_global.value = true;
|
||||
belt_config.belt_frame_tilt_decouple.value = false;
|
||||
belt_config.belt_frame_tilt_angle.value = 45.0;
|
||||
|
||||
|
||||
@@ -1438,7 +1438,6 @@ TEST_CASE("Belt printers slice Precise Seam modifiers in the frame the object wa
|
||||
{ "belt_printer", 1 },
|
||||
{ "belt_slice_rotation", "x" },
|
||||
{ "belt_slice_rotation_angle", 45 },
|
||||
{ "belt_slice_rotation_global", 1 },
|
||||
{ "layer_height", 0.2 },
|
||||
{ "initial_layer_print_height", 0.2 },
|
||||
{ "skirt_loops", 0 },
|
||||
|
||||
+116
-19
@@ -30,6 +30,10 @@
|
||||
#include "libslic3r/BuildVolume.hpp"
|
||||
#include "libslic3r/Support/TreeModelVolumes.hpp"
|
||||
#include "libslic3r/Support/TreeSupportCommon.hpp"
|
||||
#include "libslic3r/Support/BeltFloorContext.hpp"
|
||||
#include "libslic3r/ExtrusionEntity.hpp"
|
||||
#include "libslic3r/Polyline.hpp"
|
||||
#include <limits>
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/GCodeReader.hpp"
|
||||
@@ -765,7 +769,6 @@ TEST_CASE("Belt printers never start a scarf seam below the layer", "[Print][bel
|
||||
{ "belt_printer", 1 },
|
||||
{ "belt_slice_rotation", "x" },
|
||||
{ "belt_slice_rotation_angle", 45 },
|
||||
{ "belt_slice_rotation_global", 1 },
|
||||
{ "gcode_remap_x", "rev_x" },
|
||||
{ "gcode_remap_y", "pos_z" },
|
||||
{ "gcode_remap_z", "pos_y" },
|
||||
@@ -815,7 +818,6 @@ TEST_CASE("Belt printers refuse an object taller than the gantry clearance", "[P
|
||||
{ "belt_printer", 1 },
|
||||
{ "belt_slice_rotation", "x" },
|
||||
{ "belt_slice_rotation_angle", 45 },
|
||||
{ "belt_slice_rotation_global", 1 },
|
||||
{ "gcode_remap_x", "rev_x" },
|
||||
{ "gcode_remap_y", "pos_z" },
|
||||
{ "gcode_remap_z", "pos_y" },
|
||||
@@ -851,7 +853,6 @@ TEST_CASE("Belt printers keep the part fan off within the band above the belt",
|
||||
{ "belt_printer", 1 },
|
||||
{ "belt_slice_rotation", "x" },
|
||||
{ "belt_slice_rotation_angle", 45 },
|
||||
{ "belt_slice_rotation_global", 1 },
|
||||
{ "gcode_remap_x", "rev_x" },
|
||||
{ "gcode_remap_y", "pos_z" },
|
||||
{ "gcode_remap_z", "pos_y" },
|
||||
@@ -930,7 +931,6 @@ TEST_CASE("Belt printers slice organic tree supports that reach the belt", "[Pri
|
||||
{ "belt_printer", 1 },
|
||||
{ "belt_slice_rotation", "x" },
|
||||
{ "belt_slice_rotation_angle", 45 },
|
||||
{ "belt_slice_rotation_global", 1 },
|
||||
{ "gcode_remap_x", "rev_x" },
|
||||
{ "gcode_remap_y", "pos_z" },
|
||||
{ "gcode_remap_z", "pos_y" },
|
||||
@@ -1003,7 +1003,6 @@ static DynamicPrintConfig belt_test_config()
|
||||
{ "belt_printer", 1 },
|
||||
{ "belt_slice_rotation", "x" },
|
||||
{ "belt_slice_rotation_angle", 45 },
|
||||
{ "belt_slice_rotation_global", 1 },
|
||||
{ "gcode_remap_x", "rev_x" },
|
||||
{ "gcode_remap_y", "pos_z" },
|
||||
{ "gcode_remap_z", "pos_y" },
|
||||
@@ -1039,30 +1038,19 @@ TEST_CASE("Belt-only keys at non-default values leave non-belt G-code unchanged"
|
||||
// Every belt key a profile can carry, at a value that would change a belt print.
|
||||
// belt_printer stays off, so none of them may reach the G-code: the axis remaps are
|
||||
// gated on belt mode, the rest is only read on belt printers. build_plate_tilt_x/y
|
||||
// and an explicit first_layer_plane are features of their own on a flat bed and are
|
||||
// left alone here; "leading_edge_only" prints as an outer brim by design.
|
||||
// is a feature of its own on a flat bed and is left alone here; "leading_edge_only"
|
||||
// prints as an outer brim by design.
|
||||
config.set_deserialize_strict({
|
||||
{ "belt_printer", 0 },
|
||||
{ "belt_printer_infinite_y", 0 },
|
||||
{ "belt_slice_rotation", "y" },
|
||||
{ "belt_slice_rotation_angle", 30 },
|
||||
{ "belt_slice_rotation_global", 0 },
|
||||
{ "belt_preslice_global", 0 },
|
||||
{ "preslice_remap_x", "pos_x" },
|
||||
{ "preslice_remap_y", "pos_z" },
|
||||
{ "preslice_remap_z", "neg_y" },
|
||||
{ "preslice_remap_global", 1 },
|
||||
{ "gcode_remap_x", "rev_x" },
|
||||
{ "gcode_remap_y", "pos_z" },
|
||||
{ "gcode_remap_z", "pos_y" },
|
||||
{ "gcode_back_transform", 0 },
|
||||
{ "belt_frame_tilt_decouple", 1 },
|
||||
{ "belt_frame_tilt_angle", 30 },
|
||||
{ "first_layer_plane_offset", 1 },
|
||||
{ "first_layer_plane_thickness", 1 },
|
||||
{ "belt_support_floor_offset", -5 },
|
||||
{ "belt_support_floor_mode", "none" },
|
||||
{ "belt_support_z_offset_mode", "raft_only" },
|
||||
{ "enable_belt_purge_tower", 1 },
|
||||
{ "belt_purge_tower_width", 10 },
|
||||
{ "leading_brim_length", 10 },
|
||||
@@ -1136,7 +1124,6 @@ TEST_CASE("A support-only change on a belt purge print matches a fresh slice", "
|
||||
{ "belt_printer", 1 },
|
||||
{ "belt_slice_rotation", "x" },
|
||||
{ "belt_slice_rotation_angle", 45 },
|
||||
{ "belt_slice_rotation_global", 1 },
|
||||
{ "gcode_remap_x", "rev_x" },
|
||||
{ "gcode_remap_y", "pos_z" },
|
||||
{ "gcode_remap_z", "pos_y" },
|
||||
@@ -1255,3 +1242,113 @@ TEST_CASE("Organic tree supports place a support blocker at its own height above
|
||||
CHECK(collides(last + 1));
|
||||
CHECK(collides(last + num_raft));
|
||||
}
|
||||
|
||||
// A part with an overhang on its LEADING side (the end that prints first) needs
|
||||
// supports below the object's own lowest slicing layer: the belt under that overhang
|
||||
// is reached before the object's first contact with it, so the support layers sit at
|
||||
// a lower slicing Z than any object layer. A generator that stops at the object's
|
||||
// first layer, or at global Z = 0, leaves those supports floating above the belt.
|
||||
TEST_CASE("Belt supports reach the belt under a leading overhang", "[Print][belt][Support][Regression]")
|
||||
{
|
||||
// default resolves to organic for tree support; tree_hybrid is the classic tree.
|
||||
const char *support_type = GENERATE("normal(auto)", "tree(auto)");
|
||||
const char *support_style = GENERATE("default", "organic", "tree_hybrid");
|
||||
if (std::string(support_type) == "normal(auto)" && std::string(support_style) != "default")
|
||||
return; // organic and tree_hybrid are tree styles
|
||||
DYNAMIC_SECTION(support_type << " / " << support_style) {
|
||||
// A 20 mm cube with a 2 mm thick fin that leaves its top edge and reaches
|
||||
// 20 mm toward -Y, the end of the part that prints first, climbing at 45 deg
|
||||
// as it goes (from z = 18 at the cube to z = 38 at the tip). With the layers
|
||||
// leaning toward -Y at 45 deg the fin's underside is parallel to the layers:
|
||||
// a ceiling 20 x 28 mm in one layer, with nothing but air between it and the
|
||||
// belt, which lies up to 41 mm (of slicing Z) below the object's own lowest
|
||||
// point. Support has to span all of it.
|
||||
indexed_triangle_set its = its_make_cube(20., 20., 20.);
|
||||
indexed_triangle_set fin = its_make_cube(20., 20., 2.);
|
||||
Transform3d shear = Transform3d::Identity();
|
||||
shear.matrix() << 1., 0., 0., 0.,
|
||||
0., 1., 0., -20.,
|
||||
0., -1., 1., 38.,
|
||||
0., 0., 0., 1.;
|
||||
its_transform(fin, shear);
|
||||
its_merge(its, fin);
|
||||
TriangleMesh mesh(std::move(its));
|
||||
|
||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({
|
||||
{ "belt_printer", 1 },
|
||||
{ "belt_slice_rotation", "x" },
|
||||
{ "belt_slice_rotation_angle", 45 },
|
||||
{ "gcode_remap_x", "rev_x" },
|
||||
{ "gcode_remap_y", "pos_z" },
|
||||
{ "gcode_remap_z", "pos_y" },
|
||||
{ "layer_height", 0.2 },
|
||||
{ "initial_layer_print_height", 0.2 },
|
||||
{ "skirt_loops", 0 },
|
||||
{ "z_hop", 0 },
|
||||
{ "enable_support", 1 },
|
||||
{ "support_type", support_type },
|
||||
{ "support_style", support_style },
|
||||
{ "support_threshold_angle", 30 },
|
||||
{ "machine_start_gcode", "T[initial_tool]\n" },
|
||||
{ "layer_change_gcode", "G92 E0\n" },
|
||||
});
|
||||
Print print;
|
||||
Model model;
|
||||
init_print({ mesh }, print, model, config);
|
||||
// On the bed, not at its corner: organic tree support clips its branches to
|
||||
// the bed outline, and 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();
|
||||
REQUIRE(! object.layers().empty());
|
||||
// The whole part is sliced: the layers lean at 45 deg, so the part spans
|
||||
// (y + z) / sqrt(2) of slicing Z, and every layer in that span has geometry.
|
||||
{
|
||||
double lo = std::numeric_limits<double>::max(), hi = std::numeric_limits<double>::lowest();
|
||||
for (const stl_vertex &v : mesh.its.vertices) {
|
||||
lo = std::min<double>(lo, v.y() + v.z());
|
||||
hi = std::max<double>(hi, v.y() + v.z());
|
||||
}
|
||||
const double span = (hi - lo) / std::sqrt(2.);
|
||||
size_t nonempty = 0;
|
||||
for (const Layer *layer : object.layers())
|
||||
if (! layer->lslices.empty())
|
||||
++ nonempty;
|
||||
INFO("non-empty object layers " << nonempty << ", slicing span " << span << " mm");
|
||||
CHECK(double(nonempty) * 0.2 > span - 0.6);
|
||||
}
|
||||
BeltFloorContext floor;
|
||||
REQUIRE(floor.init(object.slicing_parameters(), print.config()));
|
||||
|
||||
// The lowest support layer that prints anything, and the belt floor beneath it.
|
||||
const SupportLayer *lowest = nullptr;
|
||||
for (const SupportLayer *layer : object.support_layers())
|
||||
if (! layer->support_fills.empty() && (lowest == nullptr || layer->print_z < lowest->print_z))
|
||||
lowest = layer;
|
||||
REQUIRE(lowest != nullptr);
|
||||
double floor_under_lowest = std::numeric_limits<double>::max();
|
||||
for (const ExtrusionEntity *entity : lowest->support_fills.flatten().entities)
|
||||
for (const Polyline &pl : entity->as_polylines())
|
||||
for (const Point &pt : pl.points)
|
||||
floor_under_lowest = std::min(floor_under_lowest, floor.floor_print_z(pt));
|
||||
// The object's lowest geometry. The slicing frame starts at the lowest
|
||||
// belt-floor point under the footprint, so the layers below the leading
|
||||
// tip of the overhang are empty.
|
||||
double first_object_z = std::numeric_limits<double>::max();
|
||||
for (const Layer *layer : object.layers())
|
||||
if (! layer->lslices.empty()) { first_object_z = layer->print_z; break; }
|
||||
REQUIRE(first_object_z < std::numeric_limits<double>::max());
|
||||
INFO("lowest support z " << lowest->print_z << ", floor under it " << floor_under_lowest
|
||||
<< ", first object layer " << first_object_z);
|
||||
// Well below the object's own lowest layer (the belt under the tip of the fin
|
||||
// is ~41 mm of slicing Z below the cube's leading edge, which rests on it)...
|
||||
CHECK(lowest->print_z < first_object_z - 5.);
|
||||
// ...and resting on the belt: within a few layers of the floor beneath its own lines.
|
||||
CHECK(lowest->print_z - floor_under_lowest < 4. * 0.2 + EPSILON);
|
||||
CHECK(lowest->print_z - floor_under_lowest > -0.2 - EPSILON);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -641,7 +641,6 @@ static DynamicPrintConfig belt_brim_config()
|
||||
{ "belt_printer", 1 },
|
||||
{ "belt_slice_rotation", "x" },
|
||||
{ "belt_slice_rotation_angle", 45 },
|
||||
{ "belt_slice_rotation_global", 1 },
|
||||
{ "gcode_remap_x", "rev_x" },
|
||||
{ "gcode_remap_y", "pos_z" },
|
||||
{ "gcode_remap_z", "pos_y" },
|
||||
@@ -667,7 +666,6 @@ static DynamicPrintConfig belt_brim_multifilament_config(unsigned int filaments,
|
||||
{ "belt_printer", 1 },
|
||||
{ "belt_slice_rotation", "x" },
|
||||
{ "belt_slice_rotation_angle", 45 },
|
||||
{ "belt_slice_rotation_global", 1 },
|
||||
{ "gcode_remap_x", "rev_x" },
|
||||
{ "gcode_remap_y", "pos_z" },
|
||||
{ "gcode_remap_z", "pos_y" },
|
||||
@@ -717,9 +715,10 @@ static double first_role_z(const std::string &gcode, const std::string &role)
|
||||
return z;
|
||||
}
|
||||
|
||||
// Number of object layers that carry a belt brim band. Each such band is emitted as one
|
||||
// contiguous brim pass, so for a single object whose first-contact layer carries a band
|
||||
// (the apron prologue folds into that layer's pass) this equals role_passes(gcode, "brim").
|
||||
// Number of object layers that carry a belt brim band. Every band prints at its own
|
||||
// layer Z, so this equals role_layers(gcode, "brim") (plus any apron bands below the
|
||||
// first object layer). It is not a pass count: the bands on the empty lead-in layers
|
||||
// ahead of the object's first contact print back to back, so they fold into one pass.
|
||||
static int nonempty_belt_brim_layers(const PrintObject &object)
|
||||
{
|
||||
int n = 0;
|
||||
@@ -729,6 +728,30 @@ static int nonempty_belt_brim_layers(const PrintObject &object)
|
||||
return n;
|
||||
}
|
||||
|
||||
// Number of distinct Z heights at which `role` extrudes: one per layer that prints it.
|
||||
static int role_layers(const std::string &gcode, const std::string &role)
|
||||
{
|
||||
std::set<long> zs;
|
||||
GCodeReader reader;
|
||||
reader.parse_buffer(gcode, [&](GCodeReader &self, const GCodeReader::GCodeLine &line) {
|
||||
if (! line.extruding(self) || line.dist_XY(self) <= EPSILON)
|
||||
return;
|
||||
if (line.comment().find(role) != std::string_view::npos)
|
||||
zs.insert(std::lround(self.z() * 1000.));
|
||||
});
|
||||
return int(zs.size());
|
||||
}
|
||||
|
||||
// Apron bands below the object's first layer that print something.
|
||||
static int belt_brim_apron_bands(const PrintObject &object)
|
||||
{
|
||||
int n = 0;
|
||||
for (const BeltBrimBand &band : object.belt_brim_prologue())
|
||||
if (! band.fills.empty())
|
||||
++ n;
|
||||
return n;
|
||||
}
|
||||
|
||||
// For each active tool, the ordinal (1-based, over extruding moves) of the FIRST move whose
|
||||
// role comment contains `role`. Lets a per-object ordering check key off the object's
|
||||
// unique wall filament.
|
||||
@@ -803,12 +826,15 @@ TEST_CASE("Belt brim on an object layer precedes its perimeters, once", "[SkirtB
|
||||
CHECK(seq[0] == "brim");
|
||||
CHECK(seq[1] == "perimeter");
|
||||
|
||||
// Exactly once: every band is one contiguous pass (the apron prologue folds into the
|
||||
// first layer's), so the pass count equals the number of layers carrying a band - not
|
||||
// twice it, which double-emission would give, nor fewer, which a dropped band would.
|
||||
const int bands = nonempty_belt_brim_layers(*print.objects().front());
|
||||
// Exactly once: every band prints at its own layer Z, so the number of Z heights with
|
||||
// brim equals the number of bands - not fewer, which a dropped band would give. (A
|
||||
// double emission would print twice at one Z: the pass count below catches that for
|
||||
// the bands that sit on layers with perimeters.)
|
||||
const PrintObject &object = *print.objects().front();
|
||||
const int bands = nonempty_belt_brim_layers(object);
|
||||
REQUIRE(bands > 0);
|
||||
CHECK(role_passes(gc, "brim") == bands);
|
||||
CHECK(role_layers(gc, "brim") == bands + belt_brim_apron_bands(object));
|
||||
CHECK(role_passes(gc, "brim") <= bands);
|
||||
}
|
||||
|
||||
// B - single extruder (filament id 1). Every band must survive the 1-based -> 0-based
|
||||
@@ -828,9 +854,10 @@ TEST_CASE("Belt brim on a single extruder emits every band once", "[SkirtBrim][b
|
||||
init_print({ cube(20) }, print, model, config);
|
||||
const std::string gc = gcode(print);
|
||||
|
||||
const int expected = nonempty_belt_brim_layers(*print.objects().front());
|
||||
const PrintObject &object = *print.objects().front();
|
||||
const int expected = nonempty_belt_brim_layers(object) + belt_brim_apron_bands(object);
|
||||
REQUIRE(expected > 0);
|
||||
CHECK(role_passes(gc, "brim") == expected);
|
||||
CHECK(role_layers(gc, "brim") == expected);
|
||||
CHECK(belt_tools_for_role(gc, "brim") == std::set<int>{ 0 }); // filament 1 -> tool 0
|
||||
}
|
||||
|
||||
@@ -1233,51 +1260,6 @@ TEST_CASE("Belt apron survives another object printing at the same Z", "[SkirtBr
|
||||
CHECK(two >= 1.8 * one);
|
||||
}
|
||||
|
||||
TEST_CASE("Belt brim allows instances placed across the belt", "[SkirtBrim][belt]")
|
||||
{
|
||||
// Only movement ALONG the belt changes an instance's belt-floor Z, so copies placed
|
||||
// side by side ACROSS it share one set of bands and must still get a brim. The first
|
||||
// version of this guard refused every multi-instance object outright, silently
|
||||
// dropping the brim.
|
||||
//
|
||||
// The global belt flags are off here so the instances stay in one PrintObject; with
|
||||
// them on, PrintApply splits each instance into its own object and the case cannot
|
||||
// arise at all.
|
||||
auto multi_instance_has_brim = [](double dx, double dy) {
|
||||
DynamicPrintConfig config = belt_brim_config();
|
||||
config.set_deserialize_strict({
|
||||
{ "belt_slice_rotation_global", 0 },
|
||||
{ "belt_preslice_global", 0 },
|
||||
{ "preslice_remap_global", 0 },
|
||||
{ "brim_type", "outer_only" },
|
||||
{ "brim_width", 4 },
|
||||
{ "brim_object_gap", 0 },
|
||||
});
|
||||
Print print;
|
||||
Model model;
|
||||
ModelObject *object = model.add_object();
|
||||
object->name += "object.stl";
|
||||
object->add_volume(cube(20));
|
||||
object->add_instance()->set_offset(Vec3d(80., 80., 0.));
|
||||
object->add_instance()->set_offset(Vec3d(80. + dx, 80. + dy, 0.));
|
||||
object->ensure_on_bed();
|
||||
print.auto_assign_extruders(object);
|
||||
print.apply(model, config);
|
||||
print.validate();
|
||||
print.set_status_silent();
|
||||
print.process();
|
||||
REQUIRE(print.objects().size() == 1);
|
||||
REQUIRE(print.objects().front()->instances().size() == 2);
|
||||
return print.objects().front()->has_belt_brim();
|
||||
};
|
||||
|
||||
// X is across the belt when the tilt is about X, since the shear then runs along Y.
|
||||
CHECK(multi_instance_has_brim(40., 0.));
|
||||
// Y is along the belt: the copies sit at different belt heights and would each need
|
||||
// their own bands, so the brim is refused (and validate() warns).
|
||||
CHECK_FALSE(multi_instance_has_brim(0., 40.));
|
||||
}
|
||||
|
||||
TEST_CASE("Belt brim coexists with support material", "[SkirtBrim][belt]")
|
||||
{
|
||||
// Supports put extra layers into the same z stream as the apron bands, which is what
|
||||
|
||||
Reference in New Issue
Block a user