Part 2.6: Add belt floor support clipping for all support types

- Fix support clipping z-shift calculation by removing coordinate-space
  mismatch and sync belt_floor_z_shift with global_z_offset; fix
  invalidation so posSupportMaterial no longer resets slicing params
- Add belt floor polygon clipping to non-organic tree support
  (slim/strong/hybrid) with collision surface integration in
  TreeSupportData, belt extension layers, and first-layer brim
  suppression
- Add belt floor clipping to organic tree support pipeline with virtual
  belt raft layers, per-layer polygons in TreeModelVolumes, and
  post-generation layer trimming; fix pre-existing processing_last_mesh
  bug in calculateCollision()

Fix belt floor support clipping: z-shift, invalidation, and global offset

- Fix support clipping z-shift calculation by removing coordinate-space
  mismatch (raw_bounding_box min.z vs trafo_centered m_belt_min_z) and
  sync belt_floor_z_shift with global_z_offset in global shear mode
- Fix invalidation so posSupportMaterial no longer resets slicing params,
  preventing the exact posSlice z-shift from being overwritten by the
  bounding-box approximation on support-only setting changes
- Remove double-counting of global z_offset on support layers — support
  already inherits the offset from object layers during generation

This Work Was Co-Authored-By Claude Opus 4.6 (1M context) <noreply@anthropic.com>

UI: gray out inactive belt sub-options, rename to mesh transforms, move to Advanced

Fix mesh clipping through build plate after belt shear/scale transform

Generalize G-code viewer designed-view toggle for full belt transform

Clip support layers to transformed belt floor plane

Supports below the tilted build plate (Z = shear_factor * from_axis - min_z)
are now clipped via half-plane intersection after generation. Belt floor
parameters stored in SlicingParameters and populated in both update_slicing_parameters()
and the static slicing_parameters() overload.

Make belt G-code viewer toggle more prominent, add B keyboard shortcut

- Add separator + teal "Belt Printer" header in legend panel
- Append [B] hint to checkbox label
- Add B key shortcut in GLCanvas3D to toggle designed/machine view
- Read belt_printer_angle from loaded G-code headers to enable belt view

Add per-axis global transform option for belt printer shear

New belt_shear_{x,y,z}_global bool configs. When enabled, shear incorporates
instance shift so objects at different bed positions get position-aware
transform (Z += factor * instance_shift_on_from_axis).

Fix global shear: use layer Z offset instead of mesh transform, add config invalidation

- Global shear offset applied as post-slicing layer print_z adjustment
  instead of mesh transform (which was absorbed by min_z normalization
  or shifted mesh out of slice range)
- Register all belt transform options in Print::invalidate_state_by_config_options
  to trigger posSlice re-slicing (the fallback only invalidated Print steps,
  not PrintObject steps — belt changes had no effect without manual re-slice)
- Belt gcode remap options added to steps_gcode (gcode-export only)
- Skip empty-first-layer check for belt objects with global Z offset

WIP: split instances for global shear, relative Z offsets, debug logging

- PrintApply: when belt global mode active, prevent instance grouping by
  adding unique Z perturbation to trafo — each copy becomes its own
  PrintObject with independent layers
- PrintObjectSlice: compute global Z offset relative to minimum Y shift
  across all PrintObjects (lowest-Y object stays at Z=0)
- Debug logging (warning level) for belt global shift values and offsets

Known issues:
- Cached posSlice results cause stale offsets when mixing copies with
  individually-added objects — need to compute min baseline outside slice()
- Supports still generate to Z=0 instead of object's global Z offset

Fix global shear for copied objects: disable shared-object layer optimization

When belt global Z shear is active, each object needs unique layer Z
values based on its bed position. The shared-object optimization was
causing copies to reuse the source object's layers (and its Z offset)
instead of computing their own position-based offset.

started work on getting supports to work properly

one step forward, one step back

this version didn't quite work.  Getting somewhere though

about to add UI controllable tests

added configuration options for supports

tweak CLAUDE.md to be more aggressive for my machine.  This commit should probably be pulled out before contributing upstream

still chasing down some bugs

moving objects between slices no longer results in improper Z-height because of caching

added more data to the debug logs

Z offset is getting more global again

still not quite there, I think there's a fundamental logic flaw?

hunting for bugs

finally have a functional fix

Add belt floor clipping to tree supports (organic and non-organic)

- Add belt floor polygon clipping to non-organic tree support
  (slim/strong/hybrid) in draw_circles() and terminate nodes at the
  belt surface instead of the horizontal build plate
- Add belt floor clipping to organic tree support pipeline with virtual
  belt raft layers for sub-floor branch generation, per-layer belt
  floor polygons in TreeModelVolumes, and post-generation layer trimming
- Fix pre-existing processing_last_mesh bug in TreeModelVolumes that
  prevented m_anti_overhang (support blockers) from ever being applied;
  skip empty first layer check for belt printers

Commits:

current approach: make a face surface to build supports to

closer!

supports now terminate on shear plane, now need to get shear plane to correct Z height

nearly there

chasing down logic issues still

committing for checkpoint, this still does not work

still got logic problems...

cull support clipping

stashing changes for now.  Going to focus on getting the global shear OFF support generation dialed first.

beginning per object shear calcs

Local shear transform is on correct Z offset now

local shear finally works now and needs more testing

global shear works now, needs thorough testing

debugging non-45 degree angles

debugging part 2

supports at all angles work now

remove debug logging

Add belt floor collision to non-organic tree support pipeline

- Integrate belt floor as a collision surface in TreeSupportData so
  branches route around the belt naturally, replacing the explicit
  termination checks in drop_nodes()
- Add belt extension layers below the object after draw_circles() to
  allow support geometry to extend to the diagonal belt surface instead
  of terminating at a horizontal first layer
- Fix coordinate overflow in belt floor polygons (scale_(1e4) exceeds
  int32), skip first-layer brim expansion for belt printers, and
  extend empty first layer check bypass to all belt modes

add debug logging, Z translate for tree supports

still not seeing any cutoff surface yet

adding debug options

attempt #2 at trees

if hit Z buildplate stop but don't set to_buildplate true

getting closer

tree support almost there, just need to get rid of the circles at the beginning

getting closer

belt / shear plane clip works, need to figure out the buidlplate plane issues

more logic, added debugging logs

supports now extend somewhat below Z=0 in global shear mode

fix bad alloc, add 10mm below build plate

fully works now

shear transform + prusa tree support generation works now.

pull out debug logging
This commit is contained in:
harrierpigeon
2026-03-30 13:25:40 -05:00
committed by Joseph Robertson
parent 719af2d81d
commit 7ff6bc42b1
27 changed files with 1337 additions and 137 deletions
+1 -21
View File
@@ -23,27 +23,7 @@ cmake --build build/arm64 --config RelWithDebInfo --target all --
### Building on Linux
**Always use this command to build the project when testing build issues on Linux.**
```bash
cmake --build build/arm64 --config RelWithDebInfo --target all --
```
### Build test:
**Always use this command to build the project when testing build issues on Windows.**
```bash
cmake --build . --config %build_type% --target ALL_BUILD -- -m
```
### Building on macOS
**Always use this command to build the project when testing build issues on macOS.**
```bash
cmake --build build/arm64 --config RelWithDebInfo --target all --
```
### Building on Linux
**Always use this command to build the project when testing build issues on Linux.**
```bash
cmake --build build --config RelWithDebInfo --target all --
systemd-run --user --scope -p MemoryMax=48G cmake --build build --config RelWithDebInfo --target all -- -j18 -l 24
```
+1 -1
View File
@@ -84,7 +84,7 @@ public:
indexed_triangle_set bounding_mesh(bool scale=true) const;
// Center of the print bed, unscaled.
Vec2d bed_center() const { return to_2d(m_bboxf.center()); }
Vec2d bed_center() const { return get_extents(m_bed_shape).center(); }
// Convex hull of polygon(), scaled.
const Polygon& convex_hull() const { return m_convex_hull; }
// Smallest enclosing circle of polygon(), scaled.
+28 -2
View File
@@ -1745,8 +1745,16 @@ std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec
// Check that there are extrusions on the very first layer. The case with empty
// first layer may result in skirt/brim in the air and maybe other issues.
// Skip this check for belt printers. The shear transform tilts the
// model so the first horizontal layer plane intersects only a thin
// sliver of the model (width ≈ first_layer_height / shear_factor).
// This sliver is often narrower than the nozzle diameter, producing
// zero perimeters and an empty first layer — which is expected, not
// an error. In global shear mode the object may also start above
// Z=0 on the tilted belt surface.
if (layers_to_print.size() == 1u) {
if (!has_extrusions)
bool skip_empty_check = object.print()->config().belt_printer.value;
if (!has_extrusions && !skip_empty_check)
throw Slic3r::SlicingError(_(L("One object has an empty first layer and can't be printed. Please Cut the bottom or enable supports.")), object.id().id);
}
@@ -2515,9 +2523,27 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
file.write_format("; HEADER_BLOCK_START\n");
// Write information on the generator.
file.write_format("; generated by %s on %s\n", Slic3r::header_slic3r_generated().c_str(), Slic3r::Utils::local_timestamp().c_str());
// Belt printer: embed angle in header for G-code processor detection.
// Belt printer: embed angle and transform configs in header for G-code processor detection.
if (print.config().belt_printer.value) {
file.write_format("; belt_printer_angle = %.1f\n", print.config().belt_printer_angle.value);
// Shear configs
const auto &full_cfg = print.full_print_config();
file.write_format("; belt_shear_x = %s\n", full_cfg.opt_serialize("belt_shear_x").c_str());
file.write_format("; belt_shear_x_angle = %.1f\n", print.config().belt_shear_x_angle.value);
file.write_format("; belt_shear_x_from = %s\n", full_cfg.opt_serialize("belt_shear_x_from").c_str());
file.write_format("; belt_shear_y = %s\n", full_cfg.opt_serialize("belt_shear_y").c_str());
file.write_format("; belt_shear_y_angle = %.1f\n", print.config().belt_shear_y_angle.value);
file.write_format("; belt_shear_y_from = %s\n", full_cfg.opt_serialize("belt_shear_y_from").c_str());
file.write_format("; belt_shear_z = %s\n", full_cfg.opt_serialize("belt_shear_z").c_str());
file.write_format("; belt_shear_z_angle = %.1f\n", print.config().belt_shear_z_angle.value);
file.write_format("; belt_shear_z_from = %s\n", full_cfg.opt_serialize("belt_shear_z_from").c_str());
// Scale configs
file.write_format("; belt_scale_x = %s\n", full_cfg.opt_serialize("belt_scale_x").c_str());
file.write_format("; belt_scale_x_angle = %.1f\n", print.config().belt_scale_x_angle.value);
file.write_format("; belt_scale_y = %s\n", full_cfg.opt_serialize("belt_scale_y").c_str());
file.write_format("; belt_scale_y_angle = %.1f\n", print.config().belt_scale_y_angle.value);
file.write_format("; belt_scale_z = %s\n", full_cfg.opt_serialize("belt_scale_z").c_str());
file.write_format("; belt_scale_z_angle = %.1f\n", print.config().belt_scale_z_angle.value);
}
if (is_bbl_printers)
file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder).c_str());
+78
View File
@@ -3051,6 +3051,84 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers
} catch (...) {}
return;
}
// Belt printer: parse shear configs from header comments.
{
auto parse_shear_mode = [](const std::string &s) -> BeltShearMode {
if (s == "pos_cot") return BeltShearMode::PosCot;
if (s == "neg_cot") return BeltShearMode::NegCot;
if (s == "pos_tan") return BeltShearMode::PosTan;
if (s == "neg_tan") return BeltShearMode::NegTan;
return BeltShearMode::None;
};
auto parse_axis = [](const std::string &s) -> BeltAxis {
if (s == "y") return BeltAxis::Y;
if (s == "z") return BeltAxis::Z;
return BeltAxis::X;
};
auto parse_scale_mode = [](const std::string &s) -> BeltScaleMode {
if (s == "inv_sin") return BeltScaleMode::InvSin;
if (s == "inv_cos") return BeltScaleMode::InvCos;
if (s == "sin") return BeltScaleMode::Sin;
if (s == "cos") return BeltScaleMode::Cos;
return BeltScaleMode::None;
};
auto trim = [](const std::string &s) -> std::string {
size_t start = s.find_first_not_of(" \t\r\n");
size_t end = s.find_last_not_of(" \t\r\n");
return (start == std::string::npos) ? "" : s.substr(start, end - start + 1);
};
// Shear X
if (boost::starts_with(comment, " belt_shear_x = ")) {
m_result.belt_shear_x = parse_shear_mode(trim(std::string(comment.substr(16)))); return;
}
if (boost::starts_with(comment, " belt_shear_x_angle = ")) {
try { m_result.belt_shear_x_angle = std::stof(std::string(comment.substr(22))); } catch (...) {} return;
}
if (boost::starts_with(comment, " belt_shear_x_from = ")) {
m_result.belt_shear_x_from = parse_axis(trim(std::string(comment.substr(21)))); return;
}
// Shear Y
if (boost::starts_with(comment, " belt_shear_y = ")) {
m_result.belt_shear_y = parse_shear_mode(trim(std::string(comment.substr(16)))); return;
}
if (boost::starts_with(comment, " belt_shear_y_angle = ")) {
try { m_result.belt_shear_y_angle = std::stof(std::string(comment.substr(22))); } catch (...) {} return;
}
if (boost::starts_with(comment, " belt_shear_y_from = ")) {
m_result.belt_shear_y_from = parse_axis(trim(std::string(comment.substr(21)))); return;
}
// Shear Z
if (boost::starts_with(comment, " belt_shear_z = ")) {
m_result.belt_shear_z = parse_shear_mode(trim(std::string(comment.substr(16)))); return;
}
if (boost::starts_with(comment, " belt_shear_z_angle = ")) {
try { m_result.belt_shear_z_angle = std::stof(std::string(comment.substr(22))); } catch (...) {} return;
}
if (boost::starts_with(comment, " belt_shear_z_from = ")) {
m_result.belt_shear_z_from = parse_axis(trim(std::string(comment.substr(21)))); return;
}
// Scale X
if (boost::starts_with(comment, " belt_scale_x = ")) {
m_result.belt_scale_x = parse_scale_mode(trim(std::string(comment.substr(16)))); return;
}
if (boost::starts_with(comment, " belt_scale_x_angle = ")) {
try { m_result.belt_scale_x_angle = std::stof(std::string(comment.substr(22))); } catch (...) {} return;
}
// Scale Y
if (boost::starts_with(comment, " belt_scale_y = ")) {
m_result.belt_scale_y = parse_scale_mode(trim(std::string(comment.substr(16)))); return;
}
if (boost::starts_with(comment, " belt_scale_y_angle = ")) {
try { m_result.belt_scale_y_angle = std::stof(std::string(comment.substr(22))); } catch (...) {} return;
}
// Scale Z
if (boost::starts_with(comment, " belt_scale_z = ")) {
m_result.belt_scale_z = parse_scale_mode(trim(std::string(comment.substr(16)))); return;
}
if (boost::starts_with(comment, " belt_scale_z_angle = ")) {
try { m_result.belt_scale_z_angle = std::stof(std::string(comment.substr(22))); } catch (...) {} return;
}
}
// wipe start tag
if (boost::starts_with(comment, reserved_tag(ETags::Wipe_Start))) {
m_wiping = true;
+33
View File
@@ -229,6 +229,23 @@ class Print;
float z_offset;
// Belt printer: angle for coordinate transformation in preview.
float belt_printer_angle{ 0.f };
// Belt printer: per-axis shear config.
BeltShearMode belt_shear_x{ BeltShearMode::None };
float belt_shear_x_angle{ 45.f };
BeltAxis belt_shear_x_from{ BeltAxis::Y };
BeltShearMode belt_shear_y{ BeltShearMode::None };
float belt_shear_y_angle{ 45.f };
BeltAxis belt_shear_y_from{ BeltAxis::Y };
BeltShearMode belt_shear_z{ BeltShearMode::None };
float belt_shear_z_angle{ 45.f };
BeltAxis belt_shear_z_from{ BeltAxis::Y };
// Belt printer: per-axis scale config.
BeltScaleMode belt_scale_x{ BeltScaleMode::None };
float belt_scale_x_angle{ 45.f };
BeltScaleMode belt_scale_y{ BeltScaleMode::None };
float belt_scale_y_angle{ 45.f };
BeltScaleMode belt_scale_z{ BeltScaleMode::None };
float belt_scale_z_angle{ 45.f };
SettingsIds settings_ids;
size_t filaments_count;
bool backtrace_enabled;
@@ -288,6 +305,22 @@ class Print;
layer_filaments = other.layer_filaments;
filament_change_count_map = other.filament_change_count_map;
initial_layer_time = other.initial_layer_time;
belt_printer_angle = other.belt_printer_angle;
belt_shear_x = other.belt_shear_x;
belt_shear_x_angle = other.belt_shear_x_angle;
belt_shear_x_from = other.belt_shear_x_from;
belt_shear_y = other.belt_shear_y;
belt_shear_y_angle = other.belt_shear_y_angle;
belt_shear_y_from = other.belt_shear_y_from;
belt_shear_z = other.belt_shear_z;
belt_shear_z_angle = other.belt_shear_z_angle;
belt_shear_z_from = other.belt_shear_z_from;
belt_scale_x = other.belt_scale_x;
belt_scale_x_angle = other.belt_scale_x_angle;
belt_scale_y = other.belt_scale_y;
belt_scale_y_angle = other.belt_scale_y_angle;
belt_scale_z = other.belt_scale_z;
belt_scale_z_angle = other.belt_scale_z_angle;
#if ENABLE_GCODE_VIEWER_STATISTICS
time = other.time;
#endif
+6 -4
View File
@@ -1010,11 +1010,13 @@ static std::vector<std::string> s_Preset_machine_limits_options {
static std::vector<std::string> s_Preset_printer_options {
"printer_technology",
"printable_area", "extruder_printable_area", "bed_exclude_area","bed_custom_texture", "bed_custom_model", "build_plate_tilt_x", "build_plate_tilt_y", "belt_printer", "belt_printer_angle", "belt_printer_infinite_y", "belt_shear_x", "belt_shear_x_angle", "belt_shear_x_from",
"belt_shear_y", "belt_shear_y_angle", "belt_shear_y_from",
"belt_shear_z", "belt_shear_z_angle", "belt_shear_z_from",
"printable_area", "extruder_printable_area", "bed_exclude_area","bed_custom_texture", "bed_custom_model", "build_plate_tilt_x", "build_plate_tilt_y", "belt_printer", "belt_printer_angle", "belt_printer_infinite_y", "belt_shear_x", "belt_shear_x_angle", "belt_shear_x_from", "belt_shear_x_global",
"belt_shear_y", "belt_shear_y_angle", "belt_shear_y_from", "belt_shear_y_global",
"belt_shear_z", "belt_shear_z_angle", "belt_shear_z_from", "belt_shear_z_global",
"belt_scale_x", "belt_scale_x_angle", "belt_scale_y", "belt_scale_y_angle", "belt_scale_z", "belt_scale_z_angle",
"belt_gcode_remap_x", "belt_gcode_remap_y", "belt_gcode_remap_z", "gcode_flavor",
"belt_gcode_remap_x", "belt_gcode_remap_y", "belt_gcode_remap_z",
"belt_support_floor_offset", "belt_support_floor_mode", "belt_support_z_offset_mode",
"gcode_flavor",
"fan_kickstart", "fan_speedup_time", "fan_speedup_overhangs",
"single_extruder_multi_material", "manual_filament_change", "file_start_gcode", "machine_start_gcode", "machine_end_gcode", "before_layer_change_gcode", "printing_by_object_gcode", "layer_change_gcode", "time_lapse_gcode", "wrapping_detection_gcode", "change_filament_gcode", "change_extrusion_role_gcode",
"printer_model", "printer_variant", "printer_extruder_id", "printer_extruder_variant", "extruder_variant_list", "default_nozzle_volume_type",
+50 -12
View File
@@ -99,6 +99,10 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
// Cache the plenty of parameters, which influence the G-code generator only,
// or they are only notes not influencing the generated G-code.
static std::unordered_set<std::string> steps_gcode = {
// Belt printer G-code axis remap (only affects G-code output, not slicing).
"belt_gcode_remap_x",
"belt_gcode_remap_y",
"belt_gcode_remap_z",
//BBS
"additional_cooling_fan_speed",
"reduce_crossing_wall",
@@ -275,8 +279,34 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
// Spiral Vase forces different kind of slicing than the normal model:
// In Spiral Vase mode, holes are closed and only the largest area contour is kept at each layer.
// Therefore toggling the Spiral Vase on / off requires complete reslicing.
|| opt_key == "spiral_mode") {
|| opt_key == "spiral_mode"
// Belt printer transform options change the mesh geometry before slicing.
|| opt_key == "belt_printer"
|| opt_key == "belt_printer_angle"
|| opt_key == "belt_shear_x"
|| opt_key == "belt_shear_x_angle"
|| opt_key == "belt_shear_x_from"
|| opt_key == "belt_shear_x_global"
|| opt_key == "belt_shear_y"
|| opt_key == "belt_shear_y_angle"
|| opt_key == "belt_shear_y_from"
|| opt_key == "belt_shear_y_global"
|| opt_key == "belt_shear_z"
|| opt_key == "belt_shear_z_angle"
|| opt_key == "belt_shear_z_from"
|| opt_key == "belt_shear_z_global"
|| opt_key == "belt_scale_x"
|| opt_key == "belt_scale_x_angle"
|| opt_key == "belt_scale_y"
|| opt_key == "belt_scale_y_angle"
|| opt_key == "belt_scale_z"
|| opt_key == "belt_scale_z_angle") {
osteps.emplace_back(posSlice);
} else if (
opt_key == "belt_support_floor_offset"
|| opt_key == "belt_support_floor_mode"
|| opt_key == "belt_support_z_offset_mode") {
osteps.emplace_back(posSupportMaterial);
} else if (
opt_key == "print_sequence"
|| opt_key == "filament_type"
@@ -2124,15 +2154,21 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
int object_count = m_objects.size();
std::set<PrintObject*> need_slicing_objects;
std::set<PrintObject*> re_slicing_objects;
// Belt global Z shear: each object needs unique layer Z values based on
// its bed position, so sharing layers between "identical" objects is wrong.
bool belt_no_share = m_config.belt_printer.value && m_config.belt_shear_z_global.value
&& m_config.belt_shear_z.value != BeltShearMode::None;
if (!use_cache) {
for (int index = 0; index < object_count; index++)
{
PrintObject *obj = m_objects[index];
for (PrintObject *slicing_obj : need_slicing_objects)
{
if (is_print_object_the_same(obj, slicing_obj)) {
obj->set_shared_object(slicing_obj);
break;
if (!belt_no_share) {
for (PrintObject *slicing_obj : need_slicing_objects)
{
if (is_print_object_the_same(obj, slicing_obj)) {
obj->set_shared_object(slicing_obj);
break;
}
}
}
if (!obj->get_shared_object())
@@ -2151,12 +2187,14 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
PrintObject *obj = m_objects[index];
bool found_shared = false;
if (need_slicing_objects.find(obj) == need_slicing_objects.end()) {
for (PrintObject *slicing_obj : need_slicing_objects)
{
if (is_print_object_the_same(obj, slicing_obj)) {
obj->set_shared_object(slicing_obj);
found_shared = true;
break;
if (!belt_no_share) {
for (PrintObject *slicing_obj : need_slicing_objects)
{
if (is_print_object_the_same(obj, slicing_obj)) {
obj->set_shared_object(slicing_obj);
found_shared = true;
break;
}
}
}
if (!found_shared) {
+9 -1
View File
@@ -573,7 +573,15 @@ private:
PrintObject* m_shared_object{ nullptr };
// Belt printer: global Z offset applied to this object's layers for shear positioning.
double m_belt_global_z_offset { 0.0 };
// Belt printer: min_z of mesh after belt shear (before Z-shift), for z_offset calc.
double m_belt_min_z { 0.0 };
public:
double belt_global_z_offset() const { return m_belt_global_z_offset; }
private:
// SoftFever
//
// object id
+27 -3
View File
@@ -135,22 +135,29 @@ struct PrintObjectTrafoAndInstances
// Generate a list of trafos and XY offsets for instances of a ModelObject
// Orca: Updated to include XYZ filament shrinkage compensation
static std::vector<PrintObjectTrafoAndInstances> print_objects_from_model_object(const ModelObject &model_object, const Vec3d &shrinkage_compensation)
static std::vector<PrintObjectTrafoAndInstances> print_objects_from_model_object(const ModelObject &model_object, const Vec3d &shrinkage_compensation, bool force_separate_instances = false)
{
std::set<PrintObjectTrafoAndInstances> trafos;
PrintObjectTrafoAndInstances trafo;
//BBS: add useful logs for debug
int index = 0;
int unique_counter = 0;
for (ModelInstance *model_instance : model_object.instances) {
if (model_instance->is_printable()) {
// Orca: Updated with XYZ filament shrinkage compensation
Geometry::Transformation model_instance_transformation = model_instance->get_transformation();
trafo.trafo = model_instance_transformation.get_matrix_with_applied_shrinkage_compensation(shrinkage_compensation);
auto shift = Point::new_scale(trafo.trafo.data()[12], trafo.trafo.data()[13]);
// Reset the XY axes of the transformation.
trafo.trafo.data()[12] = 0;
trafo.trafo.data()[13] = 0;
// Belt printer global mode: prevent instance grouping so each
// copy gets its own PrintObject with independent layer Z values.
// Add a tiny unique perturbation to the existing Z (don't replace
// it — the Z translation from ensure_on_bed must be preserved).
if (force_separate_instances)
trafo.trafo.data()[14] += 1e-10 * (++unique_counter);
// Search or insert a trafo.
auto it = trafos.emplace(trafo).first;
const_cast<PrintObjectTrafoAndInstances&>(*it).instances.emplace_back(PrintInstance{ nullptr, model_instance, shift });
@@ -1506,11 +1513,16 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
PrintObjectPtrs print_objects_new;
print_objects_new.reserve(std::max(m_objects.size(), m_model.objects.size()));
bool new_objects = false;
bool belt_instances_shifted = false;
// Walk over all new model objects and check, whether there are matching PrintObjects.
for (ModelObject *model_object : m_model.objects) {
ModelObjectStatus &model_object_status = const_cast<ModelObjectStatus&>(model_object_status_db.reuse(*model_object));
// Orca: Updated for XYZ filament shrink compensation
model_object_status.print_instances = print_objects_from_model_object(*model_object, this->shrinkage_compensation());
// Belt global mode: force each instance into its own PrintObject
// so each gets independent layer Z values.
bool belt_force_separate = m_config.belt_printer.value && m_config.belt_shear_z_global.value
&& m_config.belt_shear_z.value != BeltShearMode::None;
model_object_status.print_instances = print_objects_from_model_object(*model_object, this->shrinkage_compensation(), belt_force_separate);
std::vector<const PrintObjectStatus*> old;
old.reserve(print_object_status_db.count(*model_object));
for (const PrintObjectStatus &print_object_status : print_object_status_db.get_range(*model_object))
@@ -1558,6 +1570,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
if (status != PrintBase::APPLY_STATUS_UNCHANGED) {
size_t extruder_num = new_full_config.option<ConfigOptionFloats>("nozzle_diameter")->size();
update_apply_status(status == PrintBase::APPLY_STATUS_INVALIDATED);
belt_instances_shifted = true;
}
print_objects_new.emplace_back((*it_old)->print_object);
const_cast<PrintObjectStatus*>(*it_old)->status = PrintObjectStatus::Reused;
@@ -1593,6 +1606,17 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
update_apply_status(object->invalidate_step(posSlice));
}
}
// Belt printer global mode: when any object's instances shifted,
// recompute m_belt_global_z_offset for ALL objects (it depends on
// min_shift across all objects, so one move affects everyone).
if (belt_instances_shifted
&& m_config.belt_printer.value
&& m_config.belt_shear_z_global.value
&& m_config.belt_shear_z.value != BeltShearMode::None) {
for (PrintObject *object : m_objects)
update_apply_status(object->invalidate_step(posSlice));
}
}
//BBS: check the config again
+64
View File
@@ -327,6 +327,21 @@ static t_config_enum_values s_keys_map_BeltRemapAxis {
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BeltRemapAxis)
static t_config_enum_values s_keys_map_BeltSupportFloorMode {
{ "none", int(BeltSupportFloorMode::None) },
{ "generator_only", int(BeltSupportFloorMode::GeneratorOnly) },
{ "clip_only", int(BeltSupportFloorMode::ClipOnly) },
{ "both", int(BeltSupportFloorMode::Both) },
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BeltSupportFloorMode)
static t_config_enum_values s_keys_map_BeltSupportZOffsetMode {
{ "none", int(BeltSupportZOffsetMode::None) },
{ "unconditional", int(BeltSupportZOffsetMode::Unconditional) },
{ "raft_only", int(BeltSupportZOffsetMode::RaftOnly) },
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BeltSupportZOffsetMode)
static t_config_enum_values s_keys_map_SupportMaterialPattern {
{ "rectilinear", smpRectilinear },
{ "rectilinear-grid", smpRectilinearGrid },
@@ -6047,17 +6062,29 @@ void PrintConfigDef::init_fff_params()
def->set_default_value(new ConfigOptionEnum<BeltAxis>(default_axis));
};
auto add_belt_shear_global = [this](const char *key, const char *label) {
auto def = this->add(key, coBool);
def->label = L(label);
def->category = L("Printable space");
def->tooltip = L("Apply shear in global coordinates (position-aware) rather than object-local coordinates.");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false));
};
add_belt_shear_mode("belt_shear_x", "Function", BeltShearMode::None);
add_belt_shear_angle("belt_shear_x_angle", "Angle");
add_belt_axis_enum("belt_shear_x_from", "From", "Source axis for X shear.", BeltAxis::Z);
add_belt_shear_global("belt_shear_x_global", "Global");
add_belt_shear_mode("belt_shear_y", "Function", BeltShearMode::PosCot);
add_belt_shear_angle("belt_shear_y_angle", "Angle");
add_belt_axis_enum("belt_shear_y_from", "From", "Source axis for Y shear.", BeltAxis::Z);
add_belt_shear_global("belt_shear_y_global", "Global");
add_belt_shear_mode("belt_shear_z", "Function", BeltShearMode::None);
add_belt_shear_angle("belt_shear_z_angle", "Angle");
add_belt_axis_enum("belt_shear_z_from", "From", "Source axis for Z shear.", BeltAxis::Y);
add_belt_shear_global("belt_shear_z_global", "Global");
// Per-axis scale controls for belt printer
auto add_belt_scale_mode = [this](const char *key, const char *label, BeltScaleMode default_mode) {
@@ -6109,6 +6136,43 @@ void PrintConfigDef::init_fff_params()
add_belt_remap("belt_gcode_remap_y", "Y", "Which slicing axis maps to machine Y in G-code output.", BeltRemapAxis::PosY);
add_belt_remap("belt_gcode_remap_z", "Z", "Which slicing axis maps to machine Z in G-code output.", BeltRemapAxis::PosZ);
// Belt support floor debug controls
def = this->add("belt_support_floor_offset", coFloat);
def->label = L("Floor Z offset");
def->category = L("Printable space");
def->tooltip = L("Shifts the computed belt floor up or down (mm). Negative values lower the floor, allowing more supports to survive. Use this to diagnose belt floor formula issues.");
def->sidetext = L("mm");
def->min = -500;
def->max = 500;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0));
{
auto def = this->add("belt_support_floor_mode", coEnum);
def->label = L("Floor mode");
def->category = L("Printable space");
def->tooltip = L("Controls belt floor awareness for supports. 'None' disables belt floor logic. "
"'Generator only' stops support generation at the belt floor plane.");
def->enum_keys_map = &ConfigOptionEnum<BeltSupportFloorMode>::get_enum_values();
def->enum_values = {"none", "generator_only"};
def->enum_labels = {L("None"), L("Generator only")};
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum<BeltSupportFloorMode>(BeltSupportFloorMode::GeneratorOnly));
}
{
auto def = this->add("belt_support_z_offset_mode", coEnum);
def->label = L("Z offset mode");
def->category = L("Printable space");
def->tooltip = L("How global Z offset is applied to support layers for belt printers with global shear. "
"'None' = don't offset. 'Unconditional' = offset all layers. 'Raft only' = only offset raft layers.");
def->enum_keys_map = &ConfigOptionEnum<BeltSupportZOffsetMode>::get_enum_values();
def->enum_values = {"none", "unconditional", "raft_only"};
def->enum_labels = {L("None"), L("Unconditional"), L("Raft only")};
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum<BeltSupportZOffsetMode>(BeltSupportZOffsetMode::Unconditional));
}
def = this->add("tree_support_branch_angle", coFloat);
def->label = L("Tree support branch angle");
def->category = L("Support");
+23
View File
@@ -189,6 +189,21 @@ enum class BeltRemapAxis
RevX = 6, RevY = 7, RevZ = 8, // Reversed: max - pos
};
enum class BeltSupportFloorMode
{
None, // No belt floor awareness
GeneratorOnly, // Only in tree support drop_nodes/contact_points
ClipOnly, // Only post-processing clipping
Both, // Both generator and clipping
};
enum class BeltSupportZOffsetMode
{
None, // Don't apply global_z_offset to support layers
Unconditional, // Apply to all support layers
RaftOnly, // Only apply to raft layers
};
enum SupportMaterialPattern {
smpDefault,
smpRectilinear, smpRectilinearGrid, smpHoneycomb,
@@ -536,6 +551,8 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltShearMode)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltScaleMode)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltAxis)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltRemapAxis)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltSupportFloorMode)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltSupportZOffsetMode)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialPattern)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialStyle)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialInterfacePattern)
@@ -1456,12 +1473,15 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
((ConfigOptionEnum<BeltShearMode>, belt_shear_x))
((ConfigOptionFloat, belt_shear_x_angle))
((ConfigOptionEnum<BeltAxis>, belt_shear_x_from))
((ConfigOptionBool, belt_shear_x_global))
((ConfigOptionEnum<BeltShearMode>, belt_shear_y))
((ConfigOptionFloat, belt_shear_y_angle))
((ConfigOptionEnum<BeltAxis>, belt_shear_y_from))
((ConfigOptionBool, belt_shear_y_global))
((ConfigOptionEnum<BeltShearMode>, belt_shear_z))
((ConfigOptionFloat, belt_shear_z_angle))
((ConfigOptionEnum<BeltAxis>, belt_shear_z_from))
((ConfigOptionBool, belt_shear_z_global))
((ConfigOptionEnum<BeltScaleMode>, belt_scale_x))
((ConfigOptionFloat, belt_scale_x_angle))
((ConfigOptionEnum<BeltScaleMode>, belt_scale_y))
@@ -1471,6 +1491,9 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
((ConfigOptionEnum<BeltRemapAxis>, belt_gcode_remap_x))
((ConfigOptionEnum<BeltRemapAxis>, belt_gcode_remap_y))
((ConfigOptionEnum<BeltRemapAxis>, belt_gcode_remap_z))
((ConfigOptionFloat, belt_support_floor_offset))
((ConfigOptionEnum<BeltSupportFloorMode>, belt_support_floor_mode))
((ConfigOptionEnum<BeltSupportZOffsetMode>, belt_support_z_offset_mode))
//BBS
((ConfigOptionInts, additional_cooling_fan_speed))
((ConfigOptionBool, reduce_crossing_wall))
+114 -2
View File
@@ -9,6 +9,7 @@
#include "MutablePolygon.hpp"
#include "PrintConfig.hpp"
#include "Support/SupportMaterial.hpp"
#include "Support/SupportCommon.hpp"
#include "Support/SupportSpotsGenerator.hpp"
#include "Support/TreeSupport.hpp"
#include "Surface.hpp"
@@ -1365,7 +1366,12 @@ bool PrintObject::invalidate_step(PrintObjectStep step)
} else if (step == posSupportMaterial) {
invalidated |= this->invalidate_steps({ posSimplifySupportPath });
invalidated |= m_print->invalidate_steps({ psSkirtBrim });
m_slicing_params.valid = false;
// NOTE: do NOT set m_slicing_params.valid = false here.
// belt_floor_z_shift is patched to an exact value during posSlice
// (PrintObjectSlice.cpp, after slice_volumes). Invalidating slicing
// params here causes update_slicing_parameters() to overwrite that
// exact value with a bounding-box approximation, while posSlice does
// not re-run to correct it — breaking belt support clipping.
}
// Wipe tower depends on the ordering of extruders, which in turn depends on everything.
@@ -3392,6 +3398,10 @@ void PrintObject::update_slicing_parameters()
// Orca: updated function call for XYZ shrinkage compensation
if (!m_slicing_params.valid) {
coordf_t object_height = this->model_object()->max_z();
// Belt floor parameters for support clipping (populated below if belt Z-shear is active).
double belt_floor_shear_factor_out = 0.0;
int belt_floor_from_axis_out = 1;
double belt_floor_z_shift_out = 0.0;
// Belt shear/scale may change the effective Z height.
const auto &pcfg = this->print()->config();
if (pcfg.belt_printer.value) {
@@ -3435,6 +3445,12 @@ void PrintObject::update_slicing_parameters()
max_rz = std::max(max_rz, new_z);
}
object_height = max_rz - min_rz;
belt_floor_shear_factor_out = shear_factor;
belt_floor_from_axis_out = from;
// Belt contact surface starts at bb.min.z() pre-shear; add the
// slicing Z-shift that keeps the mesh above Z=0.
// Exact value is patched after slice_volumes() in posSlice.
belt_floor_z_shift_out = bb.min.z() + ((min_rz < 0.) ? -min_rz : 0.);
} else {
object_height *= scale_z;
}
@@ -3442,6 +3458,10 @@ void PrintObject::update_slicing_parameters()
}
m_slicing_params = SlicingParameters::create_from_config(pcfg, m_config, object_height,
this->object_extruders(), this->print()->shrinkage_compensation());
// Populate belt floor parameters into slicing params for support clipping.
m_slicing_params.belt_floor_shear_factor = belt_floor_shear_factor_out;
m_slicing_params.belt_floor_from_axis = belt_floor_from_axis_out;
m_slicing_params.belt_floor_z_shift = belt_floor_z_shift_out;
}
}
@@ -3479,6 +3499,11 @@ SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full
sort_remove_duplicates(object_extruders);
//FIXME add painting extruders
// Belt floor parameters for support clipping (populated below if belt Z-shear is active).
double belt_floor_shear_factor_out = 0.0;
int belt_floor_from_axis_out = 1;
double belt_floor_z_shift_out = 0.0;
if (object_max_z <= 0.f) {
BoundingBoxf3 bb = model_object.raw_bounding_box();
object_max_z = (float)bb.size().z();
@@ -3523,13 +3548,20 @@ SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full
max_rz = std::max(max_rz, new_z);
}
object_max_z = (float)(max_rz - min_rz);
belt_floor_shear_factor_out = shear_factor;
belt_floor_from_axis_out = from;
belt_floor_z_shift_out = bb.min.z() + ((min_rz < 0.) ? -min_rz : 0.);
} else {
object_max_z *= (float)scale_z;
}
}
}
}
return SlicingParameters::create_from_config(print_config, object_config, object_max_z, object_extruders, object_shrinkage_compensation);
SlicingParameters params = SlicingParameters::create_from_config(print_config, object_config, object_max_z, object_extruders, object_shrinkage_compensation);
params.belt_floor_shear_factor = belt_floor_shear_factor_out;
params.belt_floor_from_axis = belt_floor_from_axis_out;
params.belt_floor_z_shift = belt_floor_z_shift_out;
return params;
}
// returns 0-based indices of extruders used to print the object (without brim, support and other helper extrusions)
@@ -4038,6 +4070,67 @@ void PrintObject::combine_infill()
}
}
// Belt printer: clip an ExtrusionEntityCollection to a region defined by clip_expoly.
// Handles ExtrusionPath, ExtrusionMultiPath, ExtrusionLoop, and nested ExtrusionEntityCollection.
static void clip_support_fills(ExtrusionEntityCollection &fills, const ExPolygons &clip_region)
{
ExtrusionEntitiesPtr new_entities;
for (ExtrusionEntity *entity : fills.entities) {
if (auto *path = dynamic_cast<ExtrusionPath *>(entity)) {
ExtrusionEntityCollection clipped;
path->intersect_expolygons(clip_region, &clipped);
if (!clipped.empty()) {
for (ExtrusionEntity *e : clipped.entities)
new_entities.push_back(e->clone());
}
delete entity;
} else if (auto *multipath = dynamic_cast<ExtrusionMultiPath *>(entity)) {
ExtrusionPaths new_paths;
for (const ExtrusionPath &p : multipath->paths) {
ExtrusionEntityCollection clipped;
p.intersect_expolygons(clip_region, &clipped);
for (ExtrusionEntity *e : clipped.entities)
if (auto *cp = dynamic_cast<ExtrusionPath *>(e))
new_paths.push_back(std::move(*cp));
}
if (!new_paths.empty()) {
multipath->paths = std::move(new_paths);
new_entities.push_back(multipath);
} else {
delete entity;
}
} else if (auto *loop = dynamic_cast<ExtrusionLoop *>(entity)) {
ExtrusionPaths new_paths;
for (const ExtrusionPath &p : loop->paths) {
ExtrusionEntityCollection clipped;
p.intersect_expolygons(clip_region, &clipped);
for (ExtrusionEntity *e : clipped.entities)
if (auto *cp = dynamic_cast<ExtrusionPath *>(e))
new_paths.push_back(std::move(*cp));
}
if (!new_paths.empty()) {
// Loop is no longer a closed loop after clipping; emit as individual paths.
for (auto &p : new_paths)
new_entities.push_back(new ExtrusionPath(std::move(p)));
delete entity;
} else {
delete entity;
}
} else if (auto *coll = dynamic_cast<ExtrusionEntityCollection *>(entity)) {
clip_support_fills(*coll, clip_region);
if (!coll->empty()) {
new_entities.push_back(coll);
} else {
delete entity;
}
} else {
// Unknown entity type — keep as-is.
new_entities.push_back(entity);
}
}
fills.entities = std::move(new_entities);
}
void PrintObject::_generate_support_material()
{
if (is_tree(m_config.support_type.value)) {
@@ -4049,6 +4142,25 @@ void PrintObject::_generate_support_material()
PrintObjectSupportMaterial support_material(this, m_slicing_params);
support_material.generate(*this);
}
// Global Z offset for support layers:
// - Normal support: layers already inherit global_z_offset from object layers.
// - Non-organic tree support (slim/strong/hybrid): plan_layer_heights() reads
// from globally-offset object layers, so support layers already have it.
// - Organic tree support: generate_tree_support_3D() computes its own Z values
// independently and does NOT inherit the offset — apply it here.
// Belt floor polygon clipping for non-organic tree support is done inside
// draw_circles() before area_groups and toolpaths are built.
if (is_tree(m_config.support_type.value) && std::abs(m_belt_global_z_offset) > EPSILON) {
// Resolve effective support style (same logic as SupportParameters).
auto style = m_config.support_style.value;
if (style == smsDefault)
style = smsTreeOrganic;
if (style == smsTreeOrganic) {
for (SupportLayer *sl : m_support_layers)
sl->print_z += m_belt_global_z_offset;
}
}
}
// BBS
+121 -3
View File
@@ -1,4 +1,5 @@
#include <boost/log/trivial.hpp>
#include <limits>
#include <tbb/parallel_for.h>
@@ -125,7 +126,8 @@ static std::vector<VolumeSlices> slice_volumes_inner(
ModelVolumePtrs model_volumes,
const std::vector<PrintObjectRegions::LayerRangeRegions> &layer_ranges,
const std::vector<float> &zs,
const std::function<void()> &throw_on_cancel_callback)
const std::function<void()> &throw_on_cancel_callback,
double *out_belt_min_z = nullptr)
{
model_volumes_sort_by_id(model_volumes);
@@ -202,8 +204,32 @@ static std::vector<VolumeSlices> slice_volumes_inner(
}
// Apply: scale * shear * trafo (shear first, then scale).
if (has_shear || has_scale)
if (has_shear || has_scale) {
params_base.trafo = belt_scale * belt_shear * params_base.trafo;
// After the shear/scale transform, the mesh may clip through the
// build plate (Z < 0). Detect this and shift the mesh up.
Transform3d combined = params_base.trafo;
double min_z = std::numeric_limits<double>::max();
for (const ModelVolume *mv : model_volumes) {
if (!mv->is_model_part()) continue;
for (const stl_vertex &v : mv->mesh().its.vertices) {
Vec3d pt = combined * v.cast<double>();
min_z = std::min(min_z, pt.z());
}
}
double belt_z_shift_val = (min_z < 0. && min_z != std::numeric_limits<double>::max()) ? -min_z : 0.;
BOOST_LOG_TRIVIAL(warning) << "Belt Z-shift: min_z=" << min_z
<< " z_shift=" << belt_z_shift_val
<< " trafo_z=" << object_trafo.matrix()(2, 3);
if (belt_z_shift_val > 0.) {
Transform3d z_shift = Transform3d::Identity();
z_shift.matrix()(2, 3) = belt_z_shift_val;
params_base.trafo = z_shift * params_base.trafo;
}
if (out_belt_min_z)
*out_belt_min_z = (min_z != std::numeric_limits<double>::max()) ? min_z : 0.;
}
}
//BBS: 0.0025mm is safe enough to simplify the data to speed slicing up for high-resolution model.
//Also has on influence on arc fitting which has default resolution 0.0125mm.
@@ -866,6 +892,20 @@ void PrintObject::slice()
m_layers = new_layers(this, generate_object_layers(m_slicing_params, layer_height_profile, m_config.precise_z_height.value));
this->slice_volumes();
m_print->throw_if_canceled();
// After slicing, m_belt_min_z holds the exact post-shear minimum Z
// in trafo_centered space (which includes the ensure_on_bed Z offset).
// The belt surface is at Z=0 in trafo_centered space; after shear it
// becomes Z = sf*Y, and after the z-shift that keeps the mesh above
// Z=0 it becomes Z = sf*Y + z_shift_val. So belt_floor_z_shift is
// simply the z-shift applied, i.e. max(0, -m_belt_min_z).
// NOTE: do NOT add raw_bounding_box().min.z() here — m_belt_min_z
// already includes the ensure_on_bed offset, unlike the min_rz used
// in update_slicing_parameters() which needs that compensation.
if (std::abs(m_slicing_params.belt_floor_shear_factor) > EPSILON) {
m_slicing_params.belt_floor_z_shift = (m_belt_min_z < 0.) ? -m_belt_min_z : 0.;
}
int firstLayerReplacedBy = 0;
#if 0
@@ -904,6 +944,83 @@ void PrintObject::slice()
if (m_layers.empty())
throw Slic3r::SlicingError(L("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n"));
// Belt printer global mode: offset all layer Z values so objects at
// different bed positions print at different heights on the tilted belt.
// This is a post-slicing adjustment — the sliced geometry is identical
// regardless of global mode, only the output Z coordinates change.
{
const auto &pcfg = this->print()->config();
BOOST_LOG_TRIVIAL(warning) << "Belt global check: belt_printer=" << pcfg.belt_printer.value
<< " belt_shear_z=" << int(pcfg.belt_shear_z.value)
<< " belt_shear_z_global=" << pcfg.belt_shear_z_global.value
<< " object=" << this->model_object()->name;
if (pcfg.belt_printer.value) {
auto compute_shear_factor = [](BeltShearMode mode, double angle_deg) -> double {
double angle_rad = Geometry::deg2rad(angle_deg);
double sin_a = std::sin(angle_rad);
double cos_a = std::cos(angle_rad);
switch (mode) {
case BeltShearMode::PosCot: return (sin_a > EPSILON) ? cos_a / sin_a : 0.;
case BeltShearMode::NegCot: return (sin_a > EPSILON) ? -cos_a / sin_a : 0.;
case BeltShearMode::PosTan: return (cos_a > EPSILON) ? sin_a / cos_a : 0.;
case BeltShearMode::NegTan: return (cos_a > EPSILON) ? -sin_a / cos_a : 0.;
default: return 0.;
}
};
Point inst_shift = this->instances().empty() ? Point(0, 0)
: this->instances().front().shift - this->center_offset();
BOOST_LOG_TRIVIAL(warning) << "Belt global: object " << this->model_object()->name
<< " instances=" << this->instances().size()
<< " shift=(" << unscale<double>(inst_shift.x()) << ", " << unscale<double>(inst_shift.y()) << ")";
double global_z_offset = 0.;
struct GAxis { BeltShearMode mode; double angle; int from; bool global; };
GAxis gaxes[3] = {
{ pcfg.belt_shear_x.value, pcfg.belt_shear_x_angle.value, int(pcfg.belt_shear_x_from.value), pcfg.belt_shear_x_global.value },
{ pcfg.belt_shear_y.value, pcfg.belt_shear_y_angle.value, int(pcfg.belt_shear_y_from.value), pcfg.belt_shear_y_global.value },
{ pcfg.belt_shear_z.value, pcfg.belt_shear_z_angle.value, int(pcfg.belt_shear_z_from.value), pcfg.belt_shear_z_global.value },
};
// Only the Z-row shear contributes a Z offset from global mode.
// (X/Y row shears with global would offset X/Y, not Z — not useful here.)
// Offsets are RELATIVE: we subtract the minimum shift across all
// PrintObjects so the lowest-positioned object stays at Z=0.
const auto &za = gaxes[2]; // Z row
if (za.global && za.mode != BeltShearMode::None && za.from < 2) {
double factor = compute_shear_factor(za.mode, za.angle);
// The Z-shift brought the mesh's lowest sheared vertex to
// Z=0. That vertex's physical Y determines the belt contact
// point. With trafo_z preserved (ensure_on_bed offset),
// min_z = Y_at_contact * factor for bottom-face vertices,
// so: z_offset = center_Y * factor + min_z.
Point phys = inst_shift; // already has center_offset subtracted
double center_on_axis = (za.from == 0) ? unscale<double>(phys.x()) : unscale<double>(phys.y());
global_z_offset += center_on_axis * factor + m_belt_min_z;
}
BOOST_LOG_TRIVIAL(warning) << "Belt global: z_offset=" << global_z_offset
<< " za.global=" << za.global << " za.mode=" << int(za.mode) << " za.from=" << za.from
<< " (relative to min across " << this->print()->objects().size() << " objects)";
m_belt_global_z_offset = global_z_offset;
if (std::abs(global_z_offset) > EPSILON) {
for (Layer *layer : m_layers)
layer->print_z += global_z_offset;
// Keep belt floor clipping in sync with the shifted print_z
// values — the support generator sees globally-offset object
// layer print_z, so belt_floor_z_shift must match.
m_slicing_params.belt_floor_z_shift += global_z_offset;
}
if (!m_layers.empty()) {
BOOST_LOG_TRIVIAL(warning) << "Belt global: first_layer_z=" << m_layers.front()->print_z
<< " last_layer_z=" << m_layers.back()->print_z
<< " num_layers=" << m_layers.size()
<< " center_offset=(" << unscale<double>(m_center_offset.x())
<< ", " << unscale<double>(m_center_offset.y()) << ")";
}
}
}
// BBS
this->set_done(posSlice);
}
@@ -1208,7 +1325,8 @@ void PrintObject::slice_volumes()
if (!slice_zs.empty()) {
objSliceByVolume = slice_volumes_inner(
print->config(), this->config(), this->trafo_centered(),
this->model_object()->volumes, m_shared_regions->layer_ranges, slice_zs, throw_on_cancel_callback);
this->model_object()->volumes, m_shared_regions->layer_ranges, slice_zs, throw_on_cancel_callback,
&m_belt_min_z);
}
//BBS: "model_part" volumes are grouded according to their connections
+7
View File
@@ -110,6 +110,13 @@ struct SlicingParameters
coordf_t object_print_z_uncompensated_max { 0 };
// Scaling factor for compensating shrinkage in Z-axis.
coordf_t object_shrinkage_compensation_z { 0 };
// Belt printer: floor plane parameters for support clipping.
// Belt contact surface in slicing coords: Z = bb_min_z + sf*Y + slicing_z_shift.
// cutoff = (print_z - belt_floor_z_shift - floor_offset) / shear_factor
double belt_floor_shear_factor { 0.0 }; // shear factor (e.g. cot(45deg))
int belt_floor_from_axis { 1 }; // which axis the shear is from (0=X, 1=Y)
double belt_floor_z_shift { 0.0 }; // bb_min_z + max(0, -min_z_after_shear)
};
static_assert(IsTriviallyCopyable<SlicingParameters>::value, "SlicingParameters class is not POD (and it should be - see constructor).");
+6
View File
@@ -144,6 +144,12 @@ int idx_lower_or_equal(const std::vector<T*> &vec, int idx, FN_LOWER_EQUAL fn_lo
return idx_lower_or_equal(vec.begin(), vec.end(), idx, fn_lower_equal);
}
// Belt floor: compute the belt-side half-plane polygon at a given print_z.
// Used to clip support polygons against the belt surface.
Polygons belt_floor_surface_polygon(
const SlicingParameters &slicing_params, const PrintConfig &print_config,
const PrintObject &object, coordf_t print_z);
} // namespace Slic3r
#endif /* slic3r_SupportCommon_hpp_ */
+286 -4
View File
@@ -367,10 +367,21 @@ inline void layers_append(SupportGeneratorLayersPtr &dst, const SupportGenerator
}
// Support layer that is covered by some form of dense interface.
static constexpr const std::initializer_list<SupporLayerType> support_types_interface {
static constexpr const std::initializer_list<SupporLayerType> support_types_interface {
SupporLayerType::RaftInterface, SupporLayerType::BottomContact, SupporLayerType::BottomInterface, SupporLayerType::TopContact, SupporLayerType::TopInterface
};
// Forward declarations for belt floor helpers (defined later in this file).
// belt_floor_surface_polygon is declared in SupportCommon.hpp (non-static,
// shared with TreeSupport.cpp).
static Polygons belt_floor_valid_region_polygon(
const SlicingParameters &slicing_params, const PrintConfig &print_config,
const PrintObject &object, coordf_t print_z);
static void trim_support_layers_by_belt_floor(
const SlicingParameters &slicing_params, const PrintConfig &print_config,
const PrintObject &object, SupportGeneratorLayersPtr &support_layers);
void PrintObjectSupportMaterial::generate(PrintObject &object)
{
BOOST_LOG_TRIVIAL(info) << "Support generator - Start";
@@ -443,11 +454,12 @@ void PrintObjectSupportMaterial::generate(PrintObject &object)
object, bottom_contacts, top_contacts, layer_storage);
this->trim_support_layers_by_object(object, top_contacts, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_support_params.gap_xy);
trim_support_layers_by_belt_floor(m_slicing_params, *m_print_config, object, top_contacts);
#ifdef SLIC3R_DEBUG
for (const SupportGeneratorLayer *layer : top_contacts)
Slic3r::SVG::export_expolygons(
debug_out_path("support-top-contacts-trimmed-by-object-%d-%lf.svg", iRun, layer->print_z),
debug_out_path("support-top-contacts-trimmed-by-object-%d-%lf.svg", iRun, layer->print_z),
union_ex(layer->polygons));
#endif
@@ -603,6 +615,139 @@ Polygons collect_region_slices_by_type(const Layer &layer, SurfaceType surface_t
return out;
}
// Belt printer: compute the belt-side half-plane polygon at a given print_z.
// This represents the region where the belt surface exists (the "phantom top surface").
// Support that overlaps with this polygon should terminate with a bottom contact.
// Returns empty if belt floor is not active.
Polygons belt_floor_surface_polygon(
const SlicingParameters &slicing_params,
const PrintConfig &print_config,
const PrintObject &object,
coordf_t print_z)
{
const double shear_factor = slicing_params.belt_floor_shear_factor;
if (std::abs(shear_factor) < EPSILON)
return {};
const int from_axis = slicing_params.belt_floor_from_axis; // 0=X, 1=Y
const double floor_offset = print_config.belt_support_floor_offset.value;
// Belt floor line in slicing coordinates: Z = sf * Y + z_shift.
// z_shift accounts for the upward shift applied when post-shear geometry
// extends below the bed (overhangs). Solving for Y:
// cutoff = (print_z - z_shift - floor_offset) / sf
const double z_shift = slicing_params.belt_floor_z_shift;
const double cutoff = (print_z - z_shift - floor_offset) / shear_factor;
const coord_t cutoff_scaled = scale_(cutoff);
const coord_t large_bound = scale_(1e4);
// Build the belt-side half-plane (inverted from the valid region).
// If shear_factor > 0: valid region is from_axis < cutoff, so belt surface is from_axis >= cutoff.
// If shear_factor < 0: valid region is from_axis > cutoff, so belt surface is from_axis <= cutoff.
Polygon belt_poly;
if (from_axis == 0) {
if (shear_factor > 0) {
// Belt surface: X >= cutoff
belt_poly.points = {
Point(cutoff_scaled, -large_bound),
Point(large_bound, -large_bound),
Point(large_bound, large_bound),
Point(cutoff_scaled, large_bound)
};
} else {
// Belt surface: X <= cutoff
belt_poly.points = {
Point(-large_bound, -large_bound),
Point(cutoff_scaled, -large_bound),
Point(cutoff_scaled, large_bound),
Point(-large_bound, large_bound)
};
}
} else {
if (shear_factor > 0) {
// Belt surface: Y >= cutoff
belt_poly.points = {
Point(-large_bound, cutoff_scaled),
Point( large_bound, cutoff_scaled),
Point( large_bound, large_bound),
Point(-large_bound, large_bound)
};
} else {
// Belt surface: Y <= cutoff
belt_poly.points = {
Point(-large_bound, -large_bound),
Point( large_bound, -large_bound),
Point( large_bound, cutoff_scaled),
Point(-large_bound, cutoff_scaled)
};
}
}
return { belt_poly };
}
// Belt printer: compute the valid-region half-plane polygon at a given print_z.
// This is the region where support is allowed to exist (above the belt).
// Used to clip the downward-propagating support projection.
static Polygons belt_floor_valid_region_polygon(
const SlicingParameters &slicing_params,
const PrintConfig &print_config,
const PrintObject &object,
coordf_t print_z)
{
const double shear_factor = slicing_params.belt_floor_shear_factor;
if (std::abs(shear_factor) < EPSILON)
return {};
const int from_axis = slicing_params.belt_floor_from_axis;
const double floor_offset = print_config.belt_support_floor_offset.value;
const double z_shift = slicing_params.belt_floor_z_shift;
const double cutoff = (print_z - z_shift - floor_offset) / shear_factor;
const coord_t cutoff_scaled = scale_(cutoff);
const coord_t large_bound = scale_(1e4);
// Valid region: the complement of the belt surface polygon.
Polygon valid_poly;
if (from_axis == 0) {
if (shear_factor > 0) {
// Valid: X < cutoff
valid_poly.points = {
Point(-large_bound, -large_bound),
Point(cutoff_scaled, -large_bound),
Point(cutoff_scaled, large_bound),
Point(-large_bound, large_bound)
};
} else {
// Valid: X > cutoff
valid_poly.points = {
Point(cutoff_scaled, -large_bound),
Point(large_bound, -large_bound),
Point(large_bound, large_bound),
Point(cutoff_scaled, large_bound)
};
}
} else {
if (shear_factor > 0) {
// Valid: Y < cutoff
valid_poly.points = {
Point(-large_bound, -large_bound),
Point( large_bound, -large_bound),
Point( large_bound, cutoff_scaled),
Point(-large_bound, cutoff_scaled)
};
} else {
// Valid: Y > cutoff
valid_poly.points = {
Point(-large_bound, cutoff_scaled),
Point( large_bound, cutoff_scaled),
Point( large_bound, large_bound),
Point(-large_bound, large_bound)
};
}
}
return { valid_poly };
}
// Collect outer contours of all slices of this layer.
// This is useful for calculating the support base with holes filled.
Polygons collect_slices_outer(const Layer &layer)
@@ -2516,6 +2661,82 @@ static inline SupportGeneratorLayer* detect_bottom_contacts(
return &layer_new;
}
// Belt printer: detect bottom contacts where support meets the belt floor plane.
// Modeled on detect_bottom_contacts() but uses the belt plane polygon instead of stTop surfaces.
static inline SupportGeneratorLayer* detect_belt_floor_bottom_contacts(
const SlicingParameters &slicing_params,
const SupportParameters &support_params,
const PrintConfig &print_config,
const PrintObject &object,
const Layer &layer,
// Existing top contact layers, for snapping.
const SupportGeneratorLayersPtr &top_contacts,
size_t contact_idx,
SupportGeneratorLayerStorage &layer_storage,
std::vector<Polygons> &layer_support_areas,
const Polygons &supports_projected)
{
// Compute the belt surface polygon at this layer's Z.
Polygons belt_surface = belt_floor_surface_polygon(slicing_params, print_config, object, layer.print_z);
if (belt_surface.empty())
return nullptr;
// Find where projected support overlaps the belt surface.
Polygons touching = intersection(belt_surface, supports_projected);
if (touching.empty())
return nullptr;
assert(layer.id() >= slicing_params.raft_layers());
size_t layer_id = layer.id() - slicing_params.raft_layers();
// Allocate a new bottom contact layer resting on the belt plane.
SupportGeneratorLayer &layer_new = layer_storage.allocate_unguarded(SupporLayerType::BottomContact);
// No object layer to sync with -- compute heights directly from flow parameters.
layer_new.height = support_params.support_material_bottom_interface_flow.height();
layer_new.print_z = layer.print_z + layer_new.height + slicing_params.gap_object_support;
layer_new.bottom_z = layer.print_z;
layer_new.idx_object_layer_below = layer_id;
layer_new.bridging = ! slicing_params.soluble_interface && object.config().thick_bridges;
layer_new.polygons = expand(touching, float(support_params.support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS);
if (! slicing_params.soluble_interface) {
// Snap to nearby top contact layers to avoid very thin support layers.
for (size_t top_idx = size_t(std::max<int>(0, int(contact_idx)));
top_idx < top_contacts.size() && top_contacts[top_idx]->print_z < layer_new.print_z + support_params.support_layer_height_min + EPSILON;
++ top_idx) {
if (top_contacts[top_idx]->print_z > layer_new.print_z - support_params.support_layer_height_min - EPSILON) {
coordf_t diff = layer_new.print_z - top_contacts[top_idx]->print_z;
assert(std::abs(diff) <= support_params.support_layer_height_min + EPSILON);
if (diff > 0.F) {
if (layer_new.height - diff > support_params.support_layer_height_min) {
layer_new.print_z = top_contacts[top_idx]->print_z;
layer_new.height -= diff;
} else {
continue;
}
} else {
layer_new.print_z = top_contacts[top_idx]->print_z;
layer_new.height -= diff;
}
break;
}
}
}
// Trim the already created base layers above this belt contact.
touching = expand(touching, float(SCALED_EPSILON));
for (int layer_id_above = int(layer_id) + 1; layer_id_above < int(object.total_layer_count()); ++ layer_id_above) {
const Layer &layer_above = *object.layers()[layer_id_above];
if (layer_above.print_z > layer_new.print_z - EPSILON)
break;
if (Polygons &above = layer_support_areas[layer_id_above]; ! above.empty())
above = diff(above, touching);
}
return &layer_new;
}
// Returns polygons to print + polygons to propagate downwards.
// Called twice: First for normal supports, possibly trimmed by "on build plate only", second for support enforcers not trimmed by "on build plate only".
static inline std::pair<Polygons, Polygons> project_support_to_grid(const Layer &layer, const SupportGridParams &grid_params, const Polygons &overhangs, Polygons *layer_buildplate_covered
@@ -2615,6 +2836,8 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::bottom_contact_layers_and_
//const auto expansion_to_slice = m_support_material_flow.scaled_spacing() / 2 + 25;
const SupportGridParams grid_params(*m_object_config, m_support_params.support_material_flow);
const bool buildplate_only = ! buildplate_covered.empty();
const bool has_belt_floor = std::abs(m_slicing_params.belt_floor_shear_factor) > EPSILON
&& m_print_config->belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly;
// Allocate empty surface areas, one per object layer.
layer_support_areas.assign(object.total_layer_count(), Polygons());
@@ -2675,8 +2898,9 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::bottom_contact_layers_and_
tbb::task_group task_group;
const Polygons &overhangs_for_bottom_contacts = buildplate_only ? enforcers_projection_raw : overhangs_projection_raw;
if (! overhangs_for_bottom_contacts.empty())
// Find the bottom contact layers above the top surfaces of this layer.
task_group.run([this, &object, &layer, &top_contacts, contact_idx, &layer_storage, &layer_support_areas, &bottom_contacts, &overhangs_for_bottom_contacts
// Find the bottom contact layers above the top surfaces of this layer,
// and also detect belt floor contacts if belt mode is active.
task_group.run([this, &object, &layer, &top_contacts, contact_idx, &layer_storage, &layer_support_areas, &bottom_contacts, &overhangs_for_bottom_contacts, has_belt_floor
#ifdef SLIC3R_DEBUG
, iRun, &polygons_new
#endif // SLIC3R_DEBUG
@@ -2690,6 +2914,15 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::bottom_contact_layers_and_
);
if (layer_new)
bottom_contacts.push_back(layer_new);
// Belt floor phantom surface: detect where support meets the belt plane.
if (has_belt_floor) {
SupportGeneratorLayer *belt_layer = detect_belt_floor_bottom_contacts(
m_slicing_params, m_support_params, *m_print_config, object,
layer, top_contacts, contact_idx, layer_storage,
layer_support_areas, overhangs_for_bottom_contacts);
if (belt_layer)
bottom_contacts.push_back(belt_layer);
}
});
Polygons &layer_support_area = layer_support_areas[layer_id];
@@ -2728,6 +2961,23 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::bottom_contact_layers_and_
task_group.wait();
// Belt floor: clip projections and support areas so support doesn't
// propagate below the belt plane.
if (has_belt_floor) {
Polygons valid_region = belt_floor_valid_region_polygon(
m_slicing_params, *m_print_config, object, layer.print_z);
if (! valid_region.empty()) {
if (! overhangs_projection.empty())
overhangs_projection = intersection(overhangs_projection, valid_region);
if (! enforcers_projection.empty())
enforcers_projection = intersection(enforcers_projection, valid_region);
if (! layer_support_area.empty())
layer_support_area = intersection(layer_support_area, valid_region);
if (! layer_support_area_enforcers.empty())
layer_support_area_enforcers = intersection(layer_support_area_enforcers, valid_region);
}
}
if (! layer_support_area_enforcers.empty()) {
if (layer_support_area.empty())
layer_support_area = std::move(layer_support_area_enforcers);
@@ -2738,6 +2988,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::bottom_contact_layers_and_
std::reverse(bottom_contacts.begin(), bottom_contacts.end());
trim_support_layers_by_object(object, bottom_contacts, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_support_params.gap_xy);
trim_support_layers_by_belt_floor(m_slicing_params, *m_print_config, object, bottom_contacts);
return bottom_contacts;
}
@@ -3108,6 +3359,37 @@ void PrintObjectSupportMaterial::generate_base_layers(
#endif /* SLIC3R_DEBUG */
this->trim_support_layers_by_object(object, intermediate_layers, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_support_params.gap_xy);
trim_support_layers_by_belt_floor(m_slicing_params, *m_print_config, object, intermediate_layers);
}
// Belt printer: trim support layer polygons by the belt floor plane.
// For each support layer, computes the belt floor half-plane at that layer's print_z
// and subtracts it from the support polygons. This follows the same diff() pattern
// as trim_support_layers_by_object() so that interface layers derived from trimmed
// intermediates automatically inherit the belt floor trimming.
static void trim_support_layers_by_belt_floor(
const SlicingParameters &slicing_params,
const PrintConfig &print_config,
const PrintObject &object,
SupportGeneratorLayersPtr &support_layers)
{
if (std::abs(slicing_params.belt_floor_shear_factor) < EPSILON)
return;
if (print_config.belt_support_floor_mode.value != BeltSupportFloorMode::GeneratorOnly)
return;
tbb::parallel_for(tbb::blocked_range<size_t>(0, support_layers.size()),
[&](const tbb::blocked_range<size_t> &range) {
for (size_t i = range.begin(); i < range.end(); ++ i) {
SupportGeneratorLayer *layer = support_layers[i];
if (layer->polygons.empty())
continue;
Polygons belt_surface = belt_floor_surface_polygon(
slicing_params, print_config, object, layer->print_z);
if (! belt_surface.empty())
layer->polygons = diff(layer->polygons, belt_surface);
}
});
}
void PrintObjectSupportMaterial::trim_support_layers_by_object(
+112 -5
View File
@@ -94,6 +94,46 @@ TreeModelVolumes::TreeModelVolumes(
#else
{
m_anti_overhang = print_object.slice_support_blockers();
// Belt floor: add belt surface polygons to anti_overhang so support
// is never generated inside the belt. Only in global shear mode —
// in local mode the belt floor clipping handles everything and
// anti_overhang at the bottom layers would block all support.
{
const auto &sp = print_object.slicing_parameters();
const auto &pcfg = print_object.print()->config();
const double sf = sp.belt_floor_shear_factor;
if (std::abs(sf) > EPSILON
&& std::abs(print_object.belt_global_z_offset()) > EPSILON
&& pcfg.belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly) {
const int from_axis = sp.belt_floor_from_axis;
const double floor_off = pcfg.belt_support_floor_offset.value;
const double z_shift = sp.belt_floor_z_shift - print_object.belt_global_z_offset();
size_t num_layers_needed = print_object.layer_count();
// Ensure m_anti_overhang is large enough.
if (m_anti_overhang.size() < num_layers_needed)
m_anti_overhang.resize(num_layers_needed, Polygons{});
for (size_t layer_idx = 0; layer_idx < num_layers_needed; ++layer_idx) {
double print_z = print_object.get_layer(layer_idx)->print_z
- print_object.belt_global_z_offset();
double cutoff = (print_z - z_shift - floor_off) / sf;
coord_t cutoff_sc = scale_(cutoff);
coord_t big = scale_(1e4);
Polygon belt_poly;
if (from_axis == 0) {
if (sf > 0)
belt_poly.points = {{cutoff_sc,-big},{big,-big},{big,big},{cutoff_sc,big}};
else
belt_poly.points = {{-big,-big},{cutoff_sc,-big},{cutoff_sc,big},{-big,big}};
} else {
if (sf > 0)
belt_poly.points = {{-big,cutoff_sc},{big,cutoff_sc},{big,big},{-big,big}};
else
belt_poly.points = {{-big,-big},{big,-big},{big,cutoff_sc},{-big,cutoff_sc}};
}
append(m_anti_overhang[layer_idx], Polygons{belt_poly});
}
}
}
TreeSupportMeshGroupSettings mesh_settings(print_object);
const TreeSupportSettings config{ mesh_settings, print_object.slicing_parameters() };
m_current_min_xy_dist = config.xy_min_distance;
@@ -102,6 +142,27 @@ TreeModelVolumes::TreeModelVolumes(
m_increase_until_radius = config.increase_radius_until_radius;
m_radius_0 = config.getRadius(0);
m_raft_layers = config.raft_layers;
// Belt printer: add virtual belt raft layers below the object, matching
// the extra layers added in generate_support_areas() so both use the
// same layer indexing.
{
const auto &sp2 = print_object.slicing_parameters();
const auto &pcfg2 = print_object.print()->config();
double belt_sf = sp2.belt_floor_shear_factor;
if (std::abs(belt_sf) > EPSILON && std::abs(print_object.belt_global_z_offset()) > EPSILON
&& pcfg2.belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly) {
double bb_min_z = std::abs(print_object.model_object()->raw_bounding_box().min.z());
double extra_depth = bb_min_z + 10.;
int num_extra = std::max(0, (int)std::ceil(extra_depth / sp2.layer_height));
if (num_extra > 0) {
std::vector<coordf_t> belt_layers;
belt_layers.reserve(num_extra);
for (int i = num_extra; i >= 1; --i)
belt_layers.push_back(sp2.first_object_layer_height - i * sp2.layer_height);
m_raft_layers.insert(m_raft_layers.begin(), belt_layers.begin(), belt_layers.end());
}
}
}
m_current_outline_idx = 0;
m_layer_outlines.emplace_back(mesh_settings, std::vector<Polygons>{});
@@ -114,6 +175,47 @@ TreeModelVolumes::TreeModelVolumes(
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx)
outlines[layer_idx] = polygons_simplify(to_polygons(print_object.get_layer(layer_idx - num_raft_layers)->lslices), mesh_settings.resolution, polygons_strictly_simple);
});
// Belt floor: pre-compute belt surface polygon per-layer for clipping.
// Branches grow toward the belt and their slices are clipped at the belt
// surface in organic_draw_branches(). The organic pipeline works in LOCAL
// Z (no global_z_offset), so use local z_shift and local print_z.
const auto &slicing_params = print_object.slicing_parameters();
const auto &pcfg = print_object.print()->config();
const double sf = slicing_params.belt_floor_shear_factor;
if (std::abs(sf) > EPSILON
&& pcfg.belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly) {
const int from_axis = slicing_params.belt_floor_from_axis;
const double floor_off = pcfg.belt_support_floor_offset.value;
// Subtract global_z_offset to get the LOCAL z_shift — the organic
// pipeline's Z coordinates don't include the global offset.
const double z_shift = slicing_params.belt_floor_z_shift
- print_object.belt_global_z_offset();
m_belt_floor.assign(num_layers, Polygons{});
for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) {
// Use local print_z (subtract global offset from object layer).
double print_z = (layer_idx >= num_raft_layers)
? print_object.get_layer(layer_idx - num_raft_layers)->print_z
- print_object.belt_global_z_offset()
: 0.;
double cutoff = (print_z - z_shift - floor_off) / sf;
coord_t cutoff_sc = scale_(cutoff);
coord_t big = scale_(1e4);
Polygon belt_poly;
if (from_axis == 0) {
if (sf > 0)
belt_poly.points = {{cutoff_sc,-big},{big,-big},{big,big},{cutoff_sc,big}};
else
belt_poly.points = {{-big,-big},{cutoff_sc,-big},{cutoff_sc,big},{-big,big}};
} else {
if (sf > 0)
belt_poly.points = {{-big,cutoff_sc},{big,cutoff_sc},{big,big},{-big,big}};
else
belt_poly.points = {{-big,-big},{big,-big},{big,cutoff_sc},{-big,cutoff_sc}};
}
m_belt_floor[layer_idx] = { belt_poly };
}
}
}
#endif
@@ -469,9 +571,9 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex
});
// 2) Sum over top / bottom ranges.
const bool processing_last_mesh = outline_idx == layer_outline_indices.size();
const bool processing_last_mesh = outline_idx == layer_outline_indices.back();
tbb::parallel_for(tbb::blocked_range<LayerIndex>(data.begin(), data.end()),
[&collision_areas_offsetted, &outlines, &machine_border = m_machine_border, &anti_overhang = m_anti_overhang, radius,
[&collision_areas_offsetted, &outlines, &machine_border = m_machine_border, &anti_overhang = m_anti_overhang, radius,
xy_distance, z_distance_bottom_layers, z_distance_top_layers, min_resolution = m_min_resolution, &data, processing_last_mesh, &throw_on_cancel]
(const tbb::blocked_range<LayerIndex>& range) {
for (LayerIndex layer_idx = range.begin(); layer_idx != range.end(); ++ layer_idx) {
@@ -517,9 +619,14 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex
// not support an overhang<90 degree than to risk fusing to it.
append(collisions, offset(union_ex(collision_areas_original), radius + required_range_x, ClipperLib::jtMiter, 1.2));
}
collisions = processing_last_mesh && layer_idx < int(anti_overhang.size()) ?
union_(collisions, offset(union_ex(anti_overhang[layer_idx]), radius, ClipperLib::jtMiter, 1.2)) :
union_(collisions);
if (processing_last_mesh) {
if (layer_idx < int(anti_overhang.size()))
append(collisions, offset(union_ex(anti_overhang[layer_idx]), radius, ClipperLib::jtMiter, 1.2));
// NOTE: m_belt_floor is NOT added to collision here — branches
// should grow toward the belt and terminate at it, not avoid it.
// Belt floor clipping is done post-generation in organic_draw_branches().
}
collisions = union_(collisions);
auto &dst = data[layer_idx];
if (processing_last_mesh) {
if (! dst.empty())
@@ -168,6 +168,9 @@ public:
}
Polygon m_bed_area;
// Belt floor polygons per layer — used for post-generation clipping
// in organic_draw_branches(). Public so the organic pipeline can access it.
std::vector<Polygons> m_belt_floor;
private:
// Caching polygons for a range of layers.
+89 -9
View File
@@ -637,6 +637,20 @@ TreeSupport::TreeSupport(PrintObject& object, const SlicingParameters &slicing_p
}
double TreeSupport::belt_floor_print_z(const Point &pos_slicing) const
{
double sf = m_slicing_params.belt_floor_shear_factor;
if (std::abs(sf) < EPSILON)
return -std::numeric_limits<double>::max(); // no belt floor
int from = m_slicing_params.belt_floor_from_axis;
// Belt floor in slicing coords: Z = sf * Y + z_shift + floor_offset.
// Inverse of cutoff = (Z - z_shift - floor_offset) / sf.
double pos = unscale<double>(from == 0 ? pos_slicing.x() : pos_slicing.y());
double floor_offset = m_print_config->belt_support_floor_offset.value;
double z_shift = m_slicing_params.belt_floor_z_shift;
return sf * pos + floor_offset + z_shift;
}
#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0.
void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/)
{
@@ -2141,6 +2155,41 @@ void TreeSupport::draw_circles()
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);
// 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), but belt_floor_z_shift includes
// global_z_offset — subtract it to get the cutoff in local coords.
if (std::abs(m_slicing_params.belt_floor_shear_factor) > EPSILON
&& m_print_config->belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly) {
const double sf = m_slicing_params.belt_floor_shear_factor;
const int from_axis = m_slicing_params.belt_floor_from_axis;
const double floor_off = m_print_config->belt_support_floor_offset.value;
const double z_shift_local = m_slicing_params.belt_floor_z_shift
- m_object->belt_global_z_offset();
const double cutoff = (ts_layer->print_z - z_shift_local - floor_off) / sf;
const coord_t cutoff_sc = scale_(cutoff);
const coord_t big = scale_(1e4);
Polygon belt_poly;
if (from_axis == 0) {
if (sf > 0)
belt_poly.points = { {cutoff_sc,-big}, {big,-big}, {big,big}, {cutoff_sc,big} };
else
belt_poly.points = { {-big,-big}, {cutoff_sc,-big}, {cutoff_sc,big}, {-big,big} };
} else {
if (sf > 0)
belt_poly.points = { {-big,cutoff_sc}, {big,cutoff_sc}, {big,big}, {-big,big} };
else
belt_poly.points = { {-big,-big}, {big,-big}, {big,cutoff_sc}, {-big,cutoff_sc} };
}
Polygons belt_surface = { belt_poly };
base_areas = diff_ex(base_areas, belt_surface);
roof_areas = diff_ex(roof_areas, belt_surface);
roof_1st_layer = diff_ex(roof_1st_layer, belt_surface);
floor_areas = diff_ex(floor_areas, belt_surface);
roof_gap_areas = diff_ex(roof_gap_areas, belt_surface);
}
if (SQUARE_SUPPORT) {
// simplify support contours
ExPolygons base_areas_simplified;
@@ -2445,6 +2494,9 @@ void TreeSupport::drop_nodes()
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 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;
const size_t bottom_interface_layers = config.support_interface_bottom_layers.value < 0 ? top_interface_layers : config.support_interface_bottom_layers.value;
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
@@ -2677,15 +2729,22 @@ void TreeSupport::drop_nodes()
node_parent = p_node->parent ? p_node : neighbour;
// Make sure the next pass doesn't drop down either of these (since that already happened).
node_parent->merged_neighbours.push_front(node_parent == p_node ? neighbour : p_node);
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);
get_max_move_dist(next_node);
m_ts_data->m_mutex.lock();
contact_nodes[layer_nr_next].push_back(next_node);
// Belt floor: don't drop merged node below belt surface.
// Treat as object-surface termination (not buildplate) so
// the node gets floor/interface areas instead of base pads.
if (has_belt_floor && print_z_next <= belt_floor_print_z(next_position)) {
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);
get_max_move_dist(next_node);
m_ts_data->m_mutex.lock();
contact_nodes[layer_nr_next].push_back(next_node);
m_ts_data->m_mutex.unlock();
}
neighbour->valid = false;
p_node->valid = false;
m_ts_data->m_mutex.unlock();
}
else if (neighbours.size() > 1) //Don't merge leaf nodes because we would then incur movement greater than the maximum move distance.
{
@@ -2729,6 +2788,12 @@ void TreeSupport::drop_nodes()
ExPolygons overhangs_next = diff_clipped({ node.overhang }, get_collision(0, obj_layer_nr_next));
for(auto& overhang:overhangs_next) {
Point next_pt = overhang.contour.centroid();
// Belt floor: don't drop polygon node below belt surface.
// Treat as object-surface termination (not buildplate).
if (has_belt_floor && print_z_next <= belt_floor_print_z(next_pt)) {
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,
to_buildplate, p_node, print_z_next, height_next);
next_node->max_move_dist = 0;
@@ -2873,6 +2938,12 @@ void TreeSupport::drop_nodes()
if (is_outside) { next_layer_vertex = candidate_vertex; }
}
}
// Belt floor: don't drop regular node below belt surface.
// Treat as object-surface termination (not buildplate).
if (has_belt_floor && print_z_next <= belt_floor_print_z(next_layer_vertex)) {
p_node->to_buildplate = false;
return; // from parallel_for_each lambda
}
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,
@@ -3167,6 +3238,9 @@ void TreeSupport::generate_contact_points()
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;
//First generate grid points to cover the entire area of the print.
BoundingBox bounding_box = m_object->bounding_box();
@@ -3257,6 +3331,10 @@ void TreeSupport::generate_contact_points()
auto insert_point = [&](Point pt, const ExPolygon& overhang, double radius, bool force_add = false, bool add_interface=true) {
// Belt floor: skip contact points whose bottom_z is at or below
// the belt floor at this XY position (overhang rests on the belt).
if (has_belt_floor && bottom_z <= belt_floor_print_z(pt))
return (SupportNode*) nullptr;
Point hash_pos = pt / ((radius_scaled + 1) / 1);
SupportNode* contact_node = nullptr;
if (force_add || !already_inserted.count(hash_pos)) {
@@ -3292,8 +3370,10 @@ void TreeSupport::generate_contact_points()
double radius = unscale_(overhang_bounds.radius());
Point candidate = overhang_bounds.center();
SupportNode *contact_node = insert_point(candidate, overhang, radius, true, true);
contact_node->type = ePolygon;
curr_nodes.emplace_back(contact_node);
if (contact_node) {
contact_node->type = ePolygon;
curr_nodes.emplace_back(contact_node);
}
}
}else{
// otherwise, all nodes should be circle nodes
+4
View File
@@ -446,6 +446,10 @@ private:
bool is_slim = false;
bool with_infill = false;
// Belt printer: compute the belt floor print_z at a given XY position (in slicing coords).
// Returns -infinity if belt floor is not active.
double belt_floor_print_z(const Point &pos_slicing) const;
/*!
+74
View File
@@ -3366,6 +3366,36 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons
// this struct is used to easy retrieve setting. No other function except those in TreeModelVolumes and generate_initial_areas() have knowledge of the existence of multiple meshes being processed.
//FIXME this is a copy
// Contains config settings to avoid loading them in every function. This was done to improve readability of the code.
// Belt printer: add virtual "belt raft" layers below the object so
// organic branches can extend below the model's first layer and
// terminate at the belt surface instead of creating a flat base at Z=0.
{
PrintObject &po = *print.get_object(processing.second.front());
const auto &sp = po.slicing_parameters();
const auto &pcfg = po.print()->config();
const double sf = sp.belt_floor_shear_factor;
if (std::abs(sf) > EPSILON && std::abs(po.belt_global_z_offset()) > EPSILON
&& pcfg.belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly) {
// z_shift_local is the belt surface height at Y=0 in local coords.
// Extend below the belt so the base expansion and build-plate
// termination happen inside the belt region and get clipped.
// Use the distance from the pre-shear bbox min Z to the part's
// post-shear min Z, plus 10mm for base expansion headroom.
double bb_min_z = std::abs(po.model_object()->raw_bounding_box().min.z());
double extra_depth = bb_min_z + 10.;
int num_extra = std::max(0, (int)std::ceil(extra_depth / sp.layer_height));
if (num_extra > 0) {
// Insert belt raft layers at the front, from lowest Z to highest.
std::vector<coordf_t> belt_layers;
belt_layers.reserve(num_extra);
for (int i = num_extra; i >= 1; --i)
belt_layers.push_back(sp.first_object_layer_height - i * sp.layer_height);
// Prepend to existing raft_layers (if any).
auto &rl = processing.first.raft_layers;
rl.insert(rl.begin(), belt_layers.begin(), belt_layers.end());
}
}
}
const TreeSupportSettings &config = processing.first;
BOOST_LOG_TRIVIAL(info) << "Processing support tree mesh group " << counter + 1 << " of " << grouped_meshes.size() << " containing " << grouped_meshes[counter].second.size() << " meshes.";
auto t_start = std::chrono::high_resolution_clock::now();
@@ -3561,6 +3591,43 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons
if (layer) layer->polygons = intersection(layer->polygons, volumes.m_bed_area);
});
// Belt floor: clip ALL organic support layers (including intermediate/base
// fill) against the belt surface. The branch slices were already clipped
// in organic_draw_branches(), but intermediate layers generated between
// branches and the build plate need clipping too.
// Compute the belt floor polygon directly from each layer's print_z
// rather than mapping to a layer index (avoids index mismatch issues).
{
const auto &sp = print_object.slicing_parameters();
const double sf = sp.belt_floor_shear_factor;
const double z_shift = sp.belt_floor_z_shift - print_object.belt_global_z_offset();
const double floor_off = print_object.print()->config().belt_support_floor_offset.value;
const int from_axis = sp.belt_floor_from_axis;
if (std::abs(sf) > EPSILON
&& print_object.print()->config().belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly) {
tbb::parallel_for_each(layers_sorted.begin(), layers_sorted.end(), [&](SupportGeneratorLayer *layer) {
if (!layer || layer->polygons.empty())
return;
double cutoff = (layer->print_z - z_shift - floor_off) / sf;
coord_t cutoff_sc = scale_(cutoff);
coord_t big = scale_(1e4);
Polygon belt_poly;
if (from_axis == 0) {
if (sf > 0)
belt_poly.points = {{cutoff_sc,-big},{big,-big},{big,big},{cutoff_sc,big}};
else
belt_poly.points = {{-big,-big},{cutoff_sc,-big},{cutoff_sc,big},{-big,big}};
} else {
if (sf > 0)
belt_poly.points = {{-big,cutoff_sc},{big,cutoff_sc},{big,big},{-big,big}};
else
belt_poly.points = {{-big,-big},{big,-big},{big,cutoff_sc},{-big,cutoff_sc}};
}
layer->polygons = diff(layer->polygons, Polygons{belt_poly});
});
}
}
print.set_status(69, _L("Generating support"));
generate_support_toolpaths(print_object.support_layers(), print_object.config(), support_params, print_object.slicing_parameters(),
raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers);
@@ -3816,6 +3883,10 @@ void organic_draw_branches(
for (LayerIndex i = 0; i < LayerIndex(slices.size()); ++i) {
slices[i] = diff_clipped(slices[i], volumes.getCollision(0, layer_begin + i, true)); // FIXME parent_uses_min || draw_area.element->state.use_min_xy_dist);
slices[i] = intersection(slices[i], volumes.m_bed_area);
// 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]);
}
size_t num_empty = 0;
if (slices.front().empty()) {
@@ -3850,6 +3921,9 @@ void organic_draw_branches(
//double support_area_min = 0.1 * support_area_min_radius;
for (LayerIndex layer_idx = layer_begin - 1; layer_idx >= layer_bottommost; -- layer_idx) {
rest_support = diff_clipped(rest_support.empty() ? slices.front() : rest_support, volumes.getCollision(0, layer_idx, false));
// Belt floor: clip propagated support at belt surface.
if (layer_idx < LayerIndex(volumes.m_belt_floor.size()) && !volumes.m_belt_floor[layer_idx].empty())
rest_support = diff(rest_support, volumes.m_belt_floor[layer_idx]);
double rest_support_area = area(rest_support);
if (rest_support_area < support_area_stop)
// Don't propagate a fraction of the tree contact surface.
+13 -13
View File
@@ -2210,17 +2210,11 @@ void GCodeViewer::render_toolpaths()
{
const Camera& camera = wxGetApp().plater()->get_camera();
Matrix4f view = camera.get_view_matrix().matrix().cast<float>();
// Belt "designed" view: apply inverse shear to view matrix so toolpaths appear
// upright (as originally designed) instead of sheared on the belt.
if (m_belt_show_designed && m_belt_view_enabled && m_belt_angle_deg > 0.f) {
double angle_rad = Geometry::deg2rad(static_cast<double>(m_belt_angle_deg));
double sin_a = std::sin(angle_rad);
if (sin_a > 1e-6) {
double cot_alpha = std::cos(angle_rad) / sin_a;
Transform3d inverse_shear = Transform3d::Identity();
inverse_shear.matrix()(1, 2) = -cot_alpha; // Y -= Z * cot(α)
view = (camera.get_view_matrix() * inverse_shear).matrix().cast<float>();
}
// Belt "designed" view: apply the precomputed inverse of the full belt
// shear+scale transform so toolpaths appear upright (as originally designed)
// instead of transformed on the belt.
if (m_belt_show_designed && m_belt_view_enabled) {
view = (camera.get_view_matrix() * m_belt_inverse_transform).matrix().cast<float>();
}
const libvgcode::Mat4x4 converted_view_matrix = libvgcode::convert(view);
const libvgcode::Mat4x4 converted_projetion_matrix = libvgcode::convert(static_cast<Matrix4f>(camera.get_projection_matrix().matrix().cast<float>()));
@@ -4418,12 +4412,18 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
if (m_nozzle_nums > 1 && (m_viewer.get_view_type() == libvgcode::EViewType::Summary || m_viewer.get_view_type() == libvgcode::EViewType::ColorPrint)) // ORCA show only on summary and filament tab
render_legend_color_arr_recommen(window_padding);
// Belt printer: toggle for viewing raw slicing-frame G-code
// Belt printer: toggle for viewing designed (upright) vs. machine-frame G-code.
// Rendered with a separator and hint text so users can find it easily.
if (m_belt_view_enabled) {
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
ImGui::Dummy({ window_padding, 0 });
ImGui::SameLine();
ImGui::Checkbox("Show designed view (upright)", &m_belt_show_designed);
ImGui::TextColored(ImVec4(0.f, 0.59f, 0.53f, 1.f), "%s", _u8L("Belt Printer").c_str());
ImGui::Dummy({ window_padding, 0 });
ImGui::SameLine();
ImGui::Checkbox(_u8L("Show designed view (upright) [B]").c_str(), &m_belt_show_designed);
}
legend_height = ImGui::GetCurrentWindow()->Size.y;
+4
View File
@@ -240,6 +240,7 @@ mutable bool m_no_render_path { false };
bool m_belt_view_enabled = false;
float m_belt_angle_deg = 0.f;
bool m_belt_show_designed = false; // Toggle: show designed (upright) view via inverse shear
Transform3d m_belt_inverse_transform{Transform3d::Identity()};
libvgcode::Viewer m_viewer;
bool m_loaded_as_preview{ false };
@@ -341,7 +342,10 @@ public:
void export_toolpaths_to_obj(const char* filename) const;
void set_belt_printer(bool enabled, float angle_deg) { m_belt_view_enabled = enabled; m_belt_angle_deg = angle_deg; }
void set_belt_inverse_transform(const Transform3d& t) { m_belt_inverse_transform = t; }
bool is_belt_view() const { return m_belt_view_enabled && m_belt_angle_deg > 0.f; }
void toggle_belt_show_designed() { if (m_belt_view_enabled) m_belt_show_designed = !m_belt_show_designed; }
bool is_belt_show_designed() const { return m_belt_show_designed; }
size_t get_extruders_count() { return m_extruders_count; }
void push_combo_style();
+10 -2
View File
@@ -3463,8 +3463,16 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE));
break;
}
//case 'B':
//case 'b': { zoom_to_bed(); break; }
case 'B':
case 'b': {
// Toggle belt printer "show designed" view when in G-code preview with belt mode active.
if (dynamic_cast<Preview*>(m_canvas->GetParent()) != nullptr &&
m_gcode_viewer.is_belt_view()) {
m_gcode_viewer.toggle_belt_show_designed();
m_dirty = true;
}
break;
}
case 'C':
case 'c': { wxGetApp().toggle_show_gcode_window(); m_dirty = true; request_extra_frame(); break; }
//case 'G':
+1 -1
View File
@@ -2516,7 +2516,7 @@ void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name
Slic3r::save_object_mesh(*new_object);
// BBS: find an empty cell to put the copied object
auto start_point = wxGetApp().plater()->build_volume().bounding_volume2d().center();
auto start_point = wxGetApp().plater()->build_volume().bed_center();
auto empty_cell = wxGetApp().plater()->canvas3D()->get_nearest_empty_cell({start_point(0), start_point(1)});
new_object->instances[0]->set_offset(center ? to_3d(Vec2d(empty_cell(0), empty_cell(1)), -new_object->origin_translation.z()) : bb.center());
+84 -2
View File
@@ -6907,7 +6907,7 @@ std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs& mode
// BBS: find an empty cell to put the copied object
for (auto& instance : new_instances) {
auto offset = instance->get_offset();
auto start_point = this->bed.build_volume().bounding_volume2d().center();
auto start_point = this->bed.build_volume().bed_center();
bool plate_empty = partplate_list.get_curr_plate()->empty();
Vec3d displacement;
if (plate_empty)
@@ -10991,10 +10991,83 @@ void Plater::priv::set_bed_shape(const Pointfs &shape,
bed.set_belt_printer(true, static_cast<float>(belt_angle));
if (preview)
preview->get_canvas3d()->get_gcode_viewer().set_belt_printer(true, static_cast<float>(belt_angle));
// Compute the inverse of the full belt shear+scale transform for the G-code viewer.
auto compute_shear_factor = [](BeltShearMode mode, double angle_deg) -> double {
double angle_rad = Geometry::deg2rad(angle_deg);
double sin_a = std::sin(angle_rad);
double cos_a = std::cos(angle_rad);
switch (mode) {
case BeltShearMode::PosCot: return (sin_a > EPSILON) ? cos_a / sin_a : 0.;
case BeltShearMode::NegCot: return (sin_a > EPSILON) ? -cos_a / sin_a : 0.;
case BeltShearMode::PosTan: return (cos_a > EPSILON) ? sin_a / cos_a : 0.;
case BeltShearMode::NegTan: return (cos_a > EPSILON) ? -sin_a / cos_a : 0.;
default: return 0.;
}
};
auto compute_scale_factor = [](BeltScaleMode mode, double angle_deg) -> double {
if (mode == BeltScaleMode::None) return 1.;
double angle_rad = Geometry::deg2rad(angle_deg);
double sin_a = std::sin(angle_rad);
double cos_a = std::cos(angle_rad);
switch (mode) {
case BeltScaleMode::InvSin: return (sin_a > EPSILON) ? 1. / sin_a : 1.;
case BeltScaleMode::InvCos: return (cos_a > EPSILON) ? 1. / cos_a : 1.;
case BeltScaleMode::Sin: return sin_a;
case BeltScaleMode::Cos: return cos_a;
default: return 1.;
}
};
// Read shear configs.
auto get_shear_mode = [this](const char *key) -> BeltShearMode {
auto opt = config->option<ConfigOptionEnum<BeltShearMode>>(key);
return opt ? opt->value : BeltShearMode::None;
};
auto get_axis = [this](const char *key) -> BeltAxis {
auto opt = config->option<ConfigOptionEnum<BeltAxis>>(key);
return opt ? opt->value : BeltAxis::X;
};
auto get_scale_mode = [this](const char *key) -> BeltScaleMode {
auto opt = config->option<ConfigOptionEnum<BeltScaleMode>>(key);
return opt ? opt->value : BeltScaleMode::None;
};
struct AxisShear { BeltShearMode mode; double angle; int from; };
AxisShear axes[3] = {
{ get_shear_mode("belt_shear_x"), config->opt_float("belt_shear_x_angle"), int(get_axis("belt_shear_x_from")) },
{ get_shear_mode("belt_shear_y"), config->opt_float("belt_shear_y_angle"), int(get_axis("belt_shear_y_from")) },
{ get_shear_mode("belt_shear_z"), config->opt_float("belt_shear_z_angle"), int(get_axis("belt_shear_z_from")) },
};
Transform3d belt_shear = Transform3d::Identity();
for (int row = 0; row < 3; ++row) {
if (axes[row].mode != BeltShearMode::None) {
double factor = compute_shear_factor(axes[row].mode, axes[row].angle);
if (std::abs(factor) > EPSILON)
belt_shear.matrix()(row, axes[row].from) += factor;
}
}
double sx = compute_scale_factor(get_scale_mode("belt_scale_x"), config->opt_float("belt_scale_x_angle"));
double sy = compute_scale_factor(get_scale_mode("belt_scale_y"), config->opt_float("belt_scale_y_angle"));
double sz = compute_scale_factor(get_scale_mode("belt_scale_z"), config->opt_float("belt_scale_z_angle"));
Transform3d belt_scale = Transform3d::Identity();
belt_scale.matrix()(0, 0) = sx;
belt_scale.matrix()(1, 1) = sy;
belt_scale.matrix()(2, 2) = sz;
// Forward transform: scale * shear. Inverse for the viewer.
Transform3d forward = belt_scale * belt_shear;
Transform3d inverse = forward.inverse();
if (preview)
preview->get_canvas3d()->get_gcode_viewer().set_belt_inverse_transform(inverse);
} else {
bed.set_belt_printer(false, 0.f);
if (preview)
if (preview) {
preview->get_canvas3d()->get_gcode_viewer().set_belt_printer(false, 0.f);
preview->get_canvas3d()->get_gcode_viewer().set_belt_inverse_transform(Transform3d::Identity());
}
}
}
@@ -13286,6 +13359,15 @@ void Plater::load_gcode(const wxString& filename)
current_print.set_gcode_file_ready();
// Belt printer: detect belt_printer_angle from loaded G-code header and enable
// belt view mode on the GCodeViewer so the "Show designed view" toggle appears.
if (current_result->belt_printer_angle > 0.f) {
float angle = current_result->belt_printer_angle;
p->preview->get_canvas3d()->get_gcode_viewer().set_belt_printer(true, angle);
} else {
p->preview->get_canvas3d()->get_gcode_viewer().set_belt_printer(false, 0.f);
}
// show results
p->preview->reload_print(m_only_gcode);
//BBS: zoom to bed 0 for gcode preview
+89 -52
View File
@@ -4364,58 +4364,6 @@ void TabPrinter::build_fff()
optgroup->append_single_option_line(option, "printer_basic_information_printable_space#excluded-bed-area");
// optgroup->append_single_option_line("printable_area");
optgroup->append_single_option_line("printable_height", "printer_basic_information_printable_space#printable-height");
optgroup->append_single_option_line("build_plate_tilt_x");
optgroup->append_single_option_line("build_plate_tilt_y");
optgroup->append_single_option_line("belt_printer");
optgroup->append_single_option_line("belt_printer_angle");
optgroup->append_single_option_line("belt_printer_infinite_y");
// Per-axis shear: group mode + angle + source on one row per axis
{
Line line = { L("Shear X"), L("Shear applied to the X axis before slicing") };
line.append_option(optgroup->get_option("belt_shear_x"));
line.append_option(optgroup->get_option("belt_shear_x_angle"));
line.append_option(optgroup->get_option("belt_shear_x_from"));
optgroup->append_line(line);
}
{
Line line = { L("Shear Y"), L("Shear applied to the Y axis before slicing") };
line.append_option(optgroup->get_option("belt_shear_y"));
line.append_option(optgroup->get_option("belt_shear_y_angle"));
line.append_option(optgroup->get_option("belt_shear_y_from"));
optgroup->append_line(line);
}
{
Line line = { L("Shear Z"), L("Shear applied to the Z axis before slicing") };
line.append_option(optgroup->get_option("belt_shear_z"));
line.append_option(optgroup->get_option("belt_shear_z_angle"));
line.append_option(optgroup->get_option("belt_shear_z_from"));
optgroup->append_line(line);
}
{
Line line = { L("Scale X"), L("Scale applied to the X axis before slicing") };
line.append_option(optgroup->get_option("belt_scale_x"));
line.append_option(optgroup->get_option("belt_scale_x_angle"));
optgroup->append_line(line);
}
{
Line line = { L("Scale Y"), L("Scale applied to the Y axis before slicing") };
line.append_option(optgroup->get_option("belt_scale_y"));
line.append_option(optgroup->get_option("belt_scale_y_angle"));
optgroup->append_line(line);
}
{
Line line = { L("Scale Z"), L("Scale applied to the Z axis before slicing") };
line.append_option(optgroup->get_option("belt_scale_z"));
line.append_option(optgroup->get_option("belt_scale_z_angle"));
optgroup->append_line(line);
}
{
Line line = { L("G-code axis remap"), L("Remap slicing-frame axes to machine axes in G-code output") };
line.append_option(optgroup->get_option("belt_gcode_remap_x"));
line.append_option(optgroup->get_option("belt_gcode_remap_y"));
line.append_option(optgroup->get_option("belt_gcode_remap_z"));
optgroup->append_line(line);
}
optgroup->append_single_option_line("support_multi_bed_types","printer_basic_information_printable_space#support-multi-bed-types");
optgroup->append_single_option_line("best_object_pos", "printer_basic_information_printable_space#best-object-position");
// todo: for multi_extruder test
@@ -4434,6 +4382,68 @@ void TabPrinter::build_fff()
//option.opt.full_width = true;
//optgroup->append_single_option_line(option);
optgroup->append_single_option_line("disable_m73", "printer_basic_information_advanced#disable-set-remaining-print-time");
optgroup->append_single_option_line("build_plate_tilt_x");
optgroup->append_single_option_line("build_plate_tilt_y");
optgroup->append_single_option_line("belt_printer");
optgroup->append_single_option_line("belt_printer_angle");
optgroup->append_single_option_line("belt_printer_infinite_y");
// Per-axis shear: group mode + angle + source on one row per axis
{
Line line = { L("Mesh shear X"), L("Shear applied to the X axis before slicing") };
line.append_option(optgroup->get_option("belt_shear_x"));
line.append_option(optgroup->get_option("belt_shear_x_angle"));
line.append_option(optgroup->get_option("belt_shear_x_from"));
line.append_option(optgroup->get_option("belt_shear_x_global"));
optgroup->append_line(line);
}
{
Line line = { L("Mesh shear Y"), L("Shear applied to the Y axis before slicing") };
line.append_option(optgroup->get_option("belt_shear_y"));
line.append_option(optgroup->get_option("belt_shear_y_angle"));
line.append_option(optgroup->get_option("belt_shear_y_from"));
line.append_option(optgroup->get_option("belt_shear_y_global"));
optgroup->append_line(line);
}
{
Line line = { L("Mesh shear Z"), L("Shear applied to the Z axis before slicing") };
line.append_option(optgroup->get_option("belt_shear_z"));
line.append_option(optgroup->get_option("belt_shear_z_angle"));
line.append_option(optgroup->get_option("belt_shear_z_from"));
line.append_option(optgroup->get_option("belt_shear_z_global"));
optgroup->append_line(line);
}
{
Line line = { L("Mesh scale X"), L("Scale applied to the X axis before slicing") };
line.append_option(optgroup->get_option("belt_scale_x"));
line.append_option(optgroup->get_option("belt_scale_x_angle"));
optgroup->append_line(line);
}
{
Line line = { L("Mesh scale Y"), L("Scale applied to the Y axis before slicing") };
line.append_option(optgroup->get_option("belt_scale_y"));
line.append_option(optgroup->get_option("belt_scale_y_angle"));
optgroup->append_line(line);
}
{
Line line = { L("Mesh scale Z"), L("Scale applied to the Z axis before slicing") };
line.append_option(optgroup->get_option("belt_scale_z"));
line.append_option(optgroup->get_option("belt_scale_z_angle"));
optgroup->append_line(line);
}
{
Line line = { L("G-code axis remap"), L("Remap slicing-frame axes to machine axes in G-code output") };
line.append_option(optgroup->get_option("belt_gcode_remap_x"));
line.append_option(optgroup->get_option("belt_gcode_remap_y"));
line.append_option(optgroup->get_option("belt_gcode_remap_z"));
optgroup->append_line(line);
}
{
Line line = { L("Support floor"), L("Belt floor awareness for support generation and clipping") };
line.append_option(optgroup->get_option("belt_support_floor_mode"));
line.append_option(optgroup->get_option("belt_support_floor_offset"));
line.append_option(optgroup->get_option("belt_support_z_offset_mode"));
optgroup->append_line(line);
}
option = optgroup->get_option("thumbnails");
option.opt.full_width = true;
optgroup->append_single_option_line(option, "printer_basic_information_advanced#g-code-thumbnails");
@@ -5283,6 +5293,33 @@ void TabPrinter::toggle_options()
"belt_scale_x", "belt_scale_y", "belt_scale_z",
"belt_gcode_remap_x"})
toggle_line(el, is_belt);
// Gray out angle/from sub-options when their parent shear/scale mode is None.
auto sx = m_config->option<ConfigOptionEnum<BeltShearMode>>("belt_shear_x")->value;
toggle_option("belt_shear_x_angle", is_belt && sx != BeltShearMode::None);
toggle_option("belt_shear_x_from", is_belt && sx != BeltShearMode::None);
toggle_option("belt_shear_x_global", is_belt && sx != BeltShearMode::None);
auto sy = m_config->option<ConfigOptionEnum<BeltShearMode>>("belt_shear_y")->value;
toggle_option("belt_shear_y_angle", is_belt && sy != BeltShearMode::None);
toggle_option("belt_shear_y_from", is_belt && sy != BeltShearMode::None);
toggle_option("belt_shear_y_global", is_belt && sy != BeltShearMode::None);
auto sz = m_config->option<ConfigOptionEnum<BeltShearMode>>("belt_shear_z")->value;
toggle_option("belt_shear_z_angle", is_belt && sz != BeltShearMode::None);
toggle_option("belt_shear_z_from", is_belt && sz != BeltShearMode::None);
toggle_option("belt_shear_z_global", is_belt && sz != BeltShearMode::None);
auto scx = m_config->option<ConfigOptionEnum<BeltScaleMode>>("belt_scale_x")->value;
toggle_option("belt_scale_x_angle", is_belt && scx != BeltScaleMode::None);
auto scy = m_config->option<ConfigOptionEnum<BeltScaleMode>>("belt_scale_y")->value;
toggle_option("belt_scale_y_angle", is_belt && scy != BeltScaleMode::None);
auto scz = m_config->option<ConfigOptionEnum<BeltScaleMode>>("belt_scale_z")->value;
toggle_option("belt_scale_z_angle", is_belt && scz != BeltScaleMode::None);
toggle_line("belt_support_floor_mode", is_belt);
}