Compare commits

..
Author SHA1 Message Date
Joseph Robertson c57bf3c044 Belt printer: sync with main, plus the fixes the merge needs (#16145)
Brings `belt-printer` up to date with `main` (4b4a261787) so that #14394
merges cleanly again, and adds the follow-up fixes the merge needs. This
PR targets `belt-printer`, not `main`.

## Commits

1. **GCode: hold the writer by value again.**
- Belt printing had turned `GCode::m_writer` into a `unique_ptr`, so
that `BeltGCode` could swap in a new writer carrying the belt
kinematics.
- Nothing subclasses `GCodeWriter`, and `set_kinematics()` can install
the belt mapping on the existing writer. This commit removes the swap.
- About 190 `m_writer->` edits revert, which takes `GCode.cpp` from 29
conflict hunks with main down to 3.
- The G-code is identical to the current `belt-printer` head on two
BabyBelt projects (see Verification).
2. **Merge upstream/main.** The resolutions are listed in the merge
commit. The ones that needed a decision:
- `write_belt_header()` follows main's relocated header block (#15897,
#15915).
- First-layer acceleration keeps the per-path first-layer plane test,
now with main's cached nozzle index (#16028).
- The arc-to-polyline fallback moves into the out-param
`extrude_arc_to_xy`, which is the overload `GCode` now calls (#16108).
   - The belt fields join `GCodeProcessorResult`'s forwarding assign.
   - The Clipper2 renames (#15969).
   - Belt printers still reserve no CLI wipe tower (#15837).
- The new sparse-layer tower options are hidden for belt printers
(#15841).
3. **Belt: register the raw G-code toggle as a Preview shortcut.**
- Main's assignable shortcuts (#15706) replaced the key switch that
carried **B**.
- The toggle is now `ToggleBeltRawGcode`, bound to B in the Preview,
where B was free. It is listed in the shortcuts dialog and can be
rebound, and the legend shows whichever key is bound.
4. **Precise Seam: slice modifiers in the belt slicing frame.**
- The new `slice_single_volume_regions()` (#16072) sliced modifiers with
`trafo_centered()`, so on belt prints the modifier regions landed in the
unrotated frame.
- It now uses `trafo_sliced()`, as the seam enforcers and support
volumes already do. On non-belt printers the two transforms are the
same.
   - A regression test is included.
5. **Belt profiles: inherit what they repeat and pass main's profile
checks.**
- The BabyBelt Pro and IR3 V2 filaments name their single extruder
variant, as the library-based filaments of other vendors do, and drop
overrides that repeat the library value.
- The Custom belt base inherits `printer_extruder_id` from its parent.
- `normalize` drops the obsolete keys. `fix-variant` gives the machine
limits their silent-mode entry, which they previously read from the
single value.
   - All three vendor versions are bumped.
- Printcepts and IdeaFormer keep their own machine and process bases,
because only filaments can inherit across vendor bundles.
6. **Profile validator: accept a belt printer's tool change without a
tower.**
- The slice sweep (185cfe4323) forces a prime tower and requires its `CP
TOOLCHANGE START` block.
- Belt printers have no wipe tower: they purge into a prism object.
Their filament change is the plain `T` command, which they emit
throughout the slice.
- On belt printers the validator now looks for that `T1` line instead.

## Verification

All on Linux.

- **`m_writer` equivalence.** The current `belt-printer` head
(b22384a559) and commit 1 were each built and used to CLI-slice two
BabyBelt benchy projects (389k and 804k lines of G-code). The output is
identical apart from the per-run object ids in the `printing object …
id:` comments.
- **Merged branch, belt-specific checks.**
- The belt header is written, no `;_BELT_BAND` markers leak, and the
belt axis never steps back.
- Against the Oct 3 belt + main merge, the only G-code differences are
fill ordering on a few layers and time estimates. Both come from main's
changes since then.
- Against the pre-merge output the differences are much larger. That is
expected: main's CLI now refreshes a project's settings from its system
presets (#15953, #16038), so for example `z_hop` follows the belt
profiles' 0.
- **Tests.**
- `fff_print_tests`: 345/345 test cases pass, including 30 `[belt]`
cases.
  - `libslic3r_tests`: 1081 passed, 2 skipped.
- The new Precise Seam test fails 266 of its 284 assertions with the fix
reverted.
- **Profiles.**
  - `scripts/orca_profile_tool.py check` passes for all 69 vendors.
- `OrcaSlicer_profile_validator -s -l 2`: all 1271 slices succeed.
Before commit 6, the six belt printers failed.
- A flattened before/after snapshot of every belt preset shows no value
a belt printer reads has changed.
- **GUI** (BabyBelt Pro, clean datadir):
- B switches the Preview between the designed and the raw machine-frame
G-code, and pressing it again restores the view exactly.
  - The legend reads "Show raw G-code (belt only) [B]".
- The shortcuts dialog lists the toggle under Preview → Display as
rebindable.
- The sparse-layer tower options stay hidden in Advanced and Expert
modes.

Main has since gained one CI-only commit (f3d0b8a553), which merges
cleanly on top.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01AJzy1xeQV3FePh5HfahDyn
2026-10-04 23:13:41 -05:00
harrierpigeon 7eb9aa3832 Profile validator: accept a belt printer's tool change without a tower
The validator slices every printer with two filaments and the prime tower
forced on, then requires the tower's CP TOOLCHANGE START block as proof
that change_filament_gcode ran. A belt printer has no wipe tower: it purges
into a prism object on the belt, so Print::has_wipe_tower() is false and
the change is the plain T command set_extruder() emits. All six belt
printers failed the sweep on that alone, although each one changes
filament throughout the slice. Look for the T1 line on belt printers.
2026-10-04 22:57:53 -05:00
harrierpigeon 56f5ff8128 Belt profiles: inherit what they repeat and pass main's profile checks
main's profile checks reject the belt bundles: their filaments override
variant keys with one value under the library's six-variant presets, the
copied vendor commons carry keys the slicer no longer reads, the IR3 V2
and BabyBelt Pro machine limits miss the silent-mode entry, and the Custom
belt base pins printer_extruder_id to one entry under a three-variant list.

- The BabyBelt Pro and IR3 V2 filaments name the one extruder variant
  their printers have, as the other vendors' filaments built on the
  library do, and drop every override that only repeats the library value
  (diameter, density, temperature range, most of the fan settings...).
  The eSUN filaments inherit the narrowed list.
- The Custom belt base inherits printer_extruder_id from its base.
- normalize drops silent_mode, adaptive_layer_height and
  tree_support_with_infill; fix-variant gives the machine limits their
  silent-mode entry, which they read from the single value before.
- The IR3 V2 drops two limits equal to its base in both modes.
- Custom's index is regenerated and all three vendor versions bumped.

Flattening every belt preset before and after, nothing a belt printer
reads changes: printer_extruder_id is one id per variant, all 1.

Printcepts and IdeaFormer keep their own machine and process bases: only
filaments can inherit across vendor bundles (from OrcaFilamentLibrary), so
they cannot build on the Custom belt printer.
2026-10-04 19:28:36 -05:00
harrierpigeon 1b4153f0cd Precise Seam: slice modifiers in the belt slicing frame
slice_single_volume_regions() sliced Precise Seam modifiers with
trafo_centered(), but a belt printer slices its layers with
trafo_sliced(): the belt rotation, any pre-slice remap and the lift off
the plate on top. On a belt print the modifier regions landed in the
unrotated frame, away from the walls they were meant to place the seam
on. Slice them with trafo_sliced(), as the support volumes and the seam
enforcers already are. It equals trafo_centered() off a belt printer.
2026-10-04 19:22:42 -05:00
harrierpigeon e8a499df7b Belt: register the raw G-code toggle as a Preview shortcut
Main's assignable shortcuts replaced the canvas key switch that carried
the belt "show designed / show raw G-code" toggle on B. Register it as
ToggleBeltRawGcode, bound to B in the Preview (B is only taken on the
Plater, by the mesh boolean gizmo), so it can be rebound and is listed in
the shortcuts dialog. The legend checkbox shows whatever key is bound.
2026-10-04 19:21:22 -05:00
harrierpigeon bbb94724ba Merge upstream/main into belt-printer
Brings belt-printer up to main 4b4a261787. Resolutions:

- G-code header (#15897, #15915): main moved the header, config and
  thumbnail block later in _do_export; write_belt_header() moves with it,
  still after the thumbnails and outside the BTT_TFT gate.
- _extrude: first-layer acceleration keeps the per-path first-layer plane
  test with main's cached nozzle index (#16028); main's set_speed out-param
  form (#16108) everywhere else.
- GCodeWriter (#16108): the arc-to-polyline fallback for machine mappings
  that cannot express G2/G3 now runs in the out-param extrude_arc_to_xy,
  which is the overload GCode calls, and appends to the caller's string.
- GCodeProcessorResult: the belt fields join main's forwarding assign.
- Clipper2 (#15969): belt arrange helpers take Slic3r::Point; the tree
  support join types lose their ClipperLib qualifier.
- CLI arrange (#15837): belt printers still reserve no wipe tower.
- Wipe tower options (#15841): the two new sparse-layer toggles are hidden
  for belt printers like the rest of the tower options.
- Keyboard shortcuts (#15706): main's registry replaces the old key switch;
  the belt view toggle is re-registered in the next commit.
- Print::process: the belt purge-plan undo runs before main's SliceStarted
  event.
- scripts/filament_id_snapshot.json: deleted on main (a77209af8f).
- Includes and appended tests: union of both sides.
2026-10-04 19:20:33 -05:00
harrierpigeon ee88b3f0b0 GCode: hold the writer by value again
Belt printing turned GCode::m_writer into a unique_ptr so BeltGCode could
swap in a freshly built writer carrying the belt kinematics. Nothing
subclasses GCodeWriter: the machine mapping lives in its MachineKinematics,
which set_kinematics() installs on an existing writer. A GCode is built for
every export and the only state on the writer when init_belt_writer() runs
is the plate offset, which the swap had to copy across by hand.

Install the belt kinematics on the writer in place, drop the copied offset,
and drop the virtual markers on GCodeWriter that the old subclass needed.
Every m_writer-> in GCode.cpp goes back to m_writer., which is most of the
belt diff in that file and most of its conflicts with main.

The pressure-advance pattern keeps its shared_ptr writer: the unique_ptr
kinematics make GCodeWriter move-only and that class must stay copyable.
2026-10-04 19:16:59 -05:00
Joseph Robertson b22384a559 Belt Printer Updates - Oct 4 (#16127)
# Belt Printing Bug Fixes & Feature Updates


This should be the majority of substantive work keeping ``belt-printer``
from being ready to merge into ``main``. It includes Hanif Koh's review
fixes from #15685 and the answers to his review on #14394, findings from
running the branch on a BabyBelt Pro and an IR3 V2, crash fixes
contributed by Unlayered3D, and arrange and purge-tower changes for
multi-colour belt prints.

The merge of current `main` into this branch is prepared and tested
locally. The conflicts are in the acceleration refactor of
`GCode::_extrude`, the ClipperLib namespace clean-up and a few test
files.

Tested with `libslic3r_tests`, `fff_print_tests` and `libnest2d_tests`
on Linux, validated on a stock Klipper BabyBelt Pro.

## New features

**Belt arrangement.** Parts of the same colour are grouped along the
belt into a single print run. Packing starts at the end that prints
first, following the slicing rotation and the sign of the angle. Arrange
reserves the purge prism's strip and the brim width along the bed edges,
then regenerates the prism from the result instead of moving it as a
part. Piles aimed at an off-centre `best_object_pos` are clamped to the
bed. Grouping uses a soft cost: if the belt is too short for separate
runs, colours overlap rather than move to another plate.

**Purge tower sizing.** The prism stops at the plate end. The purge
planner's existing warning reports what a shortened bar can't absorb. A
brim is accepted next to the purge tower again because the purge plan's
layer-grid shift now also moves the brim's apron bands.

**First-layer fan band.** On a belt, "the first layers" are a band along
the belt rather than the first slicing layers. The generator marks where
each extrusion enters and leaves the band. The cooling buffer keeps the
fan off inside it on every layer, taking precedence over overhang and
bridge fan requests.
Thanks to:
@Unlayered3D, @shubhracc, @dlc60, @Rexit

**Profiles.** Z-hop defaults to 0 on belt printer bases and belt
filaments; it can be turned back on. Axis remap options are shown only
in Develop mode. IdeaFormer, Printcepts and Custom bundle versions are
bumped.

Thanks to: @RobMink, @Rexit

## Bug fixes


- Scarf joint seams no longer start below the layer on a belt.
Previously, each seam caused a 0.28 mm belt back-step into the previous
layer ("the belt jumped backwards and the head hit the part"). — credit:
@dlc60
- The CLI no longer rejects every belt print with -102. The
printable-height check compared machine Z, which is belt travel on a
belt printer.
- The belt header is written outside the optional file header block, so
printers with a BTT TFT thumbnail still get belt view in the preview. —
credit: BabyBelt Discord
- The dormant tilted-bed rendering is removed from Prepare view; the bed
is shown as the slicing pipeline treats it. — credit: HanifKoh
- Plate icons, number and name no longer run across the neighbouring
plate on a long, narrow bed; their scale is bounded by the gap between
plates.
- Modifiers and support blockers no longer extend a belt object's sliced
range. The `is_model_part` filter had gone missing with some debug
logging. — credit: HanifKoh
- Crossing-perimeter avoidance no longer dereferences a null layer in
either pass while travelling on a brim apron layer. — credit:
Unlayered3D
- 3MF files with non-finite vertex coordinates are rejected instead of
crashing qhull during load. — credit: Unlayered3D
- The CLI no longer crashes on a project without `printable_height` or
with fewer filaments than were loaded. — credit: Unlayered3D
- The island tour cache is keyed on the island layout, preventing
out-of-bounds reads on later layers with fewer islands. — credit:
Unlayered3D
- The top/bottom painting projection no longer erases from an empty
vector when no shell layers are requested. — credit: Unlayered3D
- Belt purge planning detects filament changes by scanning the tool
ordering instead of checking the first layer's flag, which a brim apron
layer never carries. — credit: Unlayered3D
- The purge prism never gets a brim, regardless of its config. — credit:
Unlayered3D
- Containment tests treat the plate as open along Y on an infinite-Y
belt printer. — credit: Unlayered3D
- Belt brim lattice lines close to the belt move uphill; narrow bands no
longer get near-duplicate lines.
- Organic supports that reach the belt slice without negative flow.
- Hanif Koh's review items: restored the gantry clearance check in
`Print::validate`, read the pre-slice remap header at its real length,
removed unused `clip_support_fills()` and the two unimplemented support
floor modes (legacy values map to `none`), dropped the per-extrusion
transform determinant, indexed apron layers into the first layer's
nozzle map, read the brim axis from the config, removed tagged
diagnostic logging and planning-doc references, and documented the
exclude-object frame. — credit: Hanif Koh
- Hanif Koh's fixes from #15685 include the plate offset in the belt
writer, painted supports and seams under the belt transform, shared
build-plate tilt helpers, the belt header as the source of the tilt,
brim band loop and filament, and G-code export invalidation. — credit:
hanifkoh

At this time there are no known issues with belt printing nor any known
regressions in non-belt-printing execution paths. I have been using
these builds for all of my printing for several months now and have had
no issues.
2026-10-04 11:29:42 -05:00
harrierpigeon 69e7cc1544 Preview: judge a belt print's toolpaths by their back-transformed box
all_paths_inside() accepts the path bounding box only within 3*EPSILON of the
bed and otherwise tests every move; a belt print's moves are machine-frame
coordinates whose Z is belt travel, so that test can never pass, and the
designed view's min-corner anchor leaves the box a fraction of a millimetre
below zero. Every multi-object belt plate therefore reported a path beyond the
plate. The belt preview now judges the back-transformed box with a millimetre
of room.
2026-10-03 19:19:02 -05:00
harrierpigeon 6a0d07664f Belt purge tower: one prism per plate
ensure_belt_purge_tower only ever looked at the current plate and kept a single
prism, so the other plates had no tower and switching plates moved the one
prism around. Every plate is now planned on its own: a prism that lies on no
plate is stale, a plate whose prism matches its recorded inputs is left alone,
the rest are deleted and recreated, highest index first.
2026-10-03 18:59:01 -05:00
harrierpigeon ca934716f5 Plate icons: keep them inside the gap to the next plate
The plate's icons, number and name scale with the plate's depth, but they sit in
the gap to the next plate, which scales with its width. On a long, narrow bed
(a 95 x 500 mm belt) they came out 40 mm wide and ran across the neighbouring
plate. The scale is now also bounded by the gap, which leaves ordinary beds
unchanged.
2026-10-03 15:39:49 -05:00
harrierpigeon 82c462ab2a Prepare view: drop the dormant tilted-bed rendering
Plater::set_bed_shape read the belt keys from the plater's own config, which
never carries them, so the branch that tilted the bed model, drew the slicing
arrow and plane and switched the build volume to belt mode never ran. The
Prepare view shows the bed as the slicing pipeline treats it, flat; the
gravity arrow from build_plate_tilt stays, as does the preview's belt view,
which takes its angle from the G-code header.
2026-10-03 13:12:41 -05:00
harrierpigeon 2782374d8d Belt: write the belt header outside the optional file header block
The G-code viewer takes the belt tilt only from the belt header comments, but
they were written inside the header block that is left out when a BTT TFT
thumbnail is configured, so such a printer never got belt view. The comments
are not part of the header block; they go after it, and after the thumbnails
that firmware needs first.
2026-10-03 13:12:41 -05:00
harrierpigeon 0c3b7bc72e Belt brim: move the apron bands with the purge plan's layer grid
The purge plan snaps every object onto one layer grid after the brim is built;
the per-layer brim bands follow their layers but the apron bands below the
first layer carry their own print_z and were left behind, which is why a brim
was refused next to a purge tower object. The shift now moves them too and the
combination is accepted again.
2026-10-03 12:03:18 -05:00
harrierpigeon 6b304884d2 Belt arrange: group colours along the belt, keep the purge tower's strip free, stop the tower at the plate end
On a belt the parts print in belt order, so every colour change between parts
is a filament change. Arrange packs items in extruder order already, but it
grew the pile around its centre, so the colours ended up interleaved. A belt
print now packs from the leading end of the bed, each row filling across the
belt before the pile advances, and the objective charges an item for every
packed part of another colour it does not fully follow along the belt,
counting the tilted layers that reach cot(angle) * height past a part, so
each colour prints as one run. The direction follows the slicing rotation: a
rotation about X prints toward +Y, one about Y toward -X, and a negative angle
flips it. Packing from the edge also means the brim has to be kept on the
bed: a belt brim is printed brim_width wide for every brim type, so that much
is reserved along every edge (between parts the brims may overlap, as on any
printer).

The purge prism is regenerated from the arranged parts, flush with the far
edge of the bed, yet arrange moved it about like a part and packed parts into
the strip it comes back to. Arrange now skips the prism and reserves its strip
with a fixed virtual item, like a bed exclusion area, whenever the parts use
more than one filament.

The prism's length follows the parts plus a ramp per unit of height; with the
height at its cap that ran 100 mm past the end of a 500 mm belt and the project
could not print. The bar now stops at the plate end, and the purge planner's
existing warning reports what the shortened bar cannot absorb.
2026-10-03 12:03:18 -05:00
harrierpigeon 488c6d8226 Belt profiles: bump the IdeaFormer and Printcepts bundle versions
The IR3 V2 and BabyBelt Pro printer profiles changed since the last bump; the
updater only installs a strictly newer bundle.
2026-10-03 10:02:36 -05:00
harrierpigeon d35af1806a Belt cooling: keep the fan off in the band inside the layer cooling pass
The band was a second pass over the finished layer that fought the fan commands
the layer pass had already written (overhang, bridge and resume requests). The
generator now marks where each segment enters and leaves the band and the layer
pass treats the band as the strongest fan request, so there is one place that
decides the fan.
2026-10-02 12:46:44 -05:00
harrierpigeon 5ac0dd19d0 Belt brim: no near-duplicate lines in a narrow lattice band
A band barely wider than one line got a second line almost on top of the first,
and lattice rows could repeat within half a pitch.
2026-10-02 12:46:44 -05:00
harrierpigeon 550229b0fa Belt validation: refuse a brim next to a purge tower object
The purge plan moves objects onto a common layer grid after the brim bands are
built, so the two cannot share a print. The prime tower setting alone still does
not block a brim. The missing-prism warning now counts the filaments the objects
use, as the GUI does.
2026-10-02 12:46:44 -05:00
harrierpigeon 997fac8752 GCodeProcessor: key the belt height-check skip on belt_printer
The check was skipped for any machine-frame transform; what makes the printable
height meaningless is the belt axis, so ask for that directly.
2026-10-02 12:46:44 -05:00
harrierpigeon 007071bd56 GCode: skip the second crossing-avoidance pass on a brim apron layer too
travel_to runs avoid-crossing a second time after a wipe; that call needs the
current Layer as much as the first one does.
2026-10-02 12:46:44 -05:00
harrierpigeon 7a23d9ea2c Belt slicing: only model parts set the layer range again
The filter was lost with the debug logging it shared an #if with, so modifiers
and support blockers stretched the sliced range of a belt object.
2026-10-02 12:46:43 -05:00
harrierpigeon bfbf5ad1c2 Arrange: keep a pile aligned to an off-centre point on the bed
With best_object_pos away from the bed centre the placer packs the pile
inside the bin and then translates it so its centre lands on that point,
without checking that it still fits there. A belt printer aims at the
leading end of the belt (BabyBelt Pro: 0.5, 0.05), so any pile longer than
the 50 mm around that point was pushed past the edge: four 90 mm parts on
the 95 x 500 mm belt ended with one across the edge and one outside while
290 mm of belt stayed free.

The final alignment now stops the pile at the edge of the bin; the items'
inflated boxes leave the object spacing as the margin. A pile that does not
fit along an axis is centred on it, as before.
2026-10-02 12:09:59 -05:00
harrierpigeon 31494a9836 tests: give the belt fan band test a band the walls reach 2026-10-02 02:42:14 -05:00
harrierpigeon 441ae8e877 Belt: decide the fan band per extrusion segment
A tilted layer runs from the belt to the top of the part, so a wall loop
that starts above the belt still passes along it. Tagging only the path's
first point left such loops out of the band entirely; the band is now
evaluated at each segment, with the tag capped where the fan stops
depending on it.
2026-10-02 02:36:13 -05:00
harrierpigeon 461063856d tests: fix two belt test expectations
The clearance test needs the relative-E reset in its layer change G-code to
get past validate()'s other checks, and now asserts the height message. The
fan band test counts cycles rather than commands: the band is decided per
path start, so a cube cycles the fan far less often than a benchy.
2026-10-02 02:33:14 -05:00
harrierpigeon 1b392955de tests: organic tree supports reaching the belt slice without a negative flow
Covers the case from Hanif Koh's review of #14394 (belt raft layers below
the object with no lower bound), which the negative-Z bottom layer fix in
layer_initialize() addresses.
2026-10-02 01:13:29 -05:00
harrierpigeon 3beb448ae6 Belt brim: lattice lines closer to the belt than the band fraction move uphill
With a first layer of about 0.28 mm or more at 45 degrees (or a shallower
belt) the brim band is wider than one bead and its lines go on the nominal
lattice. A lattice line could land where the belt is almost at the band's
print_z; its flow was clamped to half a layer while the nozzle sat nearly on
the belt. Such a line now moves uphill to the 0.75 fraction the single-line
case uses, and a line that lands on the previous one is skipped.

Ported from the Unlayered fork (patch 0007 of its belt port series, found
there by fuzzing first layer heights). The fork's companion fix, restricting
the brim filament to those the writer was handed (0008), is not needed here:
ToolOrdering registers the brim filament on every band's layer, so the writer
always has it. A test pins that with every object a flush target.
2026-10-02 01:13:29 -05:00
harrierpigeon 412564cae3 Belt: document the frame of the exclude-object outlines
EXCLUDE_OBJECT_DEFINE keeps plate coordinates on a belt printer: the frame
after the slicing rotation is undone and before the G-code axis remap and
machine-frame shear, which is where the object stands on the belt.

Raised in Hanif Koh's review of #14394.
2026-10-02 01:11:59 -05:00
harrierpigeon 360a68e078 Belt: warn when the purge tower is enabled but the project has no tower object
The purge tower is a model object the GUI creates and sizes, and libslic3r
only purges into one that exists. A multi-filament belt project sliced from
the CLI without it changed filament with nowhere to purge, silently.

Raised in Hanif Koh's review of #14394.
2026-10-02 01:11:59 -05:00
harrierpigeon 556569c0e3 Belt: drive the first-layer fan band from the generator, not from parsed moves
The cooling buffer's band pass rebuilt positions from the layer's G-code
and tested them against the first-layer plane. The G-code is in machine
coordinates and the plane is in slicing coordinates, so on the shipped
profiles the nearest move was over 100 mm from a 0.2 mm band and the pass
never changed the fan. GCode::_extrude() already knows each path's height
above the belt, so it now tags the band changes and the buffer applies and
strips the tags.

The pass also took the S of every M106 as the part fan, whatever its P
index, and stored that 0..255 value where a percentage was expected (an
auxiliary fan line came back as M106 S651); it now uses FanMover's parser,
which ignores other fans, and converts to percent. It no longer overwrites
the layer's intended speed, only the fan's actual state.

Raised in Hanif Koh's review of #14394.
2026-10-02 01:10:57 -05:00
harrierpigeon 646fe6384c Belt: retire the two support floor modes nothing implements
clip_only and both were never read and behaved like none; old values now
load as none.

Raised in Hanif Koh's review of #14394.
2026-10-02 01:07:45 -05:00
harrierpigeon f7b822abb7 Belt: remove the unused clip_support_fills()
It had no caller besides its own recursion.

Raised in Hanif Koh's review of #14394.
2026-10-02 01:07:45 -05:00
harrierpigeon c0ba2c8c44 Belt brim: read the belt axis from the config in the instance check
belt_brim_instances_compatible() runs while the slicing parameters can be
stale, like the rest of the brim predicates, which read the print config.

Raised in Hanif Koh's review of #14394.
2026-10-02 01:07:44 -05:00
harrierpigeon 0701f64ba9 Belt: index apron layers into the first layer's nozzle map
Apron bands looked up their filament and nozzle config slot with a running
counter, while object layers use Layer::id(), so band N read the map of
object layer N. They precede layer 0 and now use its assignment.

Raised in Hanif Koh's review of #14394.
2026-10-02 01:07:44 -05:00
harrierpigeon c49e8d32c8 Belt: drop the per-extrusion transform determinant
The mesh transform is a rotation and an axis permutation, so its
determinant is always 1; rebuilding the forward transform on every
extrusion to divide the flow by it changed nothing.

Raised in Hanif Koh's review of #14394.
2026-10-02 01:07:43 -05:00
harrierpigeon 3752144995 Belt: do not refuse a brim because the prime tower setting is on
enable_prime_tower stays on for any multi-filament project, but a belt
printer never prints the classic tower and the belt purge prism is an
ordinary object that never takes a brim, so every brim on a multi-filament
belt print was refused for nothing.

Raised in Hanif Koh's review of #14394.
2026-10-02 01:07:42 -05:00
harrierpigeon 2aa4122aae Belt: check object height against the gantry clearance again
validate() skipped the build-volume height check whenever the machine-frame
transform was active, which is every shipped belt profile, so a 400 mm
object passed on a 300 mm printable_height. The transform only changes how
the height is written to G-code; the clearance check from f682ab5cd3
applies regardless.

Raised in Hanif Koh's review of #14394.
2026-10-02 01:07:42 -05:00
harrierpigeon 97034b22f6 Belt: remove the tagged diagnostic logging
Drops the [BELT-DEBUG], [BELTRACE], [BELT-CALIB] and [BELT-PREVIEW] log
lines, the SLIC3R_BELT_DIAGNOSTIC_LOG blocks, and the counters and
temporaries that existed only to feed them. Six of the purge tower lines
logged at warning level, which is Orca's default, on every plan. Raised in
Hanif Koh's review of #14394.
2026-10-02 01:04:33 -05:00
harrierpigeon 3776739cb5 docs: drop the private build notification note
The build-notify workflow is a local tool of one contributor and does not
belong in the shared agent instructions.
2026-10-02 01:02:58 -05:00
harrierpigeon 93fc4b31e9 Belt: show the axis remap options in Develop mode only
preslice_remap_*, preslice_remap_global and gcode_remap_* describe the
printer's kinematics and are set once by its profile. A wrong value sends
the gantry outside the machine (a user preset with the pre-slice remap in
place of the G-code remap emitted gantry moves to Y=646 mm), so they are no
longer offered in Expert mode.
2026-10-02 01:02:58 -05:00
harrierpigeon 33fc99785a Belt profiles: print without a z-hop by default
On a belt printer a lift is a move along the belt axis (0.4 mm / sin 45 =
0.57 mm of belt travel out and back on every hop), not a lift away from the
part. The three belt printer bases now ship z_hop 0, the IR3 V2 leaf no
longer restates 0.4, and the BabyBelt Pro and IR3 V2 filaments stop
overriding the printer with filament_z_hop 0.4. The option stays editable.
2026-10-02 01:02:57 -05:00
harrierpigeon f219431935 GUI: the plate is open along Y for containment tests on an infinite-Y belt printer
PartPlate's containment tests treat the plate as open along Y on a belt printer with
belt_printer_infinite_y, so a long part is no longer flagged outside the plate in Prepare
while the slicer and the G-code checks accept it. The check reads the printer preset
through the app object, which does not exist headlessly, so it is guarded on the plater.
2026-10-02 01:00:36 -05:00
harrierpigeon b8a8e38ceb G-code: no crossing-perimeter avoidance while travelling on a brim apron layer
Crossing-perimeter avoidance dereferenced the (null) layer while travelling on a brim
apron layer.
2026-10-02 01:00:36 -05:00
harrierpigeon d755580c99 Belt brim: the purge prism never gets a brim, whatever its config says
The prism's generator already sets no_brim; PrintObject::has_belt_brim() now also ignores
any brim setting on the prism (belt_purge_tower_object), so a brim on the parts beside it
never blocks purging.
2026-10-02 01:00:36 -05:00
harrierpigeon e8926efbe3 Belt purge: detect filament changes by scanning the ordering, not the first layer's flag
ToolOrdering::has_wipe_tower() reads the first layer's flag. On a belt the first layer may
be a brim apron band, which carries neither object nor support and never gets the flag, so
with a brim the purge plan returned early and nothing was purged. Scan the layers for a
change.
2026-10-02 01:00:36 -05:00
harrierpigeon 2ae41bb0ef Painting: guard the top/bottom projection erase when no shell layers are requested
With top_shell_layers = 0 the `top` vector is never filled and erasing its begin() was
undefined (found by fuzzing on a painted object dropped below the plate).
2026-10-02 01:00:36 -05:00
harrierpigeon 6a6ceb2612 G-code: key the island tour cache on the island layout, never index past the islands
The per-filament island tour was cached by island centroids only. A later layer with the
same centroids but fewer islands (thin walls, negative volumes) reused the stale visit
list, whose catch-all index pointed past the layer's islands, and extrude_perimeters read
freed memory (three fuzz crashes, planar and belt). The per-instance island layout is part
of the cache key and the use site never indexes past the islands.
2026-10-02 01:00:36 -05:00
harrierpigeon 6be7911e40 CLI: survive a project with missing printable_height or fewer filaments than loaded
Found by fuzzing the headless slicer:

- A BBS-style 3MF without Metadata/project_settings.config segfaulted the CLI silently on
  the missing printable_height option.
- A project saved with fewer filaments (or filament groups) than --load-filaments overran
  the filament variant tables (segfault in the variant match) and then hit an uncaught
  ConfigurationError from set_with_restore_2 (std::terminate). The tables are regenerated
  for the filaments the project did not know about, the destination vectors grown first
  (only from a non-empty source), the match bounded, and a failure becomes a CLI config
  error.
2026-10-02 01:00:36 -05:00
harrierpigeon d57549159d 3MF loader: refuse non-finite vertex coordinates
A 3MF vertex with a nan/inf coordinate was accepted by both parsers and crashed qhull in
ModelVolume's convex hull while the file was still loading. Both vertex handlers refuse it,
and volume generation checks again whichever parser produced the geometry. The main
parser's _stop_object_xml_parser keeps a message a handler already set.
2026-10-02 01:00:36 -05:00
harrierpigeon 4f110bc261 Belt scarf test: slice without a z-hop
The default 0.4 mm z-hop is a 0.57 mm move along the belt axis and its
return tripped the back-step check. Shipped belt profiles print without a
z-hop, so the test does too.
2026-09-30 17:42:16 -05:00
harrierpigeon 69a08915d1 Belt: do not fail the G-code height check against belt travel
check_multi_extruder_gcode_valid() compares each object's max Z with
printable_height. On a belt printer machine Z is belt travel (a 3DBenchy
on the BabyBelt Pro runs from Z=197 to Z=309 on a 69 mm printable_height),
so every belt export set the over-height error bit and the CLI refused the
plate with -102 "G-code in unprintable area". The preview already skips its
ToolHeightOutside warning for the same reason; the export check now does
too. The XY printable-area check is unchanged.
2026-09-30 17:29:33 -05:00
harrierpigeon 2c0570c97d Drop references to planning docs that are not in the tree
The MachineKinematics comments pointed at docs/superpowers plan files,
which are gitignored working notes.
2026-09-30 15:44:49 -05:00
harrierpigeon 48af5b5d97 Read the pre-slice remap header tags at their real length
The header tags lost their belt_ prefix in the Part 3.2 rename (20
characters now), but the parser still skipped 25, so every axis read as
pos_x. Found in Hanif Koh's review of #14394.
2026-09-30 15:44:18 -05:00
harrierpigeon 6cf747808c Belt: never start a scarf joint seam below the layer
A scarf joint begins one layer height below the current layer and ramps
up along the wall. On a tilted belt that start is a step backwards along
the belt axis, into the previous layer's wall at the seam: 0.283 mm per
0.2 mm layer at 45 degrees. With an aligned seam the nozzle rams the same
spot on every layer. A BabyBelt Pro benchy with seam_slope_type=external
showed 601 such back-steps from layer 107 on, and in the field the belt
"jumped backwards" and the head knocked the part loose.

Belt printers now skip the scarf in GCode::extrude_loop, and the process
tab greys the scarf controls out for them, as it already does for arc
fitting. The regression test slices a cube on a belt with the scarf
enabled and checks the belt axis never steps back by a layer pitch.
2026-09-30 15:44:18 -05:00
harrierpigeon ad6afce67b Merge upstream/hanif/belt-printer-fixes into belt/final-round
Brings in upstream/belt-printer (the Sept 14 main merge) plus Hanif Koh's
21 review-fix commits from PR #15685, on top of the MachineKinematics
refactor and the purge-prism / tree-support / first-layer-speed fixes.

Conflict resolution:
- BeltGCodeWriter is gone (kinematics refactor), so Hanif's plate-offset
  fix for it is ported into GCodeWriter: the first-layer-plane checks in
  travel_to_xy / travel_to_xyz / _travel_to_z now evaluate the plate-local
  point, and BeltGCode::init_belt_writer hands the stored plate origin to
  the writer it installs.
- init_belt_writer(Print&) takes Hanif's signature; the BBL flag is set on
  the surviving writer by GCode::_do_export.
- The shared emit_belt_brim_bands() loop keeps the BeltFloorObjectGuard the
  local branch added, so apron bands classify first-layer height against
  their own object.
- eager_lift keeps effective_type: it now carries set_force_normal_lift().
- GCodeWriter's initializer list follows Hanif's member order with
  m_kinematics in its declared position.
- TreeSupport::detect_overhangs uses Hanif's clamped build_plate_tilt_slope()
  for the non-belt path and the belt shear for the belt path.
2026-09-30 15:22:19 -05:00
Hanif Koh 2b2c710626 Order GCodeWriter Initializers Like the Members Moved to Protected
The lift, speed and cached-extruder members now live in the protected section ahead of the private ones; list their initializers first so the list reads in construction order. No behaviour change.
2026-09-14 17:27:33 +08:00
Hanif Koh 2b1a7e38df Drop Redundant Belt Checks in BeltGCode
BeltGCode is only created for belt printers, so its hooks no longer re-check belt_printer, and the BBL-machine flag is set once on whichever writer survives init_belt_writer instead of on one about to be discarded.
2026-09-14 17:27:33 +08:00
Hanif Koh b4052ec99f Drop the Redundant Lift Type Alias in eager_lift
effective_type was a plain copy of the parameter.
2026-09-14 17:27:33 +08:00
Hanif Koh 14cf7861e0 Fix Belt Tooltip Spacing and Legend Casing
Drop double spaces in the belt tilt tooltips and match the preview legend header to
the "Belt printer" settings group.
2026-09-14 17:27:33 +08:00
Hanif Koh 18c08862a8 Align up_direction Position in TriangleSelector Fill Calls
seed_fill_select_triangles() now takes up_direction right after highlight_by_angle_deg, as select_patch() does.
2026-09-14 17:27:33 +08:00
Hanif Koh 62ef0fa4f8 Write the Standard Layer Change Tag on Belt Brim Apron Layers
Apron layers appended print_z to the layer change tag, unlike every other layer; write the plain tag line.
2026-09-14 17:27:33 +08:00
Hanif Koh a8a45439db Share One Build Plate Tilt Up-Direction Helper Across the GUI
The bed gravity arrow, volume rendering and the painter/support gizmos each rebuilt
the tilt up-vector from build_plate_tilt_x/y; use one helper that also tolerates
presets without the keys.
2026-09-14 17:27:33 +08:00
Hanif Koh 5cd6661280 Check the Belt Temperature Tower Model Load
Bail out like the other calibration paths when add_model() fails instead of
indexing the empty model.
2026-09-14 17:27:33 +08:00
Hanif Koh 33aec258da Skip CLI Wipe Tower Reservation on Belt Printers
Print::has_wipe_tower() is always false for belt printers, but CLI arrange, plate checks and the pre-slice tower clamp still reserved a phantom tower footprint and wrote a clamped wipe_tower_x/y into the config.
2026-09-14 17:27:33 +08:00
Hanif Koh 76aba24ddf Number Belt Extension Support Layers Sequentially
Extension layers were all created with id 0, so every one of them could be taken for the first layer by id-only checks (ooze-prevention standby temperature, cached layer ids). Renumber the support layers after inserting them.
2026-09-14 17:27:33 +08:00
Hanif Koh cb5b489e90 Invalidate Only G-code Export for Belt Output Options
gcode_back_transform, first_layer_plane* and belt_printer_infinite_y fell through to invalidate_all_steps(), which re-ran tool ordering, skirt/brim and G-code export on toggles that only affect G-code export.
2026-09-14 17:27:33 +08:00
Hanif Koh 19a085206c Print Belt Brim Aprons in Each Object's Brim Filament
Apron-only layers printed every band with the first tool, so objects with different brim filaments at the same apron Z shared one filament. Emit each brim filament's bands with its own toolchange.
2026-09-14 17:27:33 +08:00
Hanif Koh 708212a306 Share One Belt Brim Band Loop Between Apron-Only and Ordinary Layers
The ordinary-layer path kept its own copy of the apron band loop. Give emit_belt_brim_bands() an optional brim filament filter and call it from the per-extruder lambda; without a filter it still prints every band, so apron-only layers are unchanged.
2026-09-14 17:27:33 +08:00
Hanif Koh 3b302b4666 Derive Belt Support Tilt From Slicing Rotation in Print::apply
build_plate_tilt_x/y was synced from belt_slice_rotation* only by the printer Tab, so CLI or 3MF edits of the rotation left the support tilt stale.
2026-09-14 17:27:32 +08:00
Hanif Koh 4a7311bf01 Rebuild the Brim Type Combobox Only When Its Entries Change
toggle_options() now runs on every value change and mode switch; rebuild the
brim_type choices only when the leading-edge entry has to be added or removed.
2026-09-14 17:27:32 +08:00
Hanif Koh f2a11928f6 Read the Belt Tilt Only from the Belt G-code Header
Every printer's config block lists belt_slice_rotation_angle (default 45), so the processor marked all G-code as belt G-code: imported flat G-code got the belt view on a belt printer, and the belt-only Z handling in the processor ran for non-belt prints whose config block precedes the body. Take the angle only from outside the config block, where only the belt header writes it.
2026-09-14 17:27:32 +08:00
Hanif Koh 4ed56954e8 Limit Build Plate Tilt Range Below 90 Degrees
A 90 degree tilt has no finite gravity drift per layer, so the option range
now stops at 89 degrees, matching the cap applied by the support generators.
2026-09-14 17:27:32 +08:00
Hanif Koh 6e33f3f5dd Share and Clamp the Build Plate Tilt Shift in Support Generators
The three support generators each computed lh * tan(tilt), which overflows
coord_t at 90 degrees and flips sign beyond it (belt sync can write up to
180). One helper now returns the tilt slope with the tilt capped at 89 degrees.
2026-09-14 17:27:32 +08:00
Hanif Koh 5092831007 Apply the Belt Slicing Transform to Painted Supports and Seams
Painted support/seam facets, support volumes, seam occlusion, MMU and fuzzy skin painting (top/bottom
and side facets) and the adaptive infill octree used trafo_centered(), or trafo() with a centre-offset
shift, while the layers were sliced with the belt rotation, remap and Z lift; they now share
PrintObject::trafo_sliced().
2026-09-14 17:27:32 +08:00
Hanif Koh 9cca3093ee Keep the Plate Offset When Swapping in the Belt Writer
The belt writer replaced the plate-offset-carrying writer mid-export, so belt G-code for any plate but the first kept the plate origin and long-travel clipping used the wrong frame. GCode now remembers the offset and hands it to the new writer, and the writer's first-layer probes use the plate-local point it emits.
2026-09-14 16:41:40 +08:00
Hanif Koh 6873267a9c Remove Plate Tilt Keys from Per-Object Settings Tables
build_plate_tilt_x/y are printer-preset keys; listing them in the per-object
frequent-settings and object-table bundles stored ignored values in object
configs and crashed the object table on the process config lookup.
2026-09-14 16:41:40 +08:00
Hanif Koh 8e330f951a Merge Main into Belt Printer
Merge origin/main (00429da739) into belt-printer.

Conflicts resolved:
- src/CMakeLists.txt: keep both wxInspector workarounds.
- GCodeProcessor.cpp: keep the belt compare_pos / z_for_height lines.
- PrintObjectSlice.cpp: the belt bbox-Z guard also covers main's
  printable_region_ids bookkeeping.
- TreeSupport.cpp: the belt-floor check runs before main's PendingNode
  queueing.
- Tab.hpp: keep the belt fields, drop the removed upload description
  fields.
- tests/libslic3r/CMakeLists.txt: keep both test files.

Also included:
- eSUN PLA belt presets declare their own filament_id (OFkrxQC4) and
  scripts/filament_id_snapshot.json is regenerated, as main's filament_id
  check requires.
- Custom.json version bumped to 02.04.00.05 so the belt entries reach
  existing installs.
- Fix the ambiguous WithinRel call in the belt apron width test, which
  otherwise breaks the fff_print build.
2026-09-14 16:33:08 +08:00
harrierpigeonandClaude Fable 5.1 9c83631d20 TreeSupport: drop the <cstdio> include left over from removed debug output
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SsuY8Laiyh7q2zPVVKV3HZ
2026-09-12 22:23:14 -05:00
harrierpigeonandClaude Fable 5.1 a430430690 Belt: size the purge prism against physical filaments, not mixed slots
A mixed filament slot is virtual: ToolOrdering::resolve_mixed_filaments()
replaces it with its physical components before any G-code is emitted, so
the toolchanges the prism has to absorb are between those components.
ensure_belt_purge_tower() counted the slot as a filament of its own,
provisioning one island per mixed slot that no swap can ever reach -- the
"extra purge tower" on MCTEST5, where filament 5 is a 50/50 blend of 2
and 4 and the G-code reports 0.00 g of it used.

Expand the assigned set with the same expand_mixed_filaments() the
backend uses, so the GUI sizes the prism against the filament set the
slicer actually produces. No-op when nothing is mixed. Test covers the
MCTEST5 shape, a mixed slot whose components are otherwise unused, and
the no-mixing case.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SsuY8Laiyh7q2zPVVKV3HZ
2026-09-12 22:23:14 -05:00
harrierpigeonandClaude Fable 5.1 1bcfe58064 Belt: stop the purge prism printing plastic no toolchange needs
Two independent leaks of filament on the belt purge prism, plus the
replan safety net the second one needs.

1. The early-truncation scan bounded itself with the prism's own
   toolchanges. ToolOrdering covers the whole print and the prism is a
   printed object in it, so the "last toolchange" the scan found was on
   the prism's own top layers -- it runs past every model object by
   design -- and the truncation cancelled nothing. Bound the scan at the
   tallest non-prism object (support layers included; on a belt they can
   top the object). On MCTEST5 that was 197 toolchanges over 39.4 mm of
   tower that no swap ever needed.

2. On a layer with no toolchange, the prism's entire fill printed as
   solid infill in its own filament. Drop the fills no toolchange
   claimed, right after the purge marking and before
   ensure_perimeters_infills_order() force-overrides whatever is left.
   Perimeters stay so the bar keeps a continuous wall. An earlier version
   of this deleted the entities and had to be reverted: psWipeTower can
   rerun without regenerating infill, and a later tool ordering may claim
   what this one did not. The entities are now stashed with their layer,
   region and index and put back exactly, the same reversibility contract
   layer truncation already had.

3. Both stashes go stale if an object step reruns: make_fills() clears
   and regenerates fills over m_layers only, so a stale stash would put
   old fills back next to new ones, and truncated layers would keep old
   perimeters/fills. Undo the plan's edits at the top of Print::process()
   whenever psWipeTower is not done. Every object-step invalidation also
   invalidates psWipeTower, so that condition is exactly "some object
   step may rerun"; when it is done nothing regenerates and the edits
   must stay. This also covers a prism left behind after belt mode is
   turned off, which previously stayed truncated forever.

WipingExtrusions::is_entity_overridden() becomes public so the prism can
tell claimed fills from unclaimed ones.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SsuY8Laiyh7q2zPVVKV3HZ
2026-09-12 22:23:14 -05:00
harrierpigeonandClaude Opus 5 1b6fb2a81f Belt: fix first-layer speed and the slow_down_layers ramp never applying
Fixes the report in #12998 (comment 5465250754): first-layer speed and the
slow_down_layers ramp were ignored on a belt printer. The report reads as a
per-object problem, but neither applied to *any* object -- the reporter's first
part slowed down because slow_down_for_layer_cooling was on, which is
CoolingBuffer's time-per-layer mechanism, not initial_layer_speed.

FirstLayerPlane decides first-layer-ness by perpendicular distance to a plane it
derives by composing gcode_remap_* with compute_machine_z_affine(). The plane is
therefore a function of how G-code is *addressed*, not of where the belt is:
change the output axis convention and the plane moves. On MCBELT-TYPE2 the
first layer measured 86.2 mm from the plane and got effective index 431, far
past any slow_down_layers ramp.

on_first_layer(point) and effective_layer_index_for_point() now measure height
above the belt surface, using the belt description already carried in
SlicingParameters -- belt_floor_shear_factor / belt_floor_from_axis /
belt_floor_z_shift -- the same description the support generator uses. That is a
property of how the object was sliced, so no remap or back-transform can perturb
it.

Deliberately not via BeltFloorContext: its init() folds in
belt_support_floor_offset, a support-generator diagnostic, and letting that
option steer the model's first-layer speed band would be a surprising coupling
(a negative value would switch the slowdown off outright).

Preserving the existing first-layer-plane settings:

  * first_layer_plane XY/YZ/XZ keeps the FirstLayerPlane evaluator, as those are
    explicit opt-outs.
  * A non-zero first_layer_plane_offset also keeps it. The offset is a machine-Z
    shift that FirstLayerPlane converts into a perpendicular distance in the
    slicing frame; this evaluator measures along slicing Z, so there is no
    faithful translation. Deferring to the evaluator that implements the setting
    beats silently ignoring it.
  * The two thresholds stay separate, exactly as FirstLayerPlane keeps them:
    the first-layer boolean tests initial_layer_print_height, while the
    effective layer index counts bands of first_layer_plane_thickness.

Brim and coincident apron bands are emitted before m_layer is switched to their
object -- for an apron band there is no Layer at all -- so both paths publish the
belt-floor owner explicitly. Without that a brim's classification would borrow
whichever object was visited previously, making it depend on plate order.

Note that first-layer-ness drives more than speed: extrusion acceleration, jerk,
the first-layer flow ratio and eligibility for overhang speed/fan analysis all
read it, so all of them are corrected on belt printers by this change.
Classification still samples only each path's first point, as it did before.

Non-belt is unaffected by construction: belt_height_above_floor() returns false
when the belt floor is inactive and both call sites fall back to the previous
path. FirstLayerPlane stays in place for its other modes and for CoolingBuffer,
whose machine-coordinate probe is a separate outstanding bug.

Measured, MCTEST4 on MCBELT-TYPE2 (initial_layer_speed=5, slow_down_layers=40):
15 distinct feedrates with no gradient and F300 absent, becomes 70 including the
full ramp 300(5) 382(6) 465(8) 630(10) 795(13) ... Two bare cubes on a belt:
0 slow extrusions becomes 2378 across Z 32.36..95.18. The same two cubes on a
Cartesian printer keep their slow extrusions confined to Z 0.20..2.00.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011jgzj1sf53KMLPweZ8yeUQ
2026-09-08 23:59:02 -05:00
harrierpigeonandClaude Opus 5 767db71500 Belt: fix three tree-support bugs, one of which blocked slicing entirely
1. Belt tree support could not slice at all.

layer_initialize() hardcodes layer 0's bottom_z to 0, encoding "below layer 0 is
the build plate at z = 0". True for a flat bed; false for a belt, whose virtual
support layers legitimately extend below zero. The bottom-most belt layer
therefore got height = print_z - 0 = -9.8, which reached Flow::with_height() and
threw FlowErrorNegativeFlow.

A 3DBenchy, a mushroom, an L-bracket and an extruded L all failed identically
with negative flow / return -100. Only a bare cube sliced, because its support
never reached that far down.

The bottom is now taken from the previous layer's z, and only a layer 0 whose
print_z is itself negative gets a synthesised bottom below it. Every
non-negative print_z -- every non-belt configuration -- keeps exactly the
previous 0, so this is behaviour-preserving off a belt by construction. An
earlier form used min(0., layer_z(0) - layer_height), which regressed flat beds
whenever the initial layer was thinner than the layer height.

  3DBenchy on a 45-degree belt with organic tree support: fails to slice ->
  247 support blocks / 168,596 extrusions.

2. Support generated against the belt, and against belt-tilted walls.

A plain 20mm cube on a 45-degree belt generated 86 support blocks and 46,307
support extrusions. Three causes, all gated on the belt floor being active:

  a. The build-plate tilt compensation shifted the lower layer the wrong way.
     tan(build_plate_tilt_*) carries a magnitude but no direction, and the sign
     chosen moved the lower layer away from the newly appearing material rather
     than under it, doubling the mismatch. The shift now comes from
     belt_floor_shear_factor / belt_floor_from_axis, which carry sign and axis
     exactly. Non-belt tilted beds keep the previous behaviour.
  b. Material resting on the belt was treated as unsupported. The belt surface
     is now unioned into the effective lower layer, sampled at the bottom of the
     layer -- a layer meets the belt across its thickness and print_z is the
     top. The half-plane is clipped to the layer's bounding box first: unioning
     a +/-1000mm half-plane raw with 20mm-scale geometry put a huge dynamic
     range through Clipper and left intermittent artefacts every few layers.
  c. The object's first slice can be empty on a belt (the bottom vertex is a
     sub-extrudable sliver), leaving the layer above with an empty predecessor
     even though it rests on the belt. (b) already covers that per island. What
     did need fixing is sharp-tail detection, which tests each island against
     the raw lower slices; with an empty predecessor that test is trivially true
     and every belt-contact island read as a sharp tail. It now tests against
     the same effective lower layer.

     An earlier form instead skipped the whole layer when the point of
     get_extents(curr_polys) -- the bounding box of the union of every island --
     nearest the belt was in contact. That was wrong in a way worth recording:
     one island resting on the belt could suppress overhang and sharp-tail
     detection for a separate island floating well above it. Every decision here
     is per-island.

  Cube on belt: 46,307 -> 0 support extrusions. Same cube non-belt: 0 before and
  after. Benchy on belt still 247 blocks / 168,596 extrusions and a mushroom
  111 / 82,157, so false positives are removed without suppressing true ones.

  Non-belt is unchanged by measurement, not only by the belt_ovh_active gate:
  the same mushroom sliced on a Cartesian printer before and after gives 65,866
  support extrusions and 68,717 total extrusions both times, the two G-code
  files differing in exactly one line -- the object's plate position.

3. m_anti_overhang was filled and read in different index spaces.

It is consumed in the same index space as m_layer_outlines, where object layer i
lives at num_raft_layers + i, but was filled in object-layer space. Every entry
landed num_raft_layers too low (50 for a 20mm cube at bed Y=50) and the topmost
object layers got none. The belt injection also ran before m_raft_layers was
extended, so it could not have known the offset.

The array is now shifted as a whole and the injection moved after the raft
extension. This also repairs user support blockers under a raft, which is not
belt-specific: it changes behaviour for any ordinary raft, not just the belt's
virtual one, and should be reviewed as a general fix. Measured effect on the
cube was small on its own (46,307 -> 46,334 before the other fixes) because
m_anti_overhang only feeds calculate_placable; kept as a correctness fix on its
own merits.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011jgzj1sf53KMLPweZ8yeUQ
2026-09-08 23:58:47 -05:00
harrierpigeonandClaude Opus 5 4d2c3a0af4 GCodeWriter: fix two machine-mapping bugs the extraction preserved
Both change emitted G-code, which is why they were kept out of the extraction
commit. Both are wrong only where the machine mapping is non-identity, which is
the definition of each bug.

1. Suppress lifts commanded through an unknown position.

_travel_to_z() emits full XYZ whenever the mapping must emit every axis, because
the mapping can make machine Z depend on logical X/Y, and it builds that point
from m_pos. At print start, and after any custom G-code that invalidates
position, m_pos.xy is the uninitialised origin; mapping (0, 0, z) through a
non-identity remap produces a real but wrong machine point -- for a reverse
mapping, build_vol_max, i.e. the far corner of the bed. The subsequent full-XYZ
move corrects the position, but the lift has already commanded a rapid across
the whole bed at travel speed.

Belt kinematics already guarded this; the Cartesian path did not. The guard is
now applied at all three lift sites through must_skip_lift_now(), not just the
one the extraction covered: travel_to_xyz()'s pending-lift branch,
lazy_lift(spiral_vase=true), and eager_lift(). The latter two also needed the
state fix -- both recorded m_lifted = target_lift regardless, so suppressing
only the emission would leave a later unlift() descending from a height that was
never commanded.

2. Never emit a G2/G3 arc a mapping cannot represent.

extrude_arc_to_xy() emitted G2/G3 with logical X/Y and I/J and never consulted
the mapping. There is no general fix by transforming the arc: a permutation
moves it out of the XY plane that I/J describes, a negation reverses handedness,
and the belt shear maps a circle to an ellipse that G2/G3 cannot express at all.

So supports_arc_moves() gates generation through the existing
GCode::should_disable_arc_fitting() hook, and BeltGCode's special-case override
is deleted -- belt now gets the same behaviour from the general rule instead of
its own exception.

supports_arc_moves() is m_remap_x == 0 && m_remap_y == 1, not !has_axis_remap():
an arc emits only X/Y/I/J, so a mapping that merely negates or reverses Z leaves
every emitted word untouched and keeps its arcs.

The fallback for an unrepresentable arc tessellates it into linear segments at a
0.005mm chord tolerance rather than substituting a single chord, and splits dE
proportionally across the segments. The capability check is hoisted above every
extrusion mutation: an earlier form ran it after filament()->extrude(dE) and so
extruded 2*dE on the fallback path.

Known limits of that fallback, since it is worth stating rather than discovering:
emitted relative E is conserved only to per-segment rounding (a radius-5
semicircle with dE=1.5 emits 1.50012 across 36 segments); the 0.005mm bound is a
logical-frame bound, about 0.00855mm in machine space under a 45-degree belt
shear; unequal endpoint radii and non-finite inputs are unchecked. Ordinary
export takes the original polyline when the mapping rejects arcs, so this path
is a fallback rather than the normal route.

Known gap, not claimed fixed: classic wipe towers have their own
enable_arc_fitting and their own G2/G3 emitter in GCode/WipeTower.cpp, which
should_disable_arc_fitting() does not govern. Belt printers are barred from
classic wipe towers; a remapped Cartesian printer is not.

Tests in tests/fff_print/test_gcodewriter.cpp: reverse-X remap with unknown and
with known position plus an identity control; eager_lift emitting nothing and
recording nothing; the arc-capability matrix including the Z-only cases; and the
tessellated fallback. E accounting is asserted through used_filament() rather
than E(), which resets per line in relative-E mode.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011jgzj1sf53KMLPweZ8yeUQ
2026-09-08 23:58:19 -05:00
harrierpigeonandClaude Opus 5 e695da66df GCodeWriter: extract MachineKinematics, delete BeltGCodeWriter
BeltGCodeWriter subclassed GCodeWriter and overrode seven methods, five of them
by copying the base body and changing the transform. The base writer already
carried an axis remap and already branched at each of its seven
coordinate-emission decisions; the subclass did the same branching with a
different transform, and the two copies had begun to drift.

Replace the inheritance with a strategy object owned by GCodeWriter:

  CartesianKinematics  to_machine = the existing apply_axis_remap; today's base
                       behaviour, moved rather than changed.
  BeltKinematics       to_machine = MachineFrameTransform o axis_remap o
                       BeltBackTransform, plus a world_coordinates variant for
                       the PA calibration generators.

New: src/libslic3r/GCode/MachineKinematics.{hpp,cpp}, GCode/BeltKinematics.{hpp,cpp}
Deleted: src/libslic3r/BeltGCodeWriter.{hpp,cpp} (341 lines)

Points worth a reviewer's attention:

  * The predicate is must_emit_all_axes(), not couples_axes(). The base returns
    true for any non-identity remap, including pure permutations that do not
    physically couple axes, so the question is "must every axis word be
    emitted", not a statement about kinematics.
  * Every per-site word-omission branch is preserved. The base deliberately
    emits X/Y only, or Z only, or drops Z when its quantised value is unchanged.
    The strategy changes which transform applies, never whether words are
    omitted.
  * set_kinematics() replays the configured remap and build volume onto a newly
    installed strategy, because BeltGCode::init_belt_writer runs before
    GCode.cpp calls set_axis_remap/set_build_volume_max.
  * uses_pointwise_travel_speed() preserves a pre-existing divergence rather
    than introducing one: the base travel_to_xyz emits the raw configured travel
    speed in its final branch, ignoring the first-layer value computed at the
    top, whereas the belt path used the first-layer-aware value throughout. Both
    are kept. Unifying them changes feedrates and belongs in its own change.
  * The [BELT-DEBUG] block is deleted; it rate-limited itself with a
    function-local static thread_local in the hot emission path, and this is the
    commit that would otherwise have moved it into shared code.

This commit is intended to preserve existing export output. That is reviewed by
construction -- each emission site keeps its own omission branch and each policy
divergence is preserved -- and is NOT verified against a G-code diff corpus.
Building that corpus is the outstanding work here.

Two API-equivalence exceptions, neither reachable by any caller today:

  * Belt kinematics with no plane pointer installed, m_is_first_layer true,
    initial and normal travel speeds differing, travel_to_xyz() reaching its
    final branch: the old belt writer selected the initial-layer speed, the new
    writer selects the normal travel speed. The pending-lift and XY-only
    branches keep their previous selection.
  * Belt kinematics installed without set_force_normal_lift(true) and a
    non-normal lift requested: the old belt writer forced a normal lift, the new
    writer can take the slope branch.

The PA-pattern generator reaches the writer through explicit travel_to_z() /
travel_to_xy(), not travel_to_xyz() or the lazy/eager lift paths, and normal
belt export installs both the plane and the forced-normal-lift policy, so
neither exception changes output produced today. They are recorded because a
future caller could reach them.

tests/fff_print/test_gcodewriter.cpp was also not compiling before this branch:
it called writer.to_machine_coords(), a method that existed only on
BeltGCodeWriter. It never surfaced because the build targets OrcaSlicer, not
all, and BUILD_TESTS defaults to OFF, so that translation unit was outside every
compile path. Fixed here; the existing 30-degree coordinate assertions are kept
verbatim as the best available regression net.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011jgzj1sf53KMLPweZ8yeUQ
2026-09-08 23:57:55 -05:00
Joseph Robertson c96945490b Belt Printer Sept 1 Rebase (#15526)
Also a bunch of bug fixes, thanks to the Baby Belt community for finding
issues!
2026-09-03 13:35:23 -05:00
harrierpigeon e5d4ad2aa7 Merge remote-tracking branch 'upstream/main' into haryr/aug25-rebase
# Conflicts:
#	src/libslic3r/Support/TreeSupport.cpp
2026-08-30 23:31:48 -05:00
harrierpigeonandClaude Opus 5 4fab8d0b39 fix: adapt belt sub-layer group emission to upstream m_writer unique_ptr
Upstream changed GCode::m_writer from a value to std::unique_ptr<GCodeWriter>;
the belt mixed_sub_layer_groups path still used value syntax and did not compile.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ChxXYc6Dp46qAN9c2rQCe
2026-08-30 23:31:21 -05:00
harrierpigeon e341a9b84e Merge remote-tracking branch 'upstream/main' into haryr/aug25-rebase
# Conflicts:
#	src/libslic3r/GCode/ToolOrdering.cpp
#	src/libslic3r/Print.cpp
#	src/libslic3r/PrintApply.cpp
#	src/libslic3r/PrintConfig.cpp
#	src/slic3r/GUI/Tab.cpp
2026-08-30 23:30:51 -05:00
harrierpigeon a7bc054974 docs: authorize private build notifications 2026-08-25 10:27:21 -05:00
harrierpigeon 30351d40e1 tests: adapt belt brim coverage to upstream validation 2026-08-25 10:27:08 -05:00
harrierpigeon d289478618 Merge remote-tracking branch 'upstream/main' into haryr/aug25-rebase
# Conflicts:
#	resources/profiles/Custom.json
#	src/libslic3r/Brim.cpp
#	src/libslic3r/GCode.cpp
#	src/libslic3r/GCode.hpp
#	src/libslic3r/Preset.cpp
#	src/slic3r/GUI/3DScene.cpp
#	src/slic3r/GUI/ConfigManipulation.cpp
#	src/slic3r/GUI/GLCanvas3D.cpp
#	src/slic3r/GUI/Plater.cpp
2026-08-25 06:50:46 -05:00
Joseph Robertson 306e379a2a Multicolor Belt Support & various bug fixes (#15361)
# Description
lots of small bugfixes, and multicolor belt support.
[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
2026-08-24 23:23:47 -05:00
harrierpigeon 3d11b60e71 Harden belt purge tower replanning 2026-08-08 20:14:52 -05:00
Joseph Robertson e8597fe942 Merge pull request #61 from HarrierPigeon/belt/purgeTower
Add Purge Tower and Finalize Multicolor Support
2026-08-08 18:59:00 -05:00
harrierpigeon 99ee8893cd Fix belt purge tower activation and placement safety 2026-08-08 17:14:26 -05:00
harrierpigeon ee3e014f02 allow belt purge to skip unnecessary purge volume 2026-08-08 16:35:55 -05:00
harrierpigeon 3d270c2aa7 workable belt purge, via N-1 individual "purge objects" 2026-08-08 16:35:55 -05:00
harrierpigeon 5ea6ccc56a cleanup, early purge tower stop if no longer necessary 2026-08-08 16:35:55 -05:00
harrierpigeon bcfb09481c pull purge tower into its own files, make purge tower semi-transparent like other purge towers 2026-08-08 16:35:55 -05:00
harrierpigeon c80f1ab312 cancel top of purge tower early if no extra parts to print 2026-08-08 16:35:55 -05:00
harrierpigeon 131b61b726 auto purge tower height calculation works 2026-08-08 16:35:55 -05:00
harrierpigeon d367bcef92 extra height compensation 2026-08-08 16:35:55 -05:00
harrierpigeon 79c93733d3 purge tower additional compensation 2026-08-08 16:35:55 -05:00
harrierpigeon 7cc50d750c automated placement works 2026-08-08 16:35:55 -05:00
harrierpigeon 62d8f22f52 purge tower still centered on X max 2026-08-08 16:35:55 -05:00
harrierpigeon 60e9ee26c9 strategy incremental 2 2026-08-08 16:35:55 -05:00
harrierpigeon 854dae8dd2 strategy incremental 2026-08-08 16:35:54 -05:00
harrierpigeon ec4e9717d3 Part Two: Functional Results 2026-08-08 16:35:54 -05:00
harrierpigeon 88726d76e8 Purge tower part 1 2026-08-08 16:35:54 -05:00
Joseph Robertson 39b087d6ea fix non 45 degree slicing methods (#15181)
# Description
During the UI/UX improvements about a month ago, I got the transforms
wrong, and slicing at anything other than a 45 degree angle was
affected.

Validated on a baby belt pro at 30 & 45 degrees.


[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
2026-08-08 12:37:17 -05:00
harrierpigeon 8b2e28817d fix non 45 degree slicing methods after regression created while cleaning up UI 2026-08-08 12:33:41 -05:00
Joseph Robertson ac9b5433b7 Belt printer: regression fixes + Belt Printer Brims (#15155) fixes (#15156)
## Summary

Fixes for the `belt-printer` branch ahead of upstreaming, in two groups
(10 commits). Targets `belt-printer` (not `main`) since group 2 fixes
the not-yet-merged Belt Printer Brims feature.

Every fix keeps non-belt (and brim-disabled) output unchanged; belt-only
behavior is corrected. All changed translation units and the two test
files were type-checked (`-fsyntax-only`); a full build + `ctest` still
needs to run in an environment with current deps.

## Group 1 — pre-existing belt-printer regressions

- **[HIGH] BuildVolume belt state not reset when leaving belt mode** —
toggling belt off (or switching belt→normal with matching bed geometry)
left the `BuildVolume` with `m_is_belt_printer=true` and inflated Y
bounds, so out-of-bounds objects were treated as printable on a normal
printer.
- **[HIGH] `GCodeProcessorResult::reset()` didn't clear belt fields**
(`belt_tilt_angle`, `belt_z_origin`, `preslice_remap_*`) — a reused
result corrupted a normal print's start-gcode preview Z.
- **[LOW-MED] `TreeSupport::drop_nodes`** — restored the single critical
section around node invalidation (the two `valid=false` writes had been
moved outside the mutex on the shared tree-support path); removed an
unused local.
- **[LOW] Support overhang hot paths** — avoid unconditional lower-layer
polygon copies when there is no build-plate tilt (`SupportMaterial`,
`TreeSupport3D`); untilted output matches upstream exactly.
- **[LOW] Render loop** — hoisted the frame-invariant slope
`up_direction`/`normal_z` (and their per-volume config lookup) out of
the per-volume loop.
- **[LOW] FDM-support "select by angle"** — restored the exact upstream
threshold when the build plate is untilted (the generalized form
differed for non-uniformly-scaled objects); tilted-gravity form kept
only under tilt.
- **[LOW] Printer tab tilt sync** — only clears the belt-derived
`build_plate_tilt` on a genuine in-place belt→off toggle (tracked,
seeded on preset load), no longer wiping a manually-set tilt.
- **[LOW / opt-in] Axis-remap G-code emission** — always emit full XYZ
under an active `gcode_remap_*`, apply the remap on all base
`travel_to_xyz` destinations, fall back to a linear lift for spiral/arc
under remap, sync `set_axis_remap` each export; fixed belt first-layer
travel speed. Identity/default output unchanged.

## Group 2 — Belt Printer Brims (#15155) fixes

- **[CRITICAL] Dropped brim at first belt contact** — a coincident brim
band on an object layer with no extrusion pass (zero-extrusion leading
slice, or belt support below the Z=0 floor with no coinciding object
extrusion) was never emitted. Now each coincident band's brim filament
is registered in `ToolOrdering`, each band is emitted exactly once in
its brim-filament pass, and an end-of-layer orphan sweep emits any band
whose object layer produced no visit.
- **[Multi-extruder] Wrong tool / double emission** — apron and
coincident bands now print once, in the correct brim-filament pass,
brim-first (were previously emitted with the active tool and could
double-emit per filament plan). Single-extruder / single-object output
is byte-identical apart from the previously-dropped bands now printing.
- **Inner-only predicate** — `has_belt_brim()` no longer reports a brim
(and no longer rejects the prime tower / spiral vase) for `inner_only` +
`brim_width=0` + leading/extra > 0, which produces no inner geometry;
mirrored in `wants_brim`.
- **ToolOrdering raft-gap comment** — clarified why raft-gap synthesis
is suppressed for all belt printers (belt has no rafts;
sub-object-bottom layers are apron / belt-support-below-floor /
lead-in). No behavior change.
- **Tests** — deterministic coverage: brim present at first belt contact
(support on/off), brim-before-perimeters once (no drop/double), single-
and multi-extruder tool selection with no doubling, multi-object
per-filament ordering, inner-only+leading-only not rejecting prime
tower/spiral, and inner-ring / leading-edge-only geometry units.

## Testing

- `-fsyntax-only` passes for all 16 changed source TUs + 2 test TUs
against this branch.
- Please run the full build and `ctest -R 'SkirtBrim|BeltBrim'` before
merging.

## Known follow-up (out of scope)

`extrude_arc_to_xy` does not remap its I-J center, so arc-fitted
*extrusions* under standalone axis-remap would be geometrically wrong —
a separate fix if that combination is supported.

Opened as **draft**.
2026-08-06 17:38:11 -05:00
harrierpigeon a453cb1eba tests: cover belt-brim first-contact emission, tool selection, inner/leading-edge, and predicate (E)
Deterministic tests for: coincident brim at first belt contact not dropped
(C), single- and multi-extruder brim tool selection with no doubling (B),
multi-object apron ordering, inner-only+leading-only not rejecting prime
tower/spiral (D), and inner/holed + leading-edge-only geometry.
2026-08-06 15:40:04 -05:00
harrierpigeon 1bd3404c03 Fix belt brim emission: dropped first-contact bands, tool selection, inner-only predicate (A,B,C,D)
- Emit coincident belt_brim_by_layer bands even when the leading object layer
  has no InstanceVisit (zero-extrusion lead-in / no coinciding support), so the
  brim at first belt contact is no longer dropped.
- Register each coincident band's brim filament in ToolOrdering and emit each
  band exactly once, in its brim-filament pass; emit ordinary-layer aprons in
  the brim pass before object extrusion (correct tool, brim-first) instead of
  with whatever tool was active.
- has_belt_brim(): inner-only brims need brim_width>0 (leading/extra produce no
  inner geometry), fixing spurious prime-tower/spiral rejection; mirror in
  wants_brim. Single-extruder/single-object output is unchanged except
  previously-dropped bands now print.
2026-08-06 15:40:04 -05:00
harrierpigeon c1a90fc451 Fix: correct axis-remap G-code emission and belt first-layer travel speed (B2, B3)
- BeltGCodeWriter::travel_to_xyz final branch used config.travel_speed
  instead of the computed first-layer-aware travel_speed.
- extrude_to_xyz decided emit_xyz vs emit_xy from pre-remap Z; emit full
  XYZ whenever an axis remap is active so remapped machine-Z is never
  dropped.
- base travel_to_xyz now applies apply_axis_remap() on all emitted
  destinations (standalone remap on non-belt printers was unremapped).
- spiral/arc travels fall back to normal linear lift under active remap
  (endpoint-only remap can't preserve arc plane/I-J).
- set_axis_remap() is now synced unconditionally each export to avoid a
  reused writer retaining a stale non-identity mapping.
2026-08-06 14:26:45 -05:00
harrierpigeon 7fc86db5f0 Fix: only clear belt-derived build_plate_tilt on genuine belt->off transition (R8)
update_fff() zeroed any build_plate_tilt matching the dormant belt-derived
tilt (default X/45) within 0.01, wiping a legitimate manual tilt on a
non-belt tilted-bed printer. Track the belt->non-belt transition and the
exact values belt-sync wrote, clearing only those on an in-place toggle;
reset tracking on preset load so preset switches never wipe tilt.
2026-08-06 14:26:45 -05:00
harrierpigeon 6fd2de76e6 Fix: preserve upstream select-by-angle behavior when build plate is untilted (R7)
select_facets_by_angle replaced upstream's limit.dot(down) threshold with
cos(threshold), changing facet selection for non-uniformly-scaled/mirror
objects on ALL printers. Restore the exact upstream computation when no
build-plate tilt is active; keep the tilted-gravity form only under tilt.
2026-08-06 14:26:45 -05:00
harrierpigeon f8fe5a07cd Perf: hoist frame-invariant slope up_direction/normal_z out of the per-volume render loop (R6)
Belt slope-shading changes recomputed up_direction (with a printer-preset
config lookup) and normal_z per volume; both are frame-invariant. Compute
once before the to_render loop and reuse the already-hoisted
support_normal_z. Uniforms are still set per volume; visuals unchanged.
2026-08-06 14:26:45 -05:00
harrierpigeon 049612022a Perf: avoid unconditional lower-layer polygon copies in support overhang paths (R4, R5)
SupportMaterial::detect_overhangs copied lower_layer_polygons per region
even without build-plate tilt; hoist the tilted copy out of the region
loop and use the original polygons directly when untilted. TreeSupport3D
flattened lslices_extrudable to Polygons unconditionally; restore the
upstream ExPolygons offset on the untilted path.
2026-08-06 14:26:45 -05:00
harrierpigeon 10810908a9 Fix: restore atomic node invalidation in TreeSupport::drop_nodes + drop unused var (R3, R9)
The 2-node merge moved the two valid=false writes outside the mutex that
upstream held together with the contact_nodes push_back; restore a single
critical section per branch (belt branch also guards to_buildplate).
Remove an unused top_interface_layers local in drop_nodes.
2026-08-06 14:26:45 -05:00
harrierpigeon 0d92180325 Fix: clear belt fields in GCodeProcessorResult::reset() (R2)
reset() cleared the sibling machine_frame_transform_active but not
belt_tilt_angle/belt_z_origin/preslice_remap_*; a reused result carried
stale belt metadata into a subsequent normal print, flipping the store_z
branch and corrupting start-gcode preview Z for non-belt prints.
2026-08-06 14:26:45 -05:00
harrierpigeon 88d5e9e442 Fix: reset BuildVolume belt state when leaving belt mode (R1)
Non-belt branch of set_bed_shape reset only the 3DBed renderer, not the
BuildVolume; Bed3D::set_shape early-returns on unchanged bed, so a
belt->normal switch or in-place belt toggle-off left the BuildVolume with
m_is_belt_printer=true and inflated Y bounds -> out-of-bounds objects
treated as printable on a normal printer.
2026-08-06 14:26:45 -05:00
Joseph Robertson c51d19f6b2 Add Belt Printer Brims (#15155)
# Description

This adds brim support to belt printers.

Added a new belt printer specific mode, Leading Edge Only and two new
belt-specific parameters, Leading Edge Brim Length, which increases the
number of brim lines on the side of the part printed first, and Extra
Brim Width, which increases the width of brims along the X axis. Because
belt printer first layers are effectively a single line, getting them to
stick properly can be a pain. This PR aims to help alleviate that, or at
least give more options for control.


<img width="1849" height="1043" alt="Screenshot from 2026-08-06
12-20-21"
src="https://github.com/user-attachments/assets/f963ed8e-53e7-48f8-a495-123cb9ae27f7"
/>



[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
2026-08-06 14:18:21 -05:00
harrierpigeon 849e6493f8 Belt brim: offer "Leading edge only" only on belt printers
"Leading edge only" describes where a part meets a moving belt, so it has no
meaning on a fixed bed and should not clutter the Brim type dropdown there.

Filtered the same way support_style and wipe_tower_wall_type already are a few
lines above in TabPrint::toggle_options(): the field holds its own copy of the
option definition, and Choice maps the combobox selection straight onto that
copy's enum_values, so rewriting the values, the labels and the combobox items
together keeps the mapping correct.

The entry is kept when it is the current value, so opening a project that uses
it on a non-belt printer cannot leave the control displaying an option it does
not offer - which would silently rewrite the setting on the next edit.
Print::validate() already warns that it prints as an ordinary outer brim there.

Matches the scope of the existing precedents: the per-object override panel is
not filtered.
2026-08-06 03:54:19 -05:00
harrierpigeon b1905ebc20 Belt brim: fixes from review
Six issues found by reviewing the previous commit against belt-printer, two of
them release-blocking.

Data race (high).  Print::process() runs generate_support_material() for all
objects in a tbb::parallel_for, and make_belt_brim() runs at its tail, but
belt_brim_obstacles() read every OTHER object's support_layers() - which a
concurrent task may be inside clear_support_layers() deleting.  That is a
use-after-free, and even when it survives, the obstacle set depends on which
object finishes first.  Only this object's own supports are consulted now; they
are complete at that point.  Foreign objects still contribute their slices,
which are finished and immutable before the support phase.

Apron bands dropped (high), two separate causes.  An apron band prints below
its own object's first layer, but another object can already be printing at
that print_z, in which case process_layer() takes the ordinary path and never
emitted the band - the emission is now shared by both paths.  Separately, a
band whose print_z matched a support layer of the SAME object was overwritten
in the print-wide merge, which keeps one record per object per z and could not
detect the collision because LayerToPrint::layer() is null for a band.  The
per-object pairing loop is now a three-way merge over object, support and apron
streams, so each object contributes at most one record per z.

Multi-instance was far too strict (medium).  It refused belt brim for every
multi-instance object, killing plain brim width and inner brim too, and only
warned when a leading length was set.  Only movement ALONG the belt changes an
instance's belt-floor Z, so copies side by side ACROSS the belt share one set of
bands perfectly well; belt_brim_instances_compatible() now tests just that, and
the warning fires whenever the brim is actually suppressed.

Apron layer bookkeeping (medium).  Apron layers count toward m_layer_count and
advance m_layer_index, but emitted no Z/height tags, left m_last_layer_z,
m_max_layer_z and m_last_height stale - so the first object layer computed its
height against a pre-apron Z - and skipped before_layer_change_gcode and
layer_change_gcode entirely.  All of that now matches the ordinary path.

Obstacle cost (low).  belt_brim_obstacles() ran a full-plate union per band.
A bounding-box pre-filter drops non-overlapping objects before materialising any
polygon, and the union is skipped for trivial inputs.

Deliberately unchanged: every apron band still reports cooling layer_id 0.
CoolingBuffer uses it for the initial_layer_fan_speed override and the
close_fan_the_first_x_layers gate, and every band lies on the belt plane itself,
so it is all first-layer material by the only definition that means anything on
a belt.  Numbering the bands would ramp the fan up while still printing on the
belt.  Now documented at the assignment rather than left implicit.
2026-08-06 01:08:44 -05:00
harrierpigeon 55b4dca9bc Belt printers: brim laid onto the tilted belt, with a leading apron
A belt printer slices in a rotated frame, so the belt surface is a tilted
plane rather than the Z=0 bed plane.  Each slicing layer touches the belt
only along a narrow strip at its leading edge - about 0.2mm at 45 degrees -
so a part's first layer is really a first line, with almost no contact patch
to hold it down while the belt drags it forward.  Brim was hard-disabled on
belt printers, leaving no remedy at all.

Generate the brim on the belt plane instead.  The object's belt footprint is
the union over layers of each slice clipped to that layer's contact band; the
brim is offset from it in a "flattened" frame where the shear axis is
stretched by 1/cos(tilt), so ordinary Clipper offsets measure true on-belt
distance.  It is emitted as cross-belt lines, one per layer band, anchored to
a fixed fraction of the band so every line shares a nozzle-to-belt clearance
and therefore comes out the same width; flow is matched to the resulting band
pitch, keeping the sheet uniform and gap-free.

Three new controls, all belt-only:

  * Leading brim length - extends the brim ahead of the part along the belt,
    on every downhill-facing edge of its contact area.  This apron necessarily
    prints BELOW the object's first layer, since layer 0 is the part's leading
    contact, so it needs brim-only bands of its own.
  * Extra brim width - widens the brim sideways across the belt only.
  * Brim type "Leading edge only" - brim at the part's first belt contact and
    nothing after it.  Appended last in BrimType so no existing value shifts;
    degrades to an outer brim off belt printers, with a warning.

The apron bands are lightweight records rather than a Layer subclass, so no
fabricated Layer::id() can leak into initial-layer temperature selection, the
spiral vase probe, cooling or gradual interpolation.  They are generated in
posSupportMaterial because their print_z values must exist before ToolOrdering
is built at psWipeTower, and they are emitted from a short dedicated branch in
process_layer that runs before any layer pointer is dereferenced.

The footprint is closed before offsetting outwards: a belt contact patch is
often a broken-up strip, and the merged offset rings of two islands closer
than 2 x brim_width would otherwise fill the space between them - space that
lies under the part.

Also fixes a pre-existing bug where PrintObject::get_first_layer_bbox()
overwrote a valid bbox with an unassigned one on any belt printer with a brim
configured, because has_brim() was true while make_brim() returned early.

Belt brim is refused alongside the prime tower and spiral vase, and requires
one instance per PrintObject - translating an instance along the belt axis
changes its physical belt-floor Z.  Untilted belt printers are unchanged: they
still get no brim, since the plate brim is emitted out of skirt_brim_groups(),
which _make_skirt() never builds for a belt printer.
2026-08-06 01:08:44 -05:00
Joseph Robertson 386364f84b belt profiles: fix belt printer CI failures (slice check + setting_id) (#15127)
The belt-printer branch is failing two profile gates. Both stem from the
three belt-only vendors (Custom's generic belt printer, IdeaFormer,
Printcepts) not existing upstream, so upstream maintenance passed them
by.

Slice check: 4 of 1015 printers failed - Custom's MyBeltPrinter 0.2/0.4/
0.6/0.8 nozzle all fell back to "Default Setting". No process profile in
the Custom vendor listed any MyBeltPrinter in compatible_printers, and
Custom's fdm_belt_common pointed default_print_profile at "0.20mm
Standard @System", which does not exist in that vendor's index, so the
generic belt printer had no usable process at all. This gap dates to
when MyBeltPrinter was added (2026-04-07); it only started failing now
because the slice-check job is newer than that.

Adds two process profiles modelled on the sibling @MyKlipper ones:
  - 0.20mm Standard @MyBeltPrinter - 0.4/0.6/0.8 nozzles
  - 0.12mm Fine @MyBeltPrinter     - 0.2/0.4 nozzles
The split is forced by hardware: the 0.2 nozzle preset caps
max_layer_height at 0.16, so a single 0.20mm profile cannot legally
cover
it. fdm_belt_common now defaults to the standard profile and the 0.2
nozzle preset overrides to the fine one.

setting_id: 14 files failed the rules introduced in #14432. That
migration renumbered 7425 files across 61 vendors but skipped these
three, leaving BabyBelt Pro, IdeaFormer IR3 V2 and MyBeltPrinter
squatting the "G*" id space reserved for Bambu (GMPC0BBP01, GMIF001,
GM_BELT_00x) and four instantiated filament/process presets carrying no
setting_id at all. Regenerated with
scripts/assign_vendor_setting_ids.py.

Also repoints the identical dangling "0.20mm Standard @System" in
Printcepts' and IdeaFormer's fdm_belt_common at their own real process
profiles. That is a no-op today because both concrete printers override
it, but it is the same landmine that took out MyBeltPrinter.

Vendor index versions bumped so check_installed_vendor_profiles() will
re-install the corrected profiles over an existing install.

Note: changing a shipped preset's setting_id can orphan user presets
that reference it as base_id. #14432 accepted that tradeoff for 61
vendors; this keeps these three consistent with the rest.

Verified: orca_extra_profile_check.py reports 0 errors across 66 vendors
(was 14 files with errors), and OrcaSlicer_profile_validator -s slices
all 1015 printer presets successfully (was 4 failures).


[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
2026-08-04 17:16:14 -05:00
harrierpigeon 725df64108 profiles: fix belt printer CI failures (slice check + setting_id)
The belt-printer branch is failing two profile gates. Both stem from the
three belt-only vendors (Custom's generic belt printer, IdeaFormer,
Printcepts) not existing upstream, so upstream maintenance passed them by.

Slice check: 4 of 1015 printers failed - Custom's MyBeltPrinter 0.2/0.4/
0.6/0.8 nozzle all fell back to "Default Setting". No process profile in
the Custom vendor listed any MyBeltPrinter in compatible_printers, and
Custom's fdm_belt_common pointed default_print_profile at
"0.20mm Standard @System", which does not exist in that vendor's index,
so the generic belt printer had no usable process at all. This gap dates
to when MyBeltPrinter was added (2026-04-07); it only started failing now
because the slice-check job is newer than that.

Adds two process profiles modelled on the sibling @MyKlipper ones:
  - 0.20mm Standard @MyBeltPrinter - 0.4/0.6/0.8 nozzles
  - 0.12mm Fine @MyBeltPrinter     - 0.2/0.4 nozzles
The split is forced by hardware: the 0.2 nozzle preset caps
max_layer_height at 0.16, so a single 0.20mm profile cannot legally cover
it. fdm_belt_common now defaults to the standard profile and the 0.2
nozzle preset overrides to the fine one.

setting_id: 14 files failed the rules introduced in #14432. That
migration renumbered 7425 files across 61 vendors but skipped these three,
leaving BabyBelt Pro, IdeaFormer IR3 V2 and MyBeltPrinter squatting the
"G*" id space reserved for Bambu (GMPC0BBP01, GMIF001, GM_BELT_00x) and
four instantiated filament/process presets carrying no setting_id at all.
Regenerated with scripts/assign_vendor_setting_ids.py.

Also repoints the identical dangling "0.20mm Standard @System" in
Printcepts' and IdeaFormer's fdm_belt_common at their own real process
profiles. That is a no-op today because both concrete printers override
it, but it is the same landmine that took out MyBeltPrinter.

Vendor index versions bumped so check_installed_vendor_profiles() will
re-install the corrected profiles over an existing install.

Note: changing a shipped preset's setting_id can orphan user presets that
reference it as base_id. #14432 accepted that tradeoff for 61 vendors;
this keeps these three consistent with the rest.

Verified: orca_extra_profile_check.py reports 0 errors across 66 vendors
(was 14 files with errors), and OrcaSlicer_profile_validator -s slices all
1015 printer presets successfully (was 4 failures).
2026-08-04 17:15:20 -05:00
Joseph Robertson c5bf238859 Update Belt-Printer Branch (#15087)
gets belt-printer on top of upstream again.
2026-08-03 02:09:41 -05:00
harrierpigeon f563df04f6 belt: default first_layer_plane to Auto, not BeltAffine
BeltAffine activates the FirstLayerPlane evaluator unconditionally, so on a
non-belt printer on_first_layer(point) stopped agreeing with the legacy
slicing-layer-0 test. Every per-path first-layer call site in _extrude then
took the non-first-layer branch, and first-layer speeds were skipped: brim
came out at the volumetric fallback (24.6 mm/s) instead of initial_layer_speed
(10 mm/s). This is the shared speed path, so it affected all printers on this
branch, not just belt ones.

Auto resolves to BeltAffine only when belt_printer is set with a non-zero
slicing rotation, and to XY (evaluator inactive, legacy behaviour) otherwise --
exactly what the option's own description already promised.

Caught by "Brim uses first layer speed" (upstream #14616), which arrived with
the upstream merge; the bad default dates back to a9bae54f20 (#30). Verified
against a pristine upstream/main build, which passes the same test.

tests/fff_print: 100/100 test cases, 1085 assertions (was 99/100).
Both belt regression tests still pass, confirming Auto still resolves to
BeltAffine for belt printers.

Note: this changes a config default. Projects and profiles that stored
first_layer_plane explicitly are unaffected; those relying on the default will
now get correct first-layer speeds on non-belt printers, so their G-code
changes accordingly.
2026-08-03 01:52:43 -05:00
harrierpigeon 613dad92a1 Add belt-printer regression test for prepare-stage move Z
Processes a minimal belt start sequence through GCodeProcessor::process_buffer
and asserts the move preceding the first extrusion keeps its real Z, so it can
no longer back-transform to model Y~=0 and produce the phantom extrusion line.

Belt printers are non-Bambu, so the processor uses the compatible reserved
tags ("TYPE:"); the test sets s_IsBBLPrinter=false (saved/restored via an RAII
guard) to mirror the real printer. Proven to fail without the fix (the
prepare-stage move's Z is pinned to the first-layer height, 0 here) and pass
with it.
2026-08-03 01:18:55 -05:00
harrierpigeon a83cd8aa29 Fix belt printer phantom extrusion line from Y=0 in preview
On a belt printer the sliced preview drew a stray extrusion-colored line
from Y~=0 to the model, rendered in the first extrusion role's color. It is
not a travel and does not occur on non-belt printers.

GCodeProcessor::store_move_vertex pins a move's stored Z to the first-layer
height during the start-G-code "prepare" stage. That is a harmless cosmetic
tidy-up on a normal printer, but on a belt printer the designed-view
back-transform couples machine Z into the rendered model Y (the belt tilt
mixes the height and belt-feed axes). Pinning Z back-transforms the last
prepare-stage move (the unretract before the first extrusion) to model
Y ~= 0, and libvgcode then draws a phantom extrusion segment from Y ~= 0 to
the first real toolpath.

Keep the real Z for belt printers (gated on belt_tilt_angle, parsed from the
G-code header before the body) so prepare-stage moves back-transform
correctly. Non-belt processing is byte-identical. The emitted G-code was
already correct; this is a preview-geometry fix.
2026-08-03 01:09:23 -05:00
harrierpigeon 02e313a115 Add belt-printer regression test for start-of-print gantry move
Locks in the fix from the previous commit. A fresh BeltGCodeWriter has an
unestablished planar position (is_current_position_clear() == false) and its
m_pos.xy is the origin (0,0). With a pending NormalLift z-hop, travel_to_xyz
used to lift in place via _travel_to_z(), which in belt mode shears the origin
into a machine Y ~= the layer Z — a move far up the gantry.

The test configures an X-tilt 45 deg belt transform, defers a z-hop via
lazy_lift, travels to a near-belt first point (transformed gantry Y ~= 1mm),
and asserts no emitted move has Y anywhere near the layer Z. Verified to fail
without the fix (max emitted Y = 100.0 vs the destination's ~1.0) and pass with
it.
2026-08-03 01:09:11 -05:00
harrierpigeon 04554abae6 Fix belt printer illegal gantry move at print start
On a belt printer the first travel of the print emitted a bogus move to
the bed corner with the nozzle far up the gantry, e.g.
  G1 X95 Y168.19 Z237.857 F12000
right after the first "; printing object" line. Y168 (≈ the layer Z)
is out of the gantry's range.

Root cause: the layer-change z-hop is deferred via lazy_lift and consumed
by the first BeltGCodeWriter::travel_to_xyz, whose NormalLift branch does a
separate lift-in-place via _travel_to_z(target.z()). On a normal printer
_travel_to_z emits a Z-only move, but in belt mode Z is coupled to Y/X, so
_travel_to_z re-emits the current m_pos through the belt shear. At print
start (and after custom gcode) m_pos.xy is still the uninitialised origin
(0,0), which the back-transform + axis-remap shear into machine
(X=bed_max, Y=layer_z) — the illegal move.

Guard the NormalLift branch on is_current_position_clear(), matching the
SlopeLift branch directly above it which already does so. When the position
isn't established there is nothing to lift over, and the xy_z_move that
follows travels straight to the destination with full XYZ, establishing the
correct position. Bookkeeping is unaffected: in this path m_lifted stays 0,
so no spurious restore move is produced.

Verified by re-slicing the repro project: the start-of-print move is now
G1 X44.946 Y.621 Z237.857 (straight to the first object point), no move
touches the bed-max X edge, and the max Y over the whole file is 62.8mm
(printable_height 100).
2026-08-03 00:15:24 -05:00
HarrierPigeon 0342e06d87 last step in fixing the g-code stuff up 2026-08-02 22:13:34 -05:00
HarrierPigeon 79fd847ce3 fix pre-slice warnings 2026-08-02 22:12:46 -05:00
HarrierPigeon 8f6802fff8 step one: post-process analysis 2026-08-02 22:12:09 -05:00
harrierpigeon b61ba98183 belt: adapt BeltGCodeWriter to upstream's per-extruder speed options
Upstream retyped travel_speed and travel_speed_z to ConfigOptionFloatsNullable
and initial_layer_travel_speed to ConfigOptionFloatsOrPercentsNullable, so the
scalar .value / get_abs_value() accessors no longer compile. BeltGCodeWriter.cpp
is belt-only and merged without conflict, so this only surfaced at build time.

Index them the way the base GCodeWriter does -- .get_at(m_cached_extruder_idx)
and get_abs_value_at(..., m_cached_extruder_idx) -- keeping belt's per-point
first_layer_for_point test rather than the base class's m_is_first_layer.

m_cached_extruder_idx moves from private to the existing protected block that
already exposes writer state to subclasses, so the belt writer resolves the
per-extruder index identically to the base writer instead of guessing one.
2026-08-02 16:20:22 -05:00
harrierpigeon 175075fd08 Merge upstream/main into belt-printer
Brings the belt-printer work up to date with 591 upstream commits.

Conflict resolutions (12 files, 42 hunks):

- GCode.cpp: adopted upstream's per-filament/per-nozzle config refactor
  (get_filament_config_index, NOZZLE_CONFIG), the extracted
  generate_timelapse_gcode + farthest-point timelapse, and the
  ConfigOptionFloatsNullable calibration options. Re-applied the belt
  hooks on top: init_belt_writer / axis remap / FirstLayerPlane setup,
  on_set_origin, the belt-corrected calib_z for the volumetric speed
  tower, and path_on_first_layer (belt's per-path first-layer test) in
  place of upstream's layer-index on_first_layer() in the acceleration,
  jerk and overhang-detection paths. Swept upstream's new m_writer.
  uses to m_writer-> since belt holds the writer by unique_ptr.
- interpolate_value_across_layers: kept upstream's banded stepping and
  belt's object-Z-span ratio; dropped upstream's duplicate ratio decl.
- Plater.cpp: took upstream's guarded add_model(...) early-returns and
  the VFA vfa_layer_height plumbing; kept the belt temp-tower path,
  _calib_apply_belt_mode and belt_calib_flip_ringing_tower. Dropped the
  VFA "cut upper" block, superseded upstream by model scaling.
- Brim.cpp: upstream's ObjectInstanceID-keyed brimAreaMap, keeping the
  belt early-return.
- 3DScene.cpp: kept both the belt build-plate tilt up_direction and
  upstream's per-extruder printable-height shading.
- GCodeViewer.cpp: kept upstream's dim-previous-layers setup and belt's
  exemption from the same-result early return.
- TreeSupport.cpp: upstream's >= 0 roof-layer fix inside belt's
  belt-floor branch.
- calib.cpp / GCode.hpp / GCodeWriter.{cpp,hpp} / Print.hpp: upstream's
  additions adapted to belt's pointer-held writer and helpers.
- Custom.json: kept profile version 02.04.00.03 (belt) over upstream's
  02.04.00.01; both bumped from 02.04.00.00.

Building this tree needs the wxInspector dependency, which upstream
added in the interim (python3 and wxWidgets 3.3.2 were already present
in the shared deps prefix).
2026-08-02 16:09:27 -05:00
Joseph Robertson 5428a0715d update belt-printer (#14446)
[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
2026-06-26 23:02:49 -05:00
Joseph Robertson 75770321dd Update Belt-Printer (#14425) 2026-06-25 22:40:26 -05:00
Joseph Robertson c950c3fb6b Add BabyBelt Pro Profile, Courtesy of Rexit (#14424) 2026-06-25 22:39:12 -05:00
Joseph Robertson 2ca843a38e Belt Printing: Bugfix: Solid Organic Tree Base, Slim Tree Skirt, Renderer (#14395)
* fix tree support brim
* treesupport3d part 1: more diagnostic logging.  (todo once things are fixed: remove this / gate it properly)
* make area under Z=0 in rotated slice pipeline not solid
* fix solid Z=0 layer for belt printers
* fix renderer
* clean up logging
* final review pass
2026-06-24 22:01:29 -05:00
Joseph Robertson 0ef7c6d581 Belt Printing: Update (#14393)
# Description

<!--
> Please provide a summary of the changes made in this PR. Include
details such as:
  > * What issue does this PR address or fix?
  > * What new features or enhancements does this PR introduce?
> * Are there any breaking changes or dependencies that need to be
considered?
-->

# Screenshots/Recordings/Graphs

<!--
> Please attach relevant screenshots to showcase the UI changes.
> Please attach images that can help explain the changes.
-->

## Tests

<!--
> Please describe the tests that you have conducted to verify the
changes made in this PR.
-->

<!--
> A guide for users on how to download the artifacts from this PR.
-->

[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
2026-06-24 21:34:53 -05:00
Joseph Robertson 34b0d36cda Belt Printer Initial Push (#14385)
# Description

Initial push - documentation available at #12998 

[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
2026-06-24 09:42:40 -05:00
Joseph Robertson d619c7e19c Merge branch 'belt-printer' into belt/baseChanges 2026-06-24 09:42:25 -05:00
Joseph Robertson 31b44cb731 Merge pull request #66 from HarrierPigeon/belt/tommyb-rendererChanges
Clean up and implement @tommasobbianchi's belt renderer changes
2026-06-23 00:27:39 -05:00
harrierpigeon ddbee84e68 render the G-code preview upright (designed view) + toggle UI 2026-06-23 00:14:17 -05:00
Joseph Robertson bf6cce1f40 Merge pull request #45 from tommasobbianchi/feat/belt-gcode-cartesian-preview
belt: render the G-code preview upright (model/Cartesian space)
2026-06-22 19:59:27 -05:00
Joseph Robertson 8bdf0df00a Merge branch 'main' into belt/baseChanges 2026-06-22 19:36:17 -05:00
Joseph Robertson d6c9187c71 Merge branch 'main' into belt/baseChanges 2026-06-22 19:36:17 -05:00
Ian Bassi 0cdfb88357 Lang: Gettext update (#14361) 2026-06-22 20:16:55 -03:00
foXaCe 14cec7239b i18n(fr): translate strings added after the post-refactor sync (#14304) 2026-06-22 20:13:19 -03:00
Heiko LiebscherandClaude Opus 4.8 86c6a1a66f Improve German (de) translation (#14352)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 15:40:14 -03:00
SoftFever 07f08dfe40 bump version to 2.5.0-dev 2026-06-22 00:50:51 +08:00
Noisyfox a4fb5af9e1 Don't allow adding more colors for non-semm printers on obj import color remapping dialog (#14275) 2026-06-21 18:20:58 +08:00
Tommaso Bianchi 8593d66a39 belt: correct the designed-view preview's belt-Z origin and reject mis-mapped outliers
The Cartesian designed-view preview over-extended the toolpaths past the model
shell by a height-proportional amount (up to ~20mm tall parts), most visibly on
long multi-part prints; compact parts like a calibration cube looked fine.

Two coupled causes:
- Belt start G-code that primes with a Z advance and a 'G92 Z0' reset leaves a
  constant machine-Z origin in the GCodeProcessor, so move positions are stored as
  gcode_Z + origin. The linear back-transform mixes that constant with the
  gantry-Y term, leaving a per-move designed-Y error that min-corner anchoring
  cannot cancel when an elevated move (e.g. a bridge) happens to cancel it at the
  bbox minimum. Expose GCodeProcessorResult::belt_z_origin (the m_origin[Z] left by
  the start G-code) and subtract it before the back-transform.
- Elevated features (bridges/overhangs) are mis-mapped by the linear inverse to
  outside the model body; build the anchor bbox only from moves within model_bb +/-
  10mm, with a fallback to the full bbox when the clip would drop the bulk (object
  placed away from the belt entry) so the gross-offset case still anchors.

Preview-only; G-code output is unchanged.
2026-06-21 06:48:44 +02:00
Tommaso Bianchi 3fc3b8a8ae belt: anchor the designed-view G-code preview onto the model bounding box
The belt designed (upright) preview back-transforms the machine-frame G-code
into model space with the linear belt inverse. That inverse recovers the
print's shape and orientation, but not the per-object placement/lift
translation: the object's position on the belt, the BeltSliceStrategy min-Z
lift, and the centering pre-translate are applied OUTSIDE
build_forward_transform() (see PrintObjectSlice.cpp), so its linear inverse
cannot undo them. The result was a constant offset (~20 mm on the belt-advance
axis) of the toolpaths from the model shell, on every model.

Recover the missing translation generally — independent of the offset's exact
source or the axis remap — by anchoring the back-transformed object body
(extrusions on layer_id >= 1, i.e. excluding the layer-0 prime/skirt) onto the
upright model bounding box, the same space the shells render in, and folding
that translation into the belt inverse before converting to libvgcode.

Replaces the previous Y=0 anchoring in LibVGCodeWrapper, which pinned the
toolpaths to the belt entry rather than to the model and so left the offset in
place for any object not sitting at the origin.
2026-06-21 06:48:44 +02:00
Tommaso Bianchi 695a1f897a belt: render the G-code preview in model (Cartesian) space
On a belt printer the emitted G-code is in the machine frame (45-deg sheared,
axis-remapped, scaled), so the toolpath preview shows the print as a sheared
slab floating off the bed. Map each toolpath vertex back to model/Cartesian
space for the "designed" view.

The back-transform is the inverse of the full G-code forward pipeline
(BeltGCodeWriter::to_machine_coords):
  model = [BeltForward^-1 if !gcode_back_transform] . AxisRemap^-1 . MachineFrame^-1
built from config, so it handles any rotation / shear / scale / axis-remap
combination, not just plain 45-deg belt slicing. Computed in load_as_gcode()
from print.config() and applied per-vertex inside libvgcode::convert (display
position only; layer_id, times and the volumetric/flow math keep the raw
machine values, so the layer slider and stats are unaffected).

- Toggle with the existing "Show designed view" checkbox / hotkey B; off shows
  the raw machine-frame G-code (useful for debugging the transform itself).
  Defaults to on.
- Belt printers skip the same-result-id load cache so the upright view applies
  and the toggle takes effect even when the G-code is unchanged.
- The object extrusions (layer_id >= 1) are anchored to the belt entry to drop
  the constant machine-origin offset (start-G-code belt advance) that the linear
  back-transform alone does not capture; start-G-code prime lines are excluded
  so they don't steal the anchor.
2026-06-21 06:48:44 +02:00
Tommaso Bianchi 2d69f6e17c belt: expose MachineFrameTransform's composed matrix
Add a const accessor for the shear*scale transform so the G-code viewer can
build the machine->model back-transform for the upright belt preview.
2026-06-21 06:48:44 +02:00
Joseph Robertson 340ce575e2 Merge branch 'main' into belt/baseChanges 2026-06-20 15:56:59 -05:00
Joseph Robertson d795900fcf Merge pull request #64 from tommasobbianchi/feat/esun-pla-maxvolspeed-tuning
IdeaFormer IR3 V2: tune eSUN PLA white speed from HW max-vol-speed calibration
2026-06-18 09:42:19 -05:00
Joseph Robertson 9b1fb2217a Merge branch 'main' into belt/baseChanges 2026-06-18 09:41:14 -05:00
Tommaso BianchiandClaude Opus 4.8 ef6f65eacc IdeaFormer IR3 V2: tune eSUN PLA white speed from HW max-vol-speed calibration
Physical max-volumetric-speed test (belt #62 v4 asset) on the IR3 V2 with eSUN
PLA white: the wall stayed clean up to ~100 mm/s = ~20 mm3/s before
under-extrusion. The shipped cap of 10 mm3/s was ~half the real ceiling and
was silently throttling infill.

- eSUN PLA @IdeaFormer IR3 V2: filament_max_volumetric_speed 10 -> 20
- 0.20mm Standard @IdeaFormer IR3 V2: sparse_infill_speed 200 (~18 mm3/s at the
  new cap, no longer throttled). Outer wall (45), PA (0.12), accel (1000)
  unchanged — accuracy preserved.
- IdeaFormer.json version bump for profile-cache refresh.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 07:13:59 +02:00
Joseph Robertson 0add523e1b Merge branch 'main' into belt/baseChanges 2026-06-13 08:23:47 -05:00
Joseph Robertson 375036f330 Merge pull request #44 from tommasobbianchi/feat/belt-skip-height-check
belt: don't reject long objects (skip build-height check on belt printers)
2026-06-13 08:23:26 -05:00
Joseph Robertson fbbeb1fab0 Merge pull request #58 from HarrierPigeon/belt/tempTower-TommyB
Belt/temp tower tommy b
2026-06-12 05:53:32 -05:00
harrierpigeon 0bca3fd2e5 make belt printer specific temp tower only accessible to belt printers 2026-06-12 05:13:08 -05:00
Tommaso Bianchi 85fd613cf7 feat(belt/calib): add Overhang temperature-tower model (selectable) (#48)
Belt printers can't slice a tall vertical temperature tower. This adds a
belt-specific temperature-tower model — a row of discrete, individually
engraved provini laid along the belt, each printed at one temperature via
custom per-layer M104. Each provino is an inverted-L overhang that stresses
print quality, so the operator reads the best temperature off overhang
quality rather than a continuous ramp.

It is offered as a "Test model" choice in the temperature calibration dialog
(mirroring the Cornering test's selector), so users keep Joe's counter-rotated
sectioned tower as "Standard" and can pick this one as "Overhang":
- Calib_Params::test_model (existing field) carries the choice.
- Temp_Calibration_Dlg gets a Standard/Overhang radio.
- Plater::calib_temp belt branch: test_model 0 -> _calib_temp_belt_sectioned
  (unchanged Standard path), 1 -> the discrete-provini Overhang path.

Assets: belt_temp_provino_unit.stl + belt_temp_tower_<start>_<end>.stl (6
ranges) + gen_belt_temp_tower.py (manifold engraving). Based on
belt/generic-calibrations. The Overhang path is HW-validated on the IdeaFormer
IR3 V2 (discrete M104 + engraved numbers); not re-validated since the rebase.
2026-06-12 05:13:07 -05:00
Joseph Robertson 0da24cd38b Belt/Standard calibrations (#54)
Enables supported printing of standard Orcaslicer calibration profiles.

* Build 2 Checkpoint

* fix support generation wedge, ghost layers

* flip cornering tests 180 deg to waste less supports

* fix row spacing on the flow ratio calibrations

* more testing, this didn't fix anything

* switched rotation tools, same issue

* fixed Z-offset issues

* add rest of PA features, may look a bit weird on a belt

* make temp towers work

* re-enable spiral on calibrations that want it

* Final cleanup pre-PR and community testing
2026-06-12 03:14:12 -05:00
Rodrigo Faselli d7b75540d0 Merge branch 'main' into belt/baseChanges 2026-06-11 11:59:53 -03:00
Tommaso Bianchi b7bda9912b belt: fix IR3 V2 end G-code reversing the belt into the part (#56)
The IdeaFormer IR3 V2 End G-code ran `G28 ; home all`, which homes the
Z (belt) and Y (gantry) axes. On a belt printer Z is the conveyor, so
homing it runs the belt all the way back to origin, dragging the finished
part back under the gantry that G28 has just lowered — the head knocks the
print (reported by an IR3 V2 user; the `G1 Y50` lift came after the G28,
too late).

Replace the end sequence with a belt-safe one: switch to relative mode
(G91), lift the gantry for clearance, advance the belt forward one full
machine-depth (Z676, the 676 mm product depth) to eject the part and cycle
the belt surface clean, then home X only — never the Z/belt axis.
2026-06-11 09:30:35 -05:00
Tommaso Bianchi 4f3a608009 belt: don't flag the lead-in as an empty-layer error on belt printers (#47)
collect_layers_to_print() warns (CRITICAL) when an extrusion layer sits above
the previous one with an empty gap below — the fixed-bed assumption that
material with nothing under it is floating and unprintable. On a belt printer a
*leading* empty range (the gap starts at Z=0, no prior extrusion layer) is not
floating: it is the conveyor lead-in, and the part rests on the advancing belt
as the first material is laid down well above Z=0. A part not designed for a
belt (e.g. a flat test model tilted into the belt frame) then trips this as a
false "Object can't be printed for empty layer between 0 and N" error.

Suppress only the leading case (belt_printer && last_extrusion_layer == null);
genuine internal gaps are still flagged, since on a belt those can be an
over-angle overhang printing into air. Non-belt output is unchanged.
2026-06-10 23:54:02 -05:00
Tommaso BianchiandClaude Opus 4.8 f682ab5cd3 belt: replace height-check skip with a belt-correct vertical-clearance check
The original PR skipped the max-print-height check entirely on belt printers
because the sliced (virtual) Z is belt travel, not build height. As the reviewer
noted, that removed the only working height guard. Restore a correct guard:

- Print::validate: on belt printers, compare the upright object height
  (max over instances of the scene-space bbox) against printable_height directly.
  printable_height is the usable VERTICAL clearance above the belt: the gantry
  travels up the tilted plane (reach = height/cos(tilt)) and its axis range is
  sized for that (IR3 V2: ~354 mm gantry travel = 250 mm vertical at 45deg, and
  printable_height = 250). Hardware-confirmed 250 mm vertical clearance, so no
  cos(tilt) factor is applied.
- BuildVolume::set_belt_printer: drop the diagonal Z scaling; the build-volume Z
  already equals printable_height, keeping the live 'outside build volume'
  highlight in agreement with validate().

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 21:41:18 +02:00
harrierpigeon 2bcb775b90 update IdeaFormer profiles to new generic belt printer config 2026-06-10 05:10:20 -05:00
Joseph Robertson e29a82c672 add attribution and design notes 2026-06-10 05:10:20 -05:00
Joseph Robertson 5b243eec92 Relocate Pre-Slice remap logic 2026-06-10 05:10:20 -05:00
Joseph Robertson fee6be98b2 unify frame tilt work 2026-06-10 05:10:20 -05:00
Joseph Robertson 9405ac5976 remove mesh origin snapping 2026-06-10 05:10:20 -05:00
Tommaso Bianchi 6ed2437848 Add IdeaFormer IR3 V2 belt printer profile - credit: tommasobbianchi (#43)
* Add IdeaFormer IR3 V2 belt printer profile

Self-contained vendor profile for the IdeaFormer IR3 V2 (45 deg belt printer):
machine (0.4 nozzle) + 0.20mm process + Generic PLA/PETG filaments, with the
belt machine-frame transforms set explicitly on the machine preset
(belt_printer, belt_slice_rotation x/45/global, build_plate_tilt_x=45,
gcode_remap_x/y/z, gcode_shear_z=pos_tan, gcode_scale_y=inv_cos).

The vendor bundles its own machine/process commons (fdm_belt_common,
fdm_klipper_common, fdm_machine_common, fdm_process_common) on purpose:
OrcaSlicer resolves system-preset inheritance per-vendor, so a profile that
inherits the Custom vendor's commons cross-vendor fails to resolve its parent
and the whole IdeaFormer vendor silently fails to load. Bundling the commons
(and listing them in IdeaFormer.json in dependency order) keeps the vendor
self-contained, matching how every other vendor folder is structured.

Machine limits, bed temperature (75 C for belt PLA) and start/end G-code are
taken from a working IdeaFormer IR3 V2.



* feat(belt/profile): eSUN PLA @IdeaFormer IR3 V2 — HW-calibrated belt filament

Add an eSUN PLA belt profile for the IR3 V2, inheriting Generic PLA @IdeaFormer
IR3 V2 (self-contained: parent is in the same IdeaFormer vendor, registered
after it in filament_list). HW-calibrated on the IR3 V2:
- nozzle_temperature 200/200 (temp-tower calibration)
- pressure_advance 0.12 (PA calibration)
- filament_max_volumetric_speed 10 mm³/s (max-vol-speed calibration: wall
  failed at 126 mm/s → 126 × 0.0798 mm³/mm ≈ 10 mm³/s)
2026-06-10 04:13:56 -05:00
Joseph Robertson da3fee2dfa Merge branch 'main' into belt/baseChanges 2026-06-05 11:55:44 -05:00
Joseph Robertson c0d6ae8540 Merge branch 'main' into belt/baseChanges 2026-06-05 03:12:27 -05:00
Joseph Robertson 573e1c6544 Belt/fix profiles and minor oopsies (#42)
* fix duplicate printer, bump version

* clean up extra tab in space

* fix generic defaults
2026-06-05 03:11:38 -05:00
Rodrigo Faselli 20be78a96e Merge branch 'main' into belt/baseChanges 2026-06-04 17:32:35 -03:00
Joseph Robertson 02d45c3258 Finish Fixes from Copilot Review (#39)
* fix: restore BuildVolume bounds when toggling belt mode

set_belt_printer() mutated m_bboxf when enabling but never restored
the original extents on disable or when switching infinite_y true->false,
leaving stale max.y/max.z values that broke collision and object_state
checks. Recompute m_bboxf from m_bed_shape + m_max_print_height at the
top of each call, then apply belt-specific adjustments on top.

Addresses Copilot review comment on PR #12998 (BuildVolume.cpp:196).

* chore: drop [BELT-DEBUG] to_machine_coords log to trace

Was emitting at warning level once per 0.2mm Z bucket during every belt
print export, polluting default user logs. Trace level matches the rest
of the belt diagnostics and is silent in production.

Addresses Copilot review comment on PR #12998 (BeltGCodeWriter.cpp:86).

* chore: drop [BELTRACE] make_perimeters/support logs to trace

Eight warning-level traces around make_perimeters and
generate_support_material were emitting on every call/exit during normal
slicing, cluttering default logs. They're concurrency-debug breadcrumbs
not user-facing diagnostics, so drop them to trace.

Addresses Copilot review comment on PR #12998 (PrintObject.cpp:438).

* perf: gate BeltSliceStrategy diagnostic bbox tracking behind compile flag

apply_to_trafo() walked every model vertex twice (once for min_z, once
for per-volume mesh/slicer bboxes) and emitted seven trace logs per
call. The bboxes and logs are diagnostic only; min_z is the load-bearing
output. Wrap the bbox accumulation, logging, and supporting headers in
SLIC3R_BELT_DIAGNOSTIC_LOG so production builds do the bare min_z scan.

Addresses Copilot review comment on PR #12998 (BeltSliceStrategy.cpp:95).

* fix: apply part_cooling_fan_min_pwm to first-layer plane fan crossings

apply_first_layer_plane_fan_eval emitted band-crossing M106 commands
through GCodeWriter::set_fan() without the per-printer PWM floor that
every other set_fan call in CoolingBuffer applies. On printers with a
non-zero part_cooling_fan_min_pwm, fans could fail to spin up at low
requested speeds near the belt surface.

Addresses Copilot review comment on PR #12998 (CoolingBuffer.cpp:1227).
2026-06-04 14:40:45 -05:00
harrierpigeon f9888c7d7a Merge remote-tracking branch 'upstream/main' into belt/baseChanges 2026-05-31 05:17:32 -05:00
Joseph Robertson 0bda684dd7 delete mesh transforms (#37)
* delete mesh shear, scale and refactor logger

* clean up config options

* reorder UI elements
2026-05-31 05:08:42 -05:00
Joseph Robertson 8a578cdf00 Merge branch 'main' into belt/baseChanges 2026-05-30 21:39:03 -05:00
Rodrigo Faselli 6b256db012 Merge branch 'main' into belt/baseChanges 2026-05-28 07:44:43 -03:00
Joseph Robertson 2dc4900292 Copilot review fixes & upstream code interaction fix (#34)
* first pass at review issue 8
* delete detritus
* fix build compile error due to upstream changes
2026-05-27 21:53:04 -05:00
Joseph RobertsonandCopilot Autofix powered by AI 0f75d6bc4e Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-27 19:25:02 -05:00
Joseph Robertson e913621369 Merge branch 'main' into belt/baseChanges 2026-05-27 11:50:16 -05:00
Joseph Robertson 48b6db93b8 Belt/slice rotate (#33)
* initial commit
* fix upper bounds for assemblies
* significantly less Z shift issues, still not quite tamped down yet though
* add instrumentation to logs
* finally found the issue
* update printer defaults
2026-05-27 11:45:38 -05:00
Joseph Robertson 72cafcbe06 Merge branch 'main' into belt/baseChanges 2026-05-22 15:23:07 -05:00
Joseph Robertson a9bae54f20 Rotate instead of shear for slicing stage (#30)
* initial commit

* fix upper bounds for assemblies

* significantly less Z shift issues, still not quite tamped down yet though

* add instrumentation to logs

* finally found the issue

* update printer defaults
2026-05-22 15:21:33 -05:00
Joseph Robertson 218881c6f6 fix assembly bounding box truncation problems noticed by hotcubcar (#28) 2026-05-20 02:46:41 -05:00
Joseph Robertson cd5fb68d38 Merge branch 'main' into belt/baseChanges 2026-05-19 23:00:14 -05:00
Joseph Robertson f87a46ec6e fix X mirroring (#26)
Thanks to @hotcubcar for catching this!
2026-05-19 22:54:50 -05:00
Rodrigo Faselli 8dc91d8b1d Merge branch 'main' into belt/baseChanges 2026-05-19 08:06:57 -03:00
Joseph Robertson da8b11b8ab HOTFIX: update generic belt printer profile (#23)
oops
2026-05-19 01:06:39 -05:00
Joseph Robertson c79970bedb Clean Up Settings Interface, Update Generic Profile (#22)
* clean up UI elements

* further cleaning

* final cleanup for first round of settings UI streamlining

* update generic belt printer settings

* fix generic again
2026-05-19 00:56:08 -05:00
harrierpigeonandClaude Opus 4.7 7252f6acb7 Merge upstream/main into belt/rebase/may-18
Reconciles the belt-printer branch with upstream PRs through #13723. Six
files had conflicts; three additional files needed manual follow-up fixes
where the auto-merge produced code that referenced upstream-renamed fields
or changed function signatures.

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

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

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 21:53:24 -05:00
Joseph Robertson 6a2d690f45 Decouple Slicing From Machine Frame Logic (#21)
* minor logic swap

* first attempt, has a race condition

* fixed the offset issue

* found a solution, I think things work now (at least once I quash this race condition)

* still chasing down race conditions

* add manual shear / scale order strategy swap

* tweak manual shear, fix ui uninitialization crash

* fix z height / g-code desync issue

* fix shear then scale cutoff planes

* getting closer

* fix support termination planes

* fix incorrect offsets in shear-then-scale mode

* test - fix overextrusion due to model/layer scale
2026-05-18 19:01:43 -05:00
RF47 8fa6a4602b fix profile indentation 2026-05-09 19:51:18 -03:00
harrierpigeonandClaude Opus 4.7 0f29437135 Merge remote-tracking branch 'upstream/main' into belt/baseChanges
Conflicts resolved in src/libslic3r/GCode.cpp and src/slic3r/GUI/GUI_Factories.cpp.

GCode.cpp: combined upstream's air-filtration per-extruder gating
(activate_air_filtration_during_print / _on_completion), the new
extrusion-role-change gcode lambda, ZAA's path.z_contoured arc-fit
disable, raft-aware slow_down_layers branch, and Vec3d/Line3 ZAA
plumbing with the local belt-printer changes (path_on_first_layer,
effective_layer_index_for_point, should_disable_arc_fitting). All
auto-merged m_writer.X() calls converted to m_writer->X() to match
the local unique_ptr<GCodeWriter> refactor.

GUI_Factories.cpp: inserted brim_flow_ratio in the Support category
list and renumbered around the local build_plate_tilt_x/y entries.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 16:23:41 -05:00
SoftFever 75cc0de071 Merge branch 'main' into belt/baseChanges 2026-04-17 15:44:03 +08:00
Joseph Robertson bc6d0ef0fb Add first layer detection and fan control - prototype 2026-04-13 22:29:23 -05:00
Joseph Robertson c17ae25bbc Merge branch 'main' into belt/baseChanges 2026-04-13 21:34:34 -05:00
harrierpigeon e981a517cd Merge branch 'belt/global-mesh-transform' into temp-pr19-merge 2026-04-10 11:49:37 -05:00
harrierpigeon 0703728e56 add global mesh transform option 2026-04-10 11:39:08 -05:00
SoftFever 1e9ee0c120 add a generic belt printer 2026-04-09 23:07:08 -05:00
harrierpigeon e9a579b604 switch default shear axis, swap to tan(a) instead of cot(a) 2026-04-09 23:07:07 -05:00
harrierpigeon 783acd932a revert CLAUDE.md 2026-04-09 23:07:07 -05:00
harrierpigeon c8a1bf3a99 Part 3.2: decouple axis remapping, enable viewing settings in Developer mode or when Belt mode is active 2026-04-09 23:07:07 -05:00
harrierpigeon 2facaac9e8 Part 3.1: refactor BeltTransform pipeline
add BeltGCodeWriter

add BeltGCode

consolidate changes into shared classes for BeltGcode
2026-04-09 23:07:07 -05:00
harrierpigeon 9bbac19de4 Part 2.7: Add G-code back-transform and tree support belt floor clipping
- Add BeltBackTransform class that inverts the shear/scale matrix and
  applies it in GCodeWriter::to_machine_coords() so G-code outputs in
  the machine's physical coordinate space, gated by new
  belt_gcode_back_transform config option
- Extend belt floor clipping to all three tree support pipelines
  (Prusa-style, Orca organic, TreeModelVolumes) with per-layer polygon
  clipping, anti-overhang integration, and belt raft extension layers
- Fix tree drop_nodes() belt termination, organic support global Z
  offset, collision calculation index bug, and first-layer brim/empty
  layer checks for belt printers

two-shot - first build built but didn't plumb to UI.  Woah.

add pre-slice axis remap, because Y needs to be Z

going to change tactic and move based on bbox min

switch to per axis snapping

per axis swap snap now per object

build plate tilt wasn't invalidating slicer settings

support upper bound now correct, need to get lower bound corrected

axis swapped support termination corrected

Z Shear works with and without pre-slice remap now
2026-04-09 23:07:07 -05:00
harrierpigeon ea5c6776b3 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
2026-04-09 23:07:07 -05:00
harrierpigeon 98f4d34dcb Part 2.5: Add global shear transform, support clipping, and belt UI improvements
- Implement per-object global shear transform in PrintObject with
  layer Z-offset calculation, config invalidation, and fix for
  shared-object layer optimization breaking copied objects
- Clip support layers to the transformed belt floor plane and begin
  work on tree support adaptation for sheared coordinate space
- Improve belt UI: gray out inactive sub-options, add B keyboard
  shortcut for G-code viewer design-view toggle, fix mesh clipping
  through build plate after shear/scale transform

y' = y + z·cot(α),
  while x' = x and z' = z

getting closer to customizable variant

getting closer

X/Y/Z shear initial

clean up UI

add 1/sin(a) transform, idea taken from blackbelt cura plugin

Things work now (turns out I've been using the wrong set of  transforms)
2026-04-09 23:07:07 -05:00
harrierpigeon 501aff7e53 Part 2: Replace belt rotation w/ per-axis shear transforms and G-code axis remap
- Replace monolithic belt rotation transform with independent per-axis
    shear controls (mode/angle/source-axis for X, Y, Z) and G-code axis
    remapping, giving full flexibility to match any belt printer's
    coordinate system
  - Remove all rotation mode logic and intermediate type+axes dropdowns,
    simplifying the pipeline to pure shear matrices while preserving the
    default behavior (Y += Z*cot(45deg) with identity remap)
  - Clean up GCodeWriter, GCodeProcessor, and GCodeViewer for the new
    shear-only model; expose 12 new settings in printer UI via
    Tab.cpp/Preset.cpp

Implement belt printer tilted slicing

Implement the core belt slicing pipeline that makes the slicer
tilt-aware:

Step 1: GCodeWriter::to_machine_coords() - R(+alpha, X) rotation
  from slicing frame to machine frame
Step 2: PrintObject - belt-rotated object height calculation
  (y*sin(a) + z*cos(a)) for correct layer count
Step 3: PrintObjectSlice - apply R(-alpha, X) rotation trafo so
  horizontal slice planes correspond to belt-parallel planes,
  with Z-shift computed from model volumes
Step 4: GCodeProcessor - machine-frame preview (no transform needed)
Step 5: 3DBed - rotate bed visualization about X by belt angle

Fix: belt surface IS the build plate, no mesh rotation

Currently still slicing perpendicular to the belt normal.  Need to figure out why.

Fix G-code Z sign: use R(-alpha, X) so Z+ is away from belt

The previous R(+alpha, X) transform produced negative Z values
(-y*sin(a) term dominated). Changed to R(-alpha, X) which gives
machine_z = y*sin(a) + z*cos(a), always positive for points
above the belt surface. Z increases with each layer as expected.

reverting and changing slice methodology

Add pink slicing direction arrow from origin

Shows the effective slicing direction (gantry normal) as a pink
arrow from the origin. Shorter and wider than the gravity arrow.
Direction: R(+alpha, X) * Z = (0, -sin(a), cos(a)), which is
the layer stacking direction in the original mesh frame.

Fix slicing arrow visibility and add raw G-code toggle

- Disable depth test for pink slicing arrow so it renders on top of
  the tilted bed geometry (was being occluded)
- Remove unnecessary 5mm Z-offset from arrow position
- Add m_belt_show_raw toggle to GCodeViewer
- Add "Show raw G-code (slicing frame)" checkbox in legend when
  belt mode is active

Implement to_machine_coords inverse rotation for belt printer G-code

The slicing pipeline rotates the mesh by R(-alpha, X) and shifts Z to
start at 0. The G-code output now undoes this transform via
to_machine_coords: R(+alpha, X) * T(0,0,+z_shift), recovering the
original machine-frame coordinates where Y is horizontal and Z is
vertical.

Changes:
- GCodeWriter: implement to_machine_coords with inverse rotation + Z-shift
- GCodeWriter: add belt_z_shift member and setter/getter
- GCode.cpp: compute Z-shift from print objects (same logic as
  PrintObjectSlice) and pass to writer; write z_shift to G-code header
- GCodeProcessor: parse belt_z_shift from G-code header
- GCodeViewer: store belt_z_shift from processor result

Wire raw G-code toggle to apply slicing-frame view transform

When "Show raw G-code (slicing frame)" is checked in the preview
legend, the view matrix is modified to apply R(-alpha, X) * T(0,0,-z_shift)
to the toolpath rendering. This shows the G-code as it was during
slicing: rotated part with horizontal layers.

Default (unchecked): machine-frame view — upright part with tilted layers.

Remove belt printer placeholder comment from GCodeProcessor

The preview now correctly displays machine-frame G-code with the
optional raw view toggle. No transform is needed in the processor.
2026-04-09 23:07:06 -05:00
harrierpigeon c808653565 Add belt printer transform pipeline: slicing rotation, G-code coords, preview
- Implement core belt slicing pipeline: R(-alpha, X) mesh rotation in PrintObjectSlice with corrected object height calculation for proper layer count
Add to_machine_coords() in GCodeWriter to convert slicing-frame coordinates back to machine-frame, propagated through GCode,
GCodeProcessor, and GCodeViewer
Add belt-mode UI: tilted bed visualization, slicing-direction arrow, and raw G-code toggle to switch between machine-frame and slicing-frame views

This is a combination of 6 commits.

checkpoint 1: initial MVP.  Slicing functions, but rotates instead of skews are happening and a lot of other stuff too

getting somewhere, getting to the point where I need to figure out how to verify this stuff

this appears to be a dead end.

getting somewhere I think maybe

I'm pretty sure we've completely lost the plot at this point and need to restart this process...

remove slice logic in preparation for new, more invasive plan
2026-04-09 23:07:06 -05:00
harrierpigeon a7441c7f48 stage in changes from off-plate-gravity and remove stuff I didn't need 2026-04-09 23:07:06 -05:00
SoftFever 3bc13e5cfd add a generic belt printer 2026-04-07 10:37:34 +08:00
SoftFever 141749a6f2 Merge branch 'main' into belt/baseChanges 2026-04-06 22:52:31 +08:00
harrierpigeon 4634a5dfd7 switch default shear axis, swap to tan(a) instead of cot(a) 2026-03-30 13:25:40 -05:00
harrierpigeon 372139c770 revert CLAUDE.md 2026-03-30 13:25:40 -05:00
harrierpigeon 44eebdb8ad Part 3.2: decouple axis remapping, enable viewing settings in Developer mode or when Belt mode is active 2026-03-30 13:25:40 -05:00
harrierpigeon c7aa4ca3ef Part 3.1: refactor BeltTransform pipeline
add BeltGCodeWriter

add BeltGCode

consolidate changes into shared classes for BeltGcode
2026-03-30 13:25:40 -05:00
harrierpigeon b297f68921 Part 2.7: Add G-code back-transform and tree support belt floor clipping
- Add BeltBackTransform class that inverts the shear/scale matrix and
  applies it in GCodeWriter::to_machine_coords() so G-code outputs in
  the machine's physical coordinate space, gated by new
  belt_gcode_back_transform config option
- Extend belt floor clipping to all three tree support pipelines
  (Prusa-style, Orca organic, TreeModelVolumes) with per-layer polygon
  clipping, anti-overhang integration, and belt raft extension layers
- Fix tree drop_nodes() belt termination, organic support global Z
  offset, collision calculation index bug, and first-layer brim/empty
  layer checks for belt printers

two-shot - first build built but didn't plumb to UI.  Woah.

add pre-slice axis remap, because Y needs to be Z

going to change tactic and move based on bbox min

switch to per axis snapping

per axis swap snap now per object

build plate tilt wasn't invalidating slicer settings

support upper bound now correct, need to get lower bound corrected

axis swapped support termination corrected

Z Shear works with and without pre-slice remap now
2026-03-30 13:25:40 -05:00
harrierpigeon 7ff6bc42b1 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
2026-03-30 13:25:40 -05:00
harrierpigeon 719af2d81d Part 2.5: Add global shear transform, support clipping, and belt UI improvements
- Implement per-object global shear transform in PrintObject with
  layer Z-offset calculation, config invalidation, and fix for
  shared-object layer optimization breaking copied objects
- Clip support layers to the transformed belt floor plane and begin
  work on tree support adaptation for sheared coordinate space
- Improve belt UI: gray out inactive sub-options, add B keyboard
  shortcut for G-code viewer design-view toggle, fix mesh clipping
  through build plate after shear/scale transform

y' = y + z·cot(α),
  while x' = x and z' = z

getting closer to customizable variant

getting closer

X/Y/Z shear initial

clean up UI

add 1/sin(a) transform, idea taken from blackbelt cura plugin

Things work now (turns out I've been using the wrong set of  transforms)
2026-03-30 13:25:40 -05:00
harrierpigeon cb13a22e57 Part 2: Replace belt rotation w/ per-axis shear transforms and G-code axis remap
- Replace monolithic belt rotation transform with independent per-axis
    shear controls (mode/angle/source-axis for X, Y, Z) and G-code axis
    remapping, giving full flexibility to match any belt printer's
    coordinate system
  - Remove all rotation mode logic and intermediate type+axes dropdowns,
    simplifying the pipeline to pure shear matrices while preserving the
    default behavior (Y += Z*cot(45deg) with identity remap)
  - Clean up GCodeWriter, GCodeProcessor, and GCodeViewer for the new
    shear-only model; expose 12 new settings in printer UI via
    Tab.cpp/Preset.cpp

Implement belt printer tilted slicing

Implement the core belt slicing pipeline that makes the slicer
tilt-aware:

Step 1: GCodeWriter::to_machine_coords() - R(+alpha, X) rotation
  from slicing frame to machine frame
Step 2: PrintObject - belt-rotated object height calculation
  (y*sin(a) + z*cos(a)) for correct layer count
Step 3: PrintObjectSlice - apply R(-alpha, X) rotation trafo so
  horizontal slice planes correspond to belt-parallel planes,
  with Z-shift computed from model volumes
Step 4: GCodeProcessor - machine-frame preview (no transform needed)
Step 5: 3DBed - rotate bed visualization about X by belt angle

Fix: belt surface IS the build plate, no mesh rotation

Currently still slicing perpendicular to the belt normal.  Need to figure out why.

Fix G-code Z sign: use R(-alpha, X) so Z+ is away from belt

The previous R(+alpha, X) transform produced negative Z values
(-y*sin(a) term dominated). Changed to R(-alpha, X) which gives
machine_z = y*sin(a) + z*cos(a), always positive for points
above the belt surface. Z increases with each layer as expected.

reverting and changing slice methodology

Add pink slicing direction arrow from origin

Shows the effective slicing direction (gantry normal) as a pink
arrow from the origin. Shorter and wider than the gravity arrow.
Direction: R(+alpha, X) * Z = (0, -sin(a), cos(a)), which is
the layer stacking direction in the original mesh frame.

Fix slicing arrow visibility and add raw G-code toggle

- Disable depth test for pink slicing arrow so it renders on top of
  the tilted bed geometry (was being occluded)
- Remove unnecessary 5mm Z-offset from arrow position
- Add m_belt_show_raw toggle to GCodeViewer
- Add "Show raw G-code (slicing frame)" checkbox in legend when
  belt mode is active

Implement to_machine_coords inverse rotation for belt printer G-code

The slicing pipeline rotates the mesh by R(-alpha, X) and shifts Z to
start at 0. The G-code output now undoes this transform via
to_machine_coords: R(+alpha, X) * T(0,0,+z_shift), recovering the
original machine-frame coordinates where Y is horizontal and Z is
vertical.

Changes:
- GCodeWriter: implement to_machine_coords with inverse rotation + Z-shift
- GCodeWriter: add belt_z_shift member and setter/getter
- GCode.cpp: compute Z-shift from print objects (same logic as
  PrintObjectSlice) and pass to writer; write z_shift to G-code header
- GCodeProcessor: parse belt_z_shift from G-code header
- GCodeViewer: store belt_z_shift from processor result

Wire raw G-code toggle to apply slicing-frame view transform

When "Show raw G-code (slicing frame)" is checked in the preview
legend, the view matrix is modified to apply R(-alpha, X) * T(0,0,-z_shift)
to the toolpath rendering. This shows the G-code as it was during
slicing: rotated part with horizontal layers.

Default (unchecked): machine-frame view — upright part with tilted layers.

Remove belt printer placeholder comment from GCodeProcessor

The preview now correctly displays machine-frame G-code with the
optional raw view toggle. No transform is needed in the processor.
2026-03-30 13:25:40 -05:00
harrierpigeon ed6ea086a2 Add belt printer transform pipeline: slicing rotation, G-code coords, preview
- Implement core belt slicing pipeline: R(-alpha, X) mesh rotation in PrintObjectSlice with corrected object height calculation for proper layer count
Add to_machine_coords() in GCodeWriter to convert slicing-frame coordinates back to machine-frame, propagated through GCode,
GCodeProcessor, and GCodeViewer
Add belt-mode UI: tilted bed visualization, slicing-direction arrow, and raw G-code toggle to switch between machine-frame and slicing-frame views

This is a combination of 6 commits.

checkpoint 1: initial MVP.  Slicing functions, but rotates instead of skews are happening and a lot of other stuff too

getting somewhere, getting to the point where I need to figure out how to verify this stuff

this appears to be a dead end.

getting somewhere I think maybe

I'm pretty sure we've completely lost the plot at this point and need to restart this process...

remove slice logic in preparation for new, more invasive plan
2026-03-30 13:25:40 -05:00
harrierpigeon 08aa277974 stage in changes from off-plate-gravity and remove stuff I didn't need 2026-03-30 13:25:40 -05:00
741 changed files with 13340 additions and 1930 deletions
+3 -3
View File
@@ -127,7 +127,7 @@ jobs:
if: ${{ !cancelled() && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }}
uses: ./.github/workflows/build_check_cache.yml
with:
os: ${{ vars.SELF_HOSTED && 'orca-macos-arm64' || 'macos-15' }}
os: ${{ vars.SELF_HOSTED && 'orca-macos-arm64' || 'macos-14' }}
arch: ${{ matrix.arch }}
build-deps-only: ${{ inputs.build-deps-only || false }}
force-build: ${{ github.event_name == 'schedule' }}
@@ -138,7 +138,7 @@ jobs:
if: ${{ !cancelled() && needs.build_macos_arch.result == 'success' && !inputs.build-deps-only && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }}
uses: ./.github/workflows/build_orca.yml
with:
os: ${{ vars.SELF_HOSTED && 'orca-macos-arm64' || 'macos-15' }}
os: ${{ vars.SELF_HOSTED && 'orca-macos-arm64' || 'macos-14' }}
arch: universal
macos-combine-only: true
secrets: inherit
@@ -182,7 +182,7 @@ jobs:
if: ${{ !cancelled() && success() }}
uses: ./.github/workflows/unit_tests.yml
with:
os: ${{ vars.SELF_HOSTED && 'orca-macos-arm64' || 'macos-15' }}
os: ${{ vars.SELF_HOSTED && 'orca-macos-arm64' || 'macos-14' }}
artifact: ${{ github.sha }}-tests-macos-arm64
test-dir: build/arm64/tests
# Slice a two-colour cube through every shipped printer, and through every
+1 -1
View File
@@ -40,7 +40,7 @@ jobs:
# Anything that changes how the tree is built belongs in the key, or a job
# restores one it cannot use. Linux amd64 passes no arch deliberately, so
# 'linux-clang' keeps the cache it already has.
cache-os: ${{ runner.os == 'macOS' && format('{0}-{1}', inputs.os, inputs.arch) || (runner.os == 'Windows' && format('windows-{0}-{1}', inputs.arch, inputs.compiler) || format('linux-clang{0}', inputs.arch && format('-{0}', inputs.arch) || '')) }}
cache-os: ${{ runner.os == 'macOS' && format('macos-{0}', inputs.arch) || (runner.os == 'Windows' && format('windows-{0}-{1}', inputs.arch, inputs.compiler) || format('linux-clang{0}', inputs.arch && format('-{0}', inputs.arch) || '')) }}
# The Windows ARM64 deps build in build-arm64, all others under build;
# build_deps.yml and build_orca.yml pass the Windows directory to build_win.bat.
dep-folder-name: ${{ runner.os == 'macOS' && format('/{0}', inputs.arch) || (runner.os == 'Windows' && inputs.arch == 'arm64') && '-arm64/OrcaSlicer_dep' || '/OrcaSlicer_dep' }}
+3 -5
View File
@@ -183,12 +183,10 @@ jobs:
- name: Free disk space
if: runner.os == 'macOS' && !inputs.macos-combine-only && !vars.SELF_HOSTED
run: |
df -hI /
# Keep only the selected Xcode
xcode=$(basename "$(cd "$(xcode-select -p)/../.." && pwd -P)")
sudo find /Applications -maxdepth 1 -type d -name "Xcode_*.app" ! -name "$xcode" -exec rm -rf {} +
df -hI /dev/disk3s1s1
sudo find /Applications -maxdepth 1 -type d -name "Xcode_*.app" ! -name "Xcode_15.4.app" -exec rm -rf {} +
sudo rm -rf ~/Library/Developer/CoreSimulator/Caches/*
df -hI /
df -hI /dev/disk3s1s1
- name: Build slicer mac
if: runner.os == 'macOS' && !inputs.macos-combine-only
@@ -1111,7 +1111,27 @@ private:
default: ; // DONT_ALIGN
}
auto d = cb - ci;
auto d = cb - ci;
// Keep the pile on the bin. A target near an edge (a belt printer starts its parts
// at the leading end of the belt) would otherwise centre a pile that is larger than
// the room around that point on it and push part of the pile off the bed. The pile
// stops at the edge instead; the items' boxes carry their inflation, which is the
// margin left there. A pile that does not fit along an axis is centred on it.
{
auto on_bin = [](Coord lo, Coord hi, Coord bin_lo, Coord bin_hi, Coord shift) {
if (hi - lo >= bin_hi - bin_lo)
return (bin_lo + bin_hi) / 2 - (lo + hi) / 2;
if (lo + shift < bin_lo)
shift = bin_lo - lo;
if (hi + shift > bin_hi)
shift = bin_hi - hi;
return shift;
};
setX(d, on_bin(getX(bb.minCorner()), getX(bb.maxCorner()), getX(bbin.minCorner()), getX(bbin.maxCorner()), getX(d)));
setY(d, on_bin(getY(bb.minCorner()), getY(bb.maxCorner()), getY(bbin.minCorner()), getY(bbin.maxCorner()), getY(d)));
cb = ci + d;
}
// BBS make sure the item won't clash with excluded regions
// do we have wipe tower after arranging?
@@ -0,0 +1,79 @@
#!/usr/bin/env python3
"""Belt temperature-tower asset generator (discrete-provini design).
A vertical temperature tower cannot be sliced on a belt printer, so lay a row of
DISCRETE provini (one per temperature) along the belt (designed Y) with a fixed
surface gap. Each provino is the chevron+arc unit (belt_temp_provino_unit.stl,
keel-first); its temperature is ENGRAVED upright into the 50 mm face — a raised
number would be an unsupported overhang on the belt. The C++ calib_temp belt branch
(Plater.cpp) injects one M104 per zone 70 layers INTO provino i:
print_z[i] = i * PITCH * cos(theta) + 70 * layer_height (theta = 45)
inside the body, not in the empty inter-provino gap (which has no sliced layers for
the event to attach to). PITCH below is the shared geometry contract with that code —
keep them in sync.
Generates one STL per filament temp range used by Temp_Calibration_Dlg.
"""
import numpy as np, trimesh, os
from matplotlib.textpath import TextPath
from matplotlib.font_manager import FontProperties
from shapely.geometry import Polygon as ShPoly
from shapely.ops import unary_union
HERE = os.path.dirname(os.path.abspath(__file__))
UNIT = os.path.join(HERE, 'belt_temp_provino_unit.stl') # single provino, keel-first
SURF_GAP = 25.0 # surface-to-surface gap between provini (mm) — user spec
TEXT_H = 9.0
TEXT_DEPTH = 0.8 # engraving depth (numbers are CUT into the face, not raised:
# a raised number is an unsupported Y-overhang on the belt)
TEXT_OVERSHOOT = 0.6 # extra height poking out of the face for a clean boolean cut
# Temperature ranges (start, end) per filament family, 5 C step. File name encodes them.
RANGES = [(230,190),(270,230),(250,230),(280,240),(240,210),(320,280)]
unit = trimesh.load(UNIT)
dY = unit.bounds[1,1] - unit.bounds[0,1]
PITCH = dY + SURF_GAP # designed-Y pitch == C++ contract constant
print(f"unit dY={dY:.2f} PITCH={PITCH:.3f} (C++ contract: print_z[i]=i*{PITCH:.3f}*cos45)")
# 50 mm face normal (0,-1,1)/sqrt2 ; UPRIGHT basis u=+X det(+1) (verified non-mirrored)
n = np.array([0,-1,1.])/np.sqrt(2)
u = np.array([1,0,0.]); v = np.array([0,1,1.])/np.sqrt(2)
R = np.column_stack([u,v,n])
fn = unit.face_normals; fc = unit.triangles_center; fa = unit.area_faces
sel = (fn@n) > 0.9
face_c = (fc[sel]*fa[sel,None]).sum(0)/fa[sel].sum()
def text_mesh(s):
tp = TextPath((0,0), s, size=TEXT_H, prop=FontProperties(family='DejaVu Sans'))
rings = [ShPoly(p) for p in tp.to_polygons() if len(p)>=3]
rings.sort(key=lambda r:r.area, reverse=True)
used=[False]*len(rings); parts=[]
for i,o in enumerate(rings):
if used[i]: continue
holes=[]
for j in range(i+1,len(rings)):
if not used[j] and o.contains(rings[j]): holes.append(rings[j].exterior.coords); used[j]=True
parts.append(ShPoly(o.exterior.coords,holes)); used[i]=True
poly = unary_union(parts)
geoms = list(poly.geoms) if poly.geom_type=='MultiPolygon' else [poly]
m = trimesh.util.concatenate([trimesh.creation.extrude_polygon(g,height=TEXT_DEPTH+TEXT_OVERSHOOT) for g in geoms])
c = m.bounds.mean(axis=0); m.apply_translation([-c[0],-c[1],0]); return m
for t_start, t_end in RANGES:
temps = list(range(t_start, t_end-1, -5))
parts=[]
for i,T in enumerate(temps):
c = unit.copy(); c.apply_translation([0, i*PITCH, 0])
t = text_mesh(str(T)); M=np.eye(4); M[:3,:3]=R; t.apply_transform(M)
# place the text spanning from TEXT_DEPTH inside the face to TEXT_OVERSHOOT outside,
# then CUT it out of the provino (engrave) — no raised material, no Y-overhang.
t.apply_translation(face_c - n*TEXT_DEPTH + np.array([0,i*PITCH,0]))
c = trimesh.boolean.difference([c, t], engine='manifold')
parts.append(c)
asset = trimesh.util.concatenate(parts)
out = os.path.join(HERE, f"belt_temp_tower_{t_start}_{t_end}.stl")
asset.export(out)
dims = np.round(asset.bounds[1]-asset.bounds[0],1)
wt = all(p.is_watertight for p in parts)
print(f" {t_start}->{t_end}: {len(temps)} zones bbox={dims} watertight={wt} -> {os.path.basename(out)}")
+33 -1
View File
@@ -1,9 +1,13 @@
{
"name": "Custom Printer",
"version": "02.04.00.07",
"version": "02.04.00.08",
"force_update": "0",
"description": "My configurations",
"machine_model_list": [
{
"name": "Generic Belt Printer",
"sub_path": "machine/MyBeltPrinter.json"
},
{
"name": "Generic Klipper Printer",
"sub_path": "machine/MyKlipper.json"
@@ -50,6 +54,10 @@
"name": "0.08mm Extra Fine @MyKlipper",
"sub_path": "process/0.08mm Extra Fine @MyKlipper.json"
},
{
"name": "0.12mm Fine @MyBeltPrinter",
"sub_path": "process/0.12mm Fine @MyBeltPrinter.json"
},
{
"name": "0.12mm Fine @MyKlipper",
"sub_path": "process/0.12mm Fine @MyKlipper.json"
@@ -62,6 +70,10 @@
"name": "0.16mm Optimal @MyKlipper",
"sub_path": "process/0.16mm Optimal @MyKlipper.json"
},
{
"name": "0.20mm Standard @MyBeltPrinter",
"sub_path": "process/0.20mm Standard @MyBeltPrinter.json"
},
{
"name": "0.20mm Standard @MyKlipper",
"sub_path": "process/0.20mm Standard @MyKlipper.json"
@@ -262,6 +274,10 @@
"name": "MyKlipper 0.8 nozzle",
"sub_path": "machine/MyKlipper 0.8 nozzle.json"
},
{
"name": "fdm_belt_common",
"sub_path": "machine/fdm_belt_common.json"
},
{
"name": "fdm_toolchanger_common",
"sub_path": "machine/fdm_toolchanger_common.json"
@@ -274,6 +290,22 @@
"name": "MyRRF 0.4 nozzle",
"sub_path": "machine/MyRRF 0.4 nozzle.json"
},
{
"name": "MyBeltPrinter 0.2 nozzle",
"sub_path": "machine/MyBeltPrinter 0.2 nozzle.json"
},
{
"name": "MyBeltPrinter 0.4 nozzle",
"sub_path": "machine/MyBeltPrinter 0.4 nozzle.json"
},
{
"name": "MyBeltPrinter 0.6 nozzle",
"sub_path": "machine/MyBeltPrinter 0.6 nozzle.json"
},
{
"name": "MyBeltPrinter 0.8 nozzle",
"sub_path": "machine/MyBeltPrinter 0.8 nozzle.json"
},
{
"name": "MyToolChanger 0.2 nozzle",
"sub_path": "machine/MyToolChanger 0.2 nozzle.json"
Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

@@ -0,0 +1,27 @@
{
"type": "machine",
"name": "MyBeltPrinter 0.2 nozzle",
"inherits": "fdm_belt_common",
"from": "system",
"setting_id": "3w1uyJdmm14QhDnH",
"instantiation": "true",
"printer_model": "Generic Belt Printer",
"default_print_profile": "0.12mm Fine @MyBeltPrinter",
"nozzle_diameter": [
"0.2"
],
"max_layer_height": [
"0.16"
],
"min_layer_height": [
"0.04"
],
"printer_variant": "0.2",
"printable_area": [
"0x0",
"350x0",
"350x350",
"0x350"
],
"printable_height": "300"
}
@@ -0,0 +1,20 @@
{
"type": "machine",
"name": "MyBeltPrinter 0.4 nozzle",
"inherits": "fdm_belt_common",
"from": "system",
"setting_id": "6nRHUtvJOUffocbu",
"instantiation": "true",
"printer_model": "Generic Belt Printer",
"nozzle_diameter": [
"0.4"
],
"printer_variant": "0.4",
"printable_area": [
"0x0",
"350x0",
"350x350",
"0x350"
],
"printable_height": "300"
}
@@ -0,0 +1,26 @@
{
"type": "machine",
"name": "MyBeltPrinter 0.6 nozzle",
"inherits": "fdm_belt_common",
"from": "system",
"setting_id": "K0m9HbUNwKT4UCJV",
"instantiation": "true",
"printer_model": "Generic Belt Printer",
"nozzle_diameter": [
"0.6"
],
"max_layer_height": [
"0.4"
],
"min_layer_height": [
"0.12"
],
"printer_variant": "0.6",
"printable_area": [
"0x0",
"350x0",
"350x350",
"0x350"
],
"printable_height": "300"
}
@@ -0,0 +1,26 @@
{
"type": "machine",
"name": "MyBeltPrinter 0.8 nozzle",
"inherits": "fdm_belt_common",
"from": "system",
"setting_id": "rHAweDz4eNwttPNA",
"instantiation": "true",
"printer_model": "Generic Belt Printer",
"nozzle_diameter": [
"0.8"
],
"max_layer_height": [
"0.6"
],
"min_layer_height": [
"0.2"
],
"printer_variant": "0.8",
"printable_area": [
"0x0",
"350x0",
"350x350",
"0x350"
],
"printable_height": "300"
}
@@ -0,0 +1,12 @@
{
"type": "machine_model",
"name": "Generic Belt Printer",
"model_id": "my_belt_01",
"nozzle_diameter": "0.4;0.2;0.6;0.8",
"machine_tech": "FFF",
"family": "MyPrinter",
"bed_model": "Custom_350_bed.stl",
"bed_texture": "orcaslicer_bed_texture.svg",
"hotend_model": "",
"default_materials": "Generic PLA @System;Generic PLA-CF @System;Generic PETG @System;Generic TPU @System;Generic PC @System;Generic PVA @System;Generic PA @System;Generic PA-CF @System"
}
@@ -0,0 +1,96 @@
{
"type": "machine",
"name": "fdm_belt_common",
"inherits": "fdm_klipper_common",
"from": "system",
"instantiation": "false",
"gcode_flavor": "klipper",
"single_extruder_multi_material": "0",
"default_filament_profile": [
"Generic PLA @System"
],
"default_print_profile": "0.20mm Standard @MyBeltPrinter",
"max_layer_height": [
"0.32"
],
"min_layer_height": [
"0.08"
],
"deretraction_speed": [
"30"
],
"extruder_colour": [
"#FCE94F"
],
"extruder_offset": [
"0x0"
],
"long_retractions_when_cut": [
"0"
],
"nozzle_diameter": [
"0.4"
],
"retract_before_wipe": [
"70%"
],
"retract_length_toolchange": [
"2"
],
"retract_lift_above": [
"0"
],
"retract_lift_below": [
"0"
],
"retract_lift_enforce": [
"All Surfaces"
],
"retract_restart_extra": [
"0"
],
"retract_restart_extra_toolchange": [
"0"
],
"retract_when_changing_layer": [
"1"
],
"retraction_distances_when_cut": [
"18"
],
"retraction_length": [
"0.8"
],
"retraction_minimum_travel": [
"1"
],
"retraction_speed": [
"30"
],
"travel_slope": [
"3"
],
"wipe": [
"1"
],
"wipe_distance": [
"1"
],
"z_hop": [
"0"
],
"z_hop_types": [
"Normal Lift"
],
"gcode_remap_x": "rev_x",
"gcode_remap_y": "pos_z",
"gcode_remap_z": "pos_y",
"belt_printer": "1",
"belt_slice_rotation": "x",
"belt_slice_rotation_angle": "45",
"belt_slice_rotation_global": "1",
"build_plate_tilt_x": "45",
"purge_in_prime_tower": "0",
"scan_first_layer": "0",
"auxiliary_fan": "0"
}
@@ -0,0 +1,20 @@
{
"type": "process",
"name": "0.12mm Fine @MyBeltPrinter",
"inherits": "fdm_process_klipper_common",
"from": "system",
"setting_id": "EugqqdLJ423bgEwN",
"instantiation": "true",
"layer_height": "0.12",
"initial_layer_print_height": "0.12",
"bottom_shell_layers": "5",
"top_shell_layers": "6",
"support_top_z_distance": "0.08",
"support_bottom_z_distance": "0.08",
"skirt_loops": "0",
"skirt_distance": "0",
"compatible_printers": [
"MyBeltPrinter 0.2 nozzle",
"MyBeltPrinter 0.4 nozzle"
]
}
@@ -0,0 +1,17 @@
{
"type": "process",
"name": "0.20mm Standard @MyBeltPrinter",
"inherits": "fdm_process_klipper_common",
"from": "system",
"setting_id": "YzCDAgH3uLOM53pF",
"instantiation": "true",
"layer_height": "0.2",
"initial_layer_print_height": "0.2",
"skirt_loops": "0",
"skirt_distance": "0",
"compatible_printers": [
"MyBeltPrinter 0.4 nozzle",
"MyBeltPrinter 0.6 nozzle",
"MyBeltPrinter 0.8 nozzle"
]
}
+54
View File
@@ -0,0 +1,54 @@
{
"name": "IdeaFormer",
"version": "02.00.00.06",
"force_update": "0",
"description": "IdeaFormer belt printer configurations",
"machine_model_list": [
{
"name": "IdeaFormer IR3 V2",
"sub_path": "machine/IdeaFormer IR3 V2.json"
}
],
"process_list": [
{
"name": "fdm_process_common",
"sub_path": "process/fdm_process_common.json"
},
{
"name": "0.20mm Standard @IdeaFormer IR3 V2",
"sub_path": "process/0.20mm Standard @IdeaFormer IR3 V2.json"
}
],
"filament_list": [
{
"name": "Generic PLA @IdeaFormer IR3 V2",
"sub_path": "filament/Generic PLA @IdeaFormer IR3 V2.json"
},
{
"name": "eSUN PLA @IdeaFormer IR3 V2",
"sub_path": "filament/eSUN PLA @IdeaFormer IR3 V2.json"
},
{
"name": "Generic PETG @IdeaFormer IR3 V2",
"sub_path": "filament/Generic PETG @IdeaFormer IR3 V2.json"
}
],
"machine_list": [
{
"name": "fdm_machine_common",
"sub_path": "machine/fdm_machine_common.json"
},
{
"name": "fdm_klipper_common",
"sub_path": "machine/fdm_klipper_common.json"
},
{
"name": "fdm_belt_common",
"sub_path": "machine/fdm_belt_common.json"
},
{
"name": "IdeaFormer IR3 V2 0.4 nozzle",
"sub_path": "machine/IdeaFormer IR3 V2 0.4 nozzle.json"
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

@@ -0,0 +1,77 @@
{
"type": "filament",
"name": "Generic PETG @IdeaFormer IR3 V2",
"inherits": "Generic PETG @System",
"from": "system",
"setting_id": "n4zaXcUUzTqAxq5f",
"instantiation": "true",
"filament_extruder_variant": [
"Direct Drive Standard"
],
"compatible_printers": [
"IdeaFormer IR3 V2 0.4 nozzle"
],
"filament_type": [
"PETG"
],
"filament_vendor": [
"Generic"
],
"filament_settings_id": [
"Generic PETG @IdeaFormer IR3 V2"
],
"filament_flow_ratio": [
"0.95"
],
"filament_cost": [
"25"
],
"nozzle_temperature": [
"240"
],
"nozzle_temperature_initial_layer": [
"245"
],
"cool_plate_temp": [
"80"
],
"cool_plate_temp_initial_layer": [
"80"
],
"fan_min_speed": [
"40"
],
"fan_max_speed": [
"60"
],
"overhang_fan_threshold": [
"25%"
],
"overhang_fan_speed": [
"80"
],
"full_fan_speed_layer": [
"8"
],
"slow_down_min_speed": [
"20"
],
"slow_down_layer_time": [
"4"
],
"fan_cooling_layer_time": [
"100"
],
"filament_retraction_length": [
"2"
],
"filament_retraction_speed": [
"40"
],
"filament_deretraction_speed": [
"40"
],
"filament_start_gcode": [
"; Generic PETG @IdeaFormer IR3 V2 — belt PETG, bed 80C"
]
}
@@ -0,0 +1,65 @@
{
"type": "filament",
"name": "Generic PLA @IdeaFormer IR3 V2",
"inherits": "Generic PLA @System",
"from": "system",
"setting_id": "1xjycsEAFh6KQIhp",
"instantiation": "true",
"filament_extruder_variant": [
"Direct Drive Standard"
],
"compatible_printers": [
"IdeaFormer IR3 V2 0.4 nozzle"
],
"filament_type": [
"PLA"
],
"filament_vendor": [
"Generic"
],
"filament_settings_id": [
"Generic PLA @IdeaFormer IR3 V2"
],
"nozzle_temperature": [
"215"
],
"hot_plate_temp": [
"75"
],
"hot_plate_temp_initial_layer": [
"75"
],
"cool_plate_temp": [
"75"
],
"cool_plate_temp_initial_layer": [
"75"
],
"textured_plate_temp": [
"75"
],
"textured_plate_temp_initial_layer": [
"75"
],
"close_fan_the_first_x_layers": [
"3"
],
"full_fan_speed_layer": [
"8"
],
"slow_down_min_speed": [
"20"
],
"filament_retraction_length": [
"1.5"
],
"filament_retraction_speed": [
"35"
],
"filament_deretraction_speed": [
"30"
],
"filament_start_gcode": [
"; Generic PLA @IdeaFormer IR3 V2 — belt PLA, bed 75C"
]
}
@@ -0,0 +1,36 @@
{
"type": "filament",
"name": "eSUN PLA @IdeaFormer IR3 V2",
"inherits": "Generic PLA @IdeaFormer IR3 V2",
"from": "system",
"setting_id": "XqkviBmFHEglXueX",
"filament_id": "OFkrxQC4",
"instantiation": "true",
"compatible_printers": [
"IdeaFormer IR3 V2 0.4 nozzle"
],
"filament_type": [
"PLA"
],
"filament_vendor": [
"eSUN"
],
"filament_settings_id": [
"eSUN PLA @IdeaFormer IR3 V2"
],
"nozzle_temperature_initial_layer": [
"200"
],
"nozzle_temperature": [
"200"
],
"enable_pressure_advance": [
"1"
],
"pressure_advance": [
"0.12"
],
"filament_max_volumetric_speed": [
"20"
]
}
@@ -0,0 +1,98 @@
{
"type": "machine",
"name": "IdeaFormer IR3 V2 0.4 nozzle",
"inherits": "fdm_belt_common",
"from": "system",
"setting_id": "MDQZgwRgg72lmjtu",
"instantiation": "true",
"printer_model": "IdeaFormer IR3 V2",
"printer_variant": "0.4",
"nozzle_diameter": [
"0.4"
],
"printable_area": [
"0x0",
"250x0",
"250x2000",
"0x2000"
],
"printable_height": "250",
"belt_printer_infinite_y": "1",
"thumbnails": [
"48x48/PNG",
"300x300/PNG"
],
"default_filament_profile": [
"Generic PLA @IdeaFormer IR3 V2"
],
"default_print_profile": "0.20mm Standard @IdeaFormer IR3 V2",
"use_relative_e_distances": "1",
"machine_max_acceleration_extruding": [
"5000",
"5000"
],
"machine_max_acceleration_retracting": [
"1000",
"1000"
],
"machine_max_acceleration_travel": [
"9000",
"9000"
],
"machine_max_acceleration_x": [
"5000",
"5000"
],
"machine_max_acceleration_y": [
"5000",
"5000"
],
"machine_max_acceleration_z": [
"100",
"100"
],
"machine_max_jerk_x": [
"10",
"10"
],
"machine_max_jerk_y": [
"10",
"10"
],
"machine_max_jerk_z": [
"0.4",
"0.4"
],
"machine_max_speed_e": [
"60",
"60"
],
"machine_max_speed_x": [
"500",
"500"
],
"machine_max_speed_y": [
"500",
"500"
],
"machine_max_speed_z": [
"20",
"20"
],
"retraction_length": [
"2"
],
"retraction_speed": [
"40"
],
"deretraction_speed": [
"40"
],
"retract_lift_below": [
"300"
],
"machine_start_gcode": "; === IdeaFormer IR3 V2 Belt Printer Start ===\n; Axes: X=lateral, Y=gantry height (probe), Z=belt\nG90 ; absolute positioning\nM82 ; absolute extruder\nG21 ; millimeters\nG28 ; home all axes\nG1 Y20 F500 ; lift nozzle 20mm from belt\n; Bed + hotend temps come from the active filament profile. Belt PLA requires 75 C bed — use Generic/eSun PLA @IdeaFormer IR3 V2 filament presets to get it automatically.\nM140 S[hot_plate_temp_initial_layer] ; set bed temp\nM104 S[nozzle_temperature_initial_layer] ; hotend temp\nM109 S[nozzle_temperature_initial_layer] ; wait hotend\nM190 S[hot_plate_temp_initial_layer] ; wait bed\n; --- Purge blob ---\nG92 E0 ; zero extruder\nG1 Y.1 ; nozzle 0.1mm above belt\nG1 E15 F1000 ; purge 15mm blob\nG1 Z20 E25 F800 ; belt advance 20mm + extrude\nG1 E23 ; retract 2mm\nG28 Y ; re-probe belt surface\nG1 E25 ; de-retract\n; --- Prime lines (full 250mm bed width) ---\nFMS_on ; filament motion sensor\nG1 X250 E50 F2000 ; prime line 1\nG92 Z0 ; reset belt origin\nG1 Z.4 ; belt advance 0.4mm\nG1 X0 E75 ; prime line 2\nG1 F1000 ; default feedrate\nG92 E0 Z0 ; zero extruder + belt = print origin\n",
"machine_end_gcode": "; === IdeaFormer IR3 V2 Belt Printer End ===\nM400 ; wait for moves to finish\nM104 S0 ; heater off\nM140 S0 ; bed off\nG92 E0 ; zero extruder\nG1 E-5 F300 ; retract 5mm\nG4 P5000 ; wait for ooze\nG91 ; relative mode - keep every end move relative on a belt\nG1 Y20 F1000 ; raise gantry 20mm for clearance over the part\nG1 Z676 F3000 ; advance belt one full machine-depth to eject the part and clean the belt\nG90 ; back to absolute\nG28 X ; home X only - NEVER 'G28' all: that homes Z/belt and reverses the whole print back into the gantry\nFMS_off ; filament motion sensor off\nBED_MESH_CLEAR\nM84 ; disable motors\n",
"machine_pause_gcode": "PAUSE",
"layer_change_gcode": "G92 E0 ; belt: reset extruder at layer change (relative E)"
}
@@ -0,0 +1,12 @@
{
"type": "machine_model",
"name": "IdeaFormer IR3 V2",
"model_id": "IdeaFormer_IR3_V2",
"nozzle_diameter": "0.4",
"machine_tech": "FFF",
"family": "IdeaFormer",
"bed_model": "",
"bed_texture": "",
"hotend_model": "",
"default_materials": "Generic PLA @IdeaFormer IR3 V2;Generic PETG @IdeaFormer IR3 V2"
}
@@ -0,0 +1,99 @@
{
"type": "machine",
"name": "fdm_belt_common",
"inherits": "fdm_klipper_common",
"from": "system",
"instantiation": "false",
"gcode_flavor": "klipper",
"single_extruder_multi_material": "0",
"default_filament_profile": [
"Generic PLA @System"
],
"default_print_profile": "0.20mm Standard @IdeaFormer IR3 V2",
"max_layer_height": [
"0.32"
],
"min_layer_height": [
"0.08"
],
"deretraction_speed": [
"30"
],
"extruder_colour": [
"#FCE94F"
],
"extruder_offset": [
"0x0"
],
"long_retractions_when_cut": [
"0"
],
"nozzle_diameter": [
"0.4"
],
"retract_before_wipe": [
"70%"
],
"retract_length_toolchange": [
"2"
],
"retract_lift_above": [
"0"
],
"retract_lift_below": [
"0"
],
"retract_lift_enforce": [
"All Surfaces"
],
"retract_restart_extra": [
"0"
],
"retract_restart_extra_toolchange": [
"0"
],
"retract_when_changing_layer": [
"1"
],
"retraction_distances_when_cut": [
"18"
],
"retraction_length": [
"0.8"
],
"retraction_minimum_travel": [
"1"
],
"retraction_speed": [
"30"
],
"travel_slope": [
"3"
],
"wipe": [
"1"
],
"wipe_distance": [
"1"
],
"z_hop": [
"0"
],
"z_hop_types": [
"Normal Lift"
],
"gcode_remap_x": "rev_x",
"gcode_remap_y": "pos_z",
"gcode_remap_z": "pos_y",
"printer_extruder_id": [
"1"
],
"belt_printer": "1",
"belt_slice_rotation": "x",
"belt_slice_rotation_angle": "45",
"belt_slice_rotation_global": "1",
"build_plate_tilt_x": "45",
"purge_in_prime_tower": "0",
"scan_first_layer": "0",
"auxiliary_fan": "0"
}
@@ -0,0 +1,140 @@
{
"type": "machine",
"name": "fdm_klipper_common",
"inherits": "fdm_machine_common",
"from": "system",
"instantiation": "false",
"gcode_flavor": "klipper",
"machine_max_acceleration_e": [
"5000",
"5000"
],
"machine_max_acceleration_extruding": [
"20000",
"20000"
],
"machine_max_acceleration_retracting": [
"5000",
"5000"
],
"machine_max_acceleration_travel": [
"20000",
"20000"
],
"machine_max_acceleration_x": [
"20000",
"20000"
],
"machine_max_acceleration_y": [
"20000",
"20000"
],
"machine_max_acceleration_z": [
"500",
"200"
],
"machine_max_speed_e": [
"25",
"25"
],
"machine_max_speed_x": [
"500",
"200"
],
"machine_max_speed_y": [
"500",
"200"
],
"machine_max_speed_z": [
"12",
"12"
],
"machine_max_jerk_e": [
"2.5",
"2.5"
],
"machine_max_jerk_x": [
"9",
"9"
],
"machine_max_jerk_y": [
"9",
"9"
],
"machine_max_jerk_z": [
"0.2",
"0.4"
],
"machine_min_extruding_rate": [
"0",
"0"
],
"machine_min_travel_rate": [
"0",
"0"
],
"max_layer_height": [
"0.32"
],
"min_layer_height": [
"0.08"
],
"printable_height": "250",
"extruder_clearance_radius": "65",
"extruder_clearance_height_to_rod": "36",
"extruder_clearance_height_to_lid": "140",
"printer_settings_id": "",
"printer_technology": "FFF",
"printer_variant": "0.4",
"retraction_minimum_travel": [
"1"
],
"retract_before_wipe": [
"70%"
],
"retract_when_changing_layer": [
"1"
],
"retraction_length": [
"0.8"
],
"retract_length_toolchange": [
"2"
],
"z_hop": [
"0.4"
],
"retract_restart_extra": [
"0"
],
"retract_restart_extra_toolchange": [
"0"
],
"retraction_speed": [
"30"
],
"deretraction_speed": [
"30"
],
"z_hop_types": "Normal Lift",
"single_extruder_multi_material": "1",
"change_filament_gcode": "",
"wipe": [
"1"
],
"default_filament_profile": [
"Generic PLA @System"
],
"default_print_profile": "0.20mm Standard @MyKlipper",
"bed_exclude_area": [
"0x0"
],
"machine_start_gcode": "M190 S[bed_temperature_initial_layer_single]\nM109 S[nozzle_temperature_initial_layer]\nPRINT_START EXTRUDER=[nozzle_temperature_initial_layer] BED=[bed_temperature_initial_layer_single]\n",
"machine_end_gcode": "PRINT_END",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n",
"machine_pause_gcode": "PAUSE",
"scan_first_layer": "0",
"nozzle_type": "undefine",
"auxiliary_fan": "0"
}
@@ -0,0 +1,118 @@
{
"type": "machine",
"name": "fdm_machine_common",
"from": "system",
"instantiation": "false",
"printer_technology": "FFF",
"deretraction_speed": [
"40"
],
"extruder_colour": [
"#FCE94F"
],
"extruder_offset": [
"0x0"
],
"gcode_flavor": "marlin",
"machine_max_acceleration_e": [
"5000"
],
"machine_max_acceleration_extruding": [
"10000"
],
"machine_max_acceleration_retracting": [
"1000"
],
"machine_max_acceleration_x": [
"10000"
],
"machine_max_acceleration_y": [
"10000"
],
"machine_max_acceleration_z": [
"500"
],
"machine_max_speed_e": [
"60"
],
"machine_max_speed_x": [
"500"
],
"machine_max_speed_y": [
"500"
],
"machine_max_speed_z": [
"10"
],
"machine_max_jerk_e": [
"5"
],
"machine_max_jerk_x": [
"8"
],
"machine_max_jerk_y": [
"8"
],
"machine_max_jerk_z": [
"0.4"
],
"machine_min_extruding_rate": [
"0"
],
"machine_min_travel_rate": [
"0"
],
"max_layer_height": [
"0.32"
],
"min_layer_height": [
"0.08"
],
"printable_height": "250",
"extruder_clearance_radius": "65",
"extruder_clearance_height_to_rod": "36",
"extruder_clearance_height_to_lid": "140",
"nozzle_diameter": [
"0.4"
],
"printer_settings_id": "",
"printer_variant": "0.4",
"retraction_minimum_travel": [
"2"
],
"retract_before_wipe": [
"70%"
],
"retract_when_changing_layer": [
"1"
],
"retraction_length": [
"1"
],
"retract_length_toolchange": [
"1"
],
"z_hop": [
"0"
],
"retract_restart_extra": [
"0"
],
"retract_restart_extra_toolchange": [
"0"
],
"retraction_speed": [
"60"
],
"single_extruder_multi_material": "1",
"change_filament_gcode": "",
"wipe": [
"1"
],
"default_print_profile": "",
"machine_start_gcode": "G0 Z20 F9000\nG92 E0; G1 E-10 F1200\nG28\nM970 Q1 A10 B10 C130 K0\nM970 Q1 A10 B131 C250 K1\nM974 Q1 S1 P0\nM970 Q0 A10 B10 C130 H20 K0\nM970 Q0 A10 B131 C250 K1\nM974 Q0 S1 P0\nM220 S100 ;Reset Feedrate\nM221 S100 ;Reset Flowrate\nG29 ;Home\nG90;\nG92 E0 ;Reset Extruder \nG1 Z2.0 F3000 ;Move Z Axis up \nG1 X10.1 Y20 Z0.28 F5000.0 ;Move to start position\nM109 S205;\nG1 X10.1 Y200.0 Z0.28 F1500.0 E15 ;Draw the first line\nG1 X10.4 Y200.0 Z0.28 F5000.0 ;Move to side a little\nG1 X10.4 Y20 Z0.28 F1500.0 E30 ;Draw the second line\nG92 E0 ;Reset Extruder \nG1 X110 Y110 Z2.0 F3000 ;Move Z Axis up",
"machine_end_gcode": "M400 ; wait for buffer to clear\nG92 E0 ; zero the extruder\nG1 E-4.0 F3600; retract \nG91\nG1 Z3;\nM104 S0 ; turn off hotend\nM140 S0 ; turn off bed\nM106 S0 ; turn off fan\nG90 \nG0 X110 Y200 F3600 \nprint_end",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n",
"machine_pause_gcode": "M601"
}
@@ -0,0 +1,23 @@
{
"type": "process",
"name": "0.20mm Standard @IdeaFormer IR3 V2",
"inherits": "fdm_process_common",
"from": "system",
"setting_id": "91atcIwv5728phqX",
"instantiation": "true",
"layer_height": "0.2",
"initial_layer_print_height": "0.2",
"initial_layer_line_width": "0.42",
"wall_loops": "2",
"reduce_infill_retraction": "1",
"detect_overhang_wall": "1",
"skirt_loops": "0",
"skirt_distance": "0",
"sparse_infill_pattern": "grid",
"sparse_infill_speed": "200",
"support_base_pattern": "rectilinear",
"support_interface_pattern": "rectilinear",
"compatible_printers": [
"IdeaFormer IR3 V2 0.4 nozzle"
]
}
@@ -0,0 +1,106 @@
{
"type": "process",
"name": "fdm_process_common",
"from": "system",
"instantiation": "false",
"reduce_crossing_wall": "0",
"max_travel_detour_distance": "0",
"bottom_surface_pattern": "monotonic",
"bottom_shell_thickness": "0",
"bridge_speed": "50",
"brim_width": "5",
"brim_object_gap": "0.1",
"compatible_printers": [],
"compatible_printers_condition": "",
"print_sequence": "by layer",
"default_acceleration": "1000",
"initial_layer_acceleration": "500",
"top_surface_acceleration": "1000",
"travel_acceleration": "1000",
"inner_wall_acceleration": "1000",
"outer_wall_acceleration": "700",
"bridge_no_support": "0",
"draft_shield": "disabled",
"elefant_foot_compensation": "0",
"enable_arc_fitting": "0",
"wall_infill_order": "inner wall/outer wall/infill",
"infill_direction": "45",
"sparse_infill_density": "15%",
"sparse_infill_pattern": "crosshatch",
"initial_layer_print_height": "0.2",
"infill_combination": "0",
"infill_wall_overlap": "25%",
"interface_shells": "0",
"ironing_flow": "10%",
"ironing_spacing": "0.15",
"ironing_speed": "30",
"ironing_type": "no ironing",
"reduce_infill_retraction": "1",
"filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[initial_tool]}_{printer_model}_{print_time}.gcode",
"detect_overhang_wall": "1",
"slowdown_for_curled_perimeters": "1",
"overhang_1_4_speed": "0",
"overhang_2_4_speed": "50",
"overhang_3_4_speed": "30",
"overhang_4_4_speed": "10",
"line_width": "110%",
"inner_wall_line_width": "110%",
"outer_wall_line_width": "100%",
"top_surface_line_width": "93.75%",
"sparse_infill_line_width": "110%",
"initial_layer_line_width": "120%",
"internal_solid_infill_line_width": "120%",
"support_line_width": "96%",
"wall_loops": "3",
"print_settings_id": "",
"raft_layers": "0",
"seam_position": "aligned",
"skirt_distance": "2",
"skirt_height": "3",
"min_skirt_length": "4",
"skirt_loops": "0",
"minimum_sparse_infill_area": "15",
"spiral_mode": "0",
"standby_temperature_delta": "-5",
"enable_support": "0",
"resolution": "0.012",
"support_type": "normal(auto)",
"support_on_build_plate_only": "0",
"support_top_z_distance": "0.2",
"support_bottom_z_distance": "0.2",
"support_filament": "0",
"support_interface_loop_pattern": "0",
"support_interface_filament": "0",
"support_interface_top_layers": "2",
"support_interface_bottom_layers": "2",
"support_interface_spacing": "0.5",
"support_interface_speed": "80",
"support_base_pattern": "default",
"support_base_pattern_spacing": "2.5",
"support_speed": "150",
"support_threshold_angle": "30",
"support_object_xy_distance": "0.35",
"tree_support_branch_angle": "30",
"tree_support_wall_count": "0",
"detect_thin_wall": "0",
"top_surface_pattern": "monotonicline",
"top_shell_thickness": "0.8",
"enable_prime_tower": "1",
"wipe_tower_no_sparse_layers": "0",
"prime_tower_width": "60",
"xy_hole_compensation": "0",
"xy_contour_compensation": "0",
"layer_height": "0.2",
"bottom_shell_layers": "3",
"top_shell_layers": "4",
"bridge_flow": "1",
"initial_layer_speed": "45",
"initial_layer_infill_speed": "45",
"outer_wall_speed": "45",
"inner_wall_speed": "80",
"sparse_infill_speed": "150",
"internal_solid_infill_speed": "150",
"top_surface_speed": "50",
"gap_infill_speed": "30",
"travel_speed": "200"
}
+54
View File
@@ -0,0 +1,54 @@
{
"name": "Printcepts",
"version": "01.00.00.04",
"force_update": "0",
"description": "Printcepts belt printer configurations",
"machine_model_list": [
{
"name": "BabyBelt Pro",
"sub_path": "machine/BabyBelt Pro.json"
}
],
"process_list": [
{
"name": "fdm_process_common",
"sub_path": "process/fdm_process_common.json"
},
{
"name": "0.20mm Standard @BabyBelt Pro",
"sub_path": "process/0.20mm Standard @BabyBelt Pro.json"
}
],
"filament_list": [
{
"name": "Generic PLA @BabyBelt Pro",
"sub_path": "filament/Generic PLA @BabyBelt Pro.json"
},
{
"name": "eSUN PLA @BabyBelt Pro",
"sub_path": "filament/eSUN PLA @BabyBelt Pro.json"
},
{
"name": "Generic PETG @BabyBelt Pro",
"sub_path": "filament/Generic PETG @BabyBelt Pro.json"
}
],
"machine_list": [
{
"name": "fdm_machine_common",
"sub_path": "machine/fdm_machine_common.json"
},
{
"name": "fdm_klipper_common",
"sub_path": "machine/fdm_klipper_common.json"
},
{
"name": "fdm_belt_common",
"sub_path": "machine/fdm_belt_common.json"
},
{
"name": "BabyBelt Pro 0.4 nozzle",
"sub_path": "machine/BabyBelt Pro 0.4 nozzle.json"
}
]
}
@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="95.0mm" height="500.0mm" viewBox="0 0 95.0 500.0" preserveAspectRatio="xMidYMid meet">
<!-- Printcepts BabyBelt Pro bed texture: 95 x 500 mm belt plate. -->
<!-- Transparent plate; green (#195F30) BabyBelt Pro logo centered along X, near the bottom edge. -->
<rect x="0" y="0" width="95.0" height="500.0" fill="none"/>
<g transform="translate(14.2500,436.3488) scale(0.067538)">
<g transform="translate(-11.000000,692.938562) scale(0.100000,-0.100000)"
fill="#195F30" stroke="none">
<path d="M1963 5604 l-1423 -1324 0 -2050 0 -2050 443 0 c244 0 741 3 1105 7
l662 6 0 746 c-1 575 -4 768 -14 841 -47 324 -179 486 -473 581 -40 12 -73 26
-73 30 0 4 32 17 72 29 212 64 333 166 378 320 35 121 38 191 32 868 l-5 662
-629 0 c-395 0 -628 4 -628 10 0 5 635 601 1410 1325 776 724 1410 1318 1410
1321 0 2 -190 4 -422 3 l-423 0 -1422 -1325z m-334 -2029 c143 -16 174 -96
173 -446 -2 -411 -24 -458 -224 -462 l-93 -2 -3 450 c-1 248 0 456 3 463 3 9
18 12 47 8 24 -3 67 -8 97 -11z m-4 -1556 c160 -29 173 -62 182 -469 9 -455
-14 -593 -108 -641 -39 -19 -193 -44 -210 -33 -10 6 -13 1147 -3 1157 6 6 45
2 139 -14z"/>
<path d="M3464 5979 c-142 -132 -263 -245 -268 -250 -6 -5 69 -9 190 -9 l199
1 268 249 267 250 -198 0 -198 0 -260 -241z"/>
<path d="M3650 5649 c-135 -126 -254 -238 -265 -249 -19 -20 -18 -20 177 -20
l197 0 228 211 c125 116 246 229 268 250 l40 39 -200 -1 -200 0 -245 -230z"/>
<path d="M2537 5089 c-101 -24 -204 -105 -251 -197 -96 -190 -19 -420 172
-514 l67 -33 2670 0 2670 0 57 27 c74 34 146 107 184 183 43 88 43 230 0 322
-35 76 -113 153 -193 191 l-58 27 -2640 2 c-1513 0 -2656 -3 -2678 -8z m5063
-77 c-57 -37 -118 -111 -140 -168 -31 -82 -25 -206 12 -279 26 -49 93 -121
133 -143 15 -8 -640 -11 -2410 -11 l-2430 0 30 21 c200 146 201 425 1 569
l-39 29 2434 -1 c2263 0 2432 -1 2409 -17z m-4890 -43 c270 -122 185 -526
-109 -522 -257 2 -370 324 -170 485 74 60 194 76 279 37z m5201 -12 c94 -55
140 -135 140 -242 0 -285 -393 -374 -517 -117 -26 54 -30 162 -9 219 28 74 97
139 173 164 53 17 166 4 213 -24z"/>
<path d="M2917 3973 c-4 -174 -7 -550 -7 -835 l0 -518 326 0 326 0 -7 150 -7
150 110 0 110 0 11 -32 c5 -18 26 -86 46 -150 l36 -118 325 0 c179 0 323 4
320 9 -3 4 -155 374 -337 822 -182 448 -333 820 -336 827 -4 9 -105 12 -457
12 l-453 0 -6 -317z m773 -745 c0 -5 -47 -8 -104 -8 l-103 0 -7 92 c-3 50 -6
202 -5 337 l1 246 109 -330 c60 -181 109 -333 109 -337z"/>
<path d="M4680 3455 l0 -835 448 0 c693 1 885 16 985 80 99 63 126 132 134
340 12 327 -44 405 -342 476 -28 7 -27 8 25 19 199 42 255 95 267 248 11 146
-32 285 -110 353 -145 126 -329 153 -1049 154 l-358 0 0 -835z m846 520 c36
-23 44 -54 44 -162 0 -152 -29 -183 -170 -183 l-40 0 0 186 0 187 71 -6 c39
-3 82 -13 95 -22z m4 -625 c33 -18 40 -52 40 -208 0 -200 -9 -214 -143 -228
l-67 -7 0 233 0 233 74 -6 c41 -3 84 -10 96 -17z"/>
<path d="M6150 4286 c0 -3 131 -242 290 -531 l290 -526 0 -304 0 -305 385 0
385 0 0 299 0 299 305 533 305 534 -377 3 c-207 1 -381 -2 -385 -6 -16 -16
-110 -258 -172 -444 l-62 -187 -18 77 c-18 75 -139 450 -171 525 l-15 37 -380
0 c-209 0 -380 -2 -380 -4z"/>
<path d="M2910 1355 l0 -1185 830 0 830 0 0 240 0 240 -350 0 -350 0 0 255 0
255 300 0 300 0 0 230 0 230 -300 0 -300 0 0 220 0 220 320 0 320 0 0 240 0
240 -800 0 -800 0 0 -1185z"/>
<path d="M4680 1355 l0 -1185 775 0 775 0 0 240 0 240 -295 0 -295 0 0 945 0
945 -480 0 -480 0 0 -1185z"/>
<path d="M5800 2300 l0 -240 280 0 280 0 0 -945 0 -945 480 0 480 0 0 945 0
945 285 0 285 0 0 240 0 240 -1045 0 -1045 0 0 -240z"/>
<path d="M8032 1358 l-2 -1188 1008 1 c621 1 971 5 912 10 -309 27 -631 139
-885 306 -593 391 -984 1122 -1025 1918 -4 77 -8 -394 -8 -1047z"/>
<path d="M7441 1934 c-43 -36 -59 -70 -70 -148 -18 -124 16 -252 76 -291 32
-21 226 -33 328 -20 114 14 161 97 153 269 -5 96 -24 151 -68 191 -20 18 -39
20 -205 23 l-182 3 -32 -27z m389 -199 c7 -8 10 -22 6 -30 -4 -13 -34 -15
-186 -15 -189 0 -202 3 -186 45 8 22 348 22 366 0z"/>
<path d="M7450 1267 c-14 -6 -35 -32 -47 -57 -21 -41 -23 -58 -23 -222 l0
-178 270 0 270 0 0 105 0 105 -121 0 -120 0 3 28 3 27 118 3 117 3 0 99 0 100
-113 0 c-121 0 -138 -7 -162 -65 -8 -19 -9 -19 -12 2 -10 55 -116 84 -183 50z
m134 -203 c15 -38 8 -44 -54 -44 -62 0 -69 6 -54 44 9 23 99 23 108 0z"/>
<path d="M466 1193 l-29 -43 -163 0 -164 0 0 -235 0 -235 165 0 165 0 27 -42
28 -42 3 163 c1 89 1 233 0 319 l-3 157 -29 -42z"/>
<path d="M7443 620 c-48 -20 -58 -60 -61 -262 l-4 -188 271 0 271 0 0 110 0
110 -110 0 -110 0 0 70 c0 76 -21 145 -51 160 -22 12 -176 12 -206 0z m161
-186 c15 -39 8 -44 -64 -44 -72 0 -79 5 -64 44 9 23 119 23 128 0z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

@@ -0,0 +1,77 @@
{
"type": "filament",
"name": "Generic PETG @BabyBelt Pro",
"inherits": "Generic PETG @System",
"from": "system",
"setting_id": "gCzHpDNgVwQR6tgk",
"instantiation": "true",
"filament_extruder_variant": [
"Direct Drive Standard"
],
"compatible_printers": [
"BabyBelt Pro 0.4 nozzle"
],
"filament_type": [
"PETG"
],
"filament_vendor": [
"Generic"
],
"filament_settings_id": [
"Generic PETG @BabyBelt Pro"
],
"filament_flow_ratio": [
"0.95"
],
"filament_cost": [
"25"
],
"nozzle_temperature": [
"240"
],
"nozzle_temperature_initial_layer": [
"245"
],
"cool_plate_temp": [
"80"
],
"cool_plate_temp_initial_layer": [
"80"
],
"fan_min_speed": [
"40"
],
"fan_max_speed": [
"60"
],
"overhang_fan_threshold": [
"25%"
],
"overhang_fan_speed": [
"80"
],
"full_fan_speed_layer": [
"8"
],
"slow_down_min_speed": [
"20"
],
"slow_down_layer_time": [
"4"
],
"fan_cooling_layer_time": [
"100"
],
"filament_retraction_length": [
"2"
],
"filament_retraction_speed": [
"40"
],
"filament_deretraction_speed": [
"40"
],
"filament_start_gcode": [
"; Generic PETG @BabyBelt Pro — belt PETG, bed 80C"
]
}
@@ -0,0 +1,65 @@
{
"type": "filament",
"name": "Generic PLA @BabyBelt Pro",
"inherits": "Generic PLA @System",
"from": "system",
"setting_id": "24PpcnhVx9v5f4fD",
"instantiation": "true",
"filament_extruder_variant": [
"Direct Drive Standard"
],
"compatible_printers": [
"BabyBelt Pro 0.4 nozzle"
],
"filament_type": [
"PLA"
],
"filament_vendor": [
"Generic"
],
"filament_settings_id": [
"Generic PLA @BabyBelt Pro"
],
"nozzle_temperature": [
"215"
],
"hot_plate_temp": [
"75"
],
"hot_plate_temp_initial_layer": [
"75"
],
"cool_plate_temp": [
"75"
],
"cool_plate_temp_initial_layer": [
"75"
],
"textured_plate_temp": [
"75"
],
"textured_plate_temp_initial_layer": [
"75"
],
"close_fan_the_first_x_layers": [
"3"
],
"full_fan_speed_layer": [
"8"
],
"slow_down_min_speed": [
"20"
],
"filament_retraction_length": [
"1.5"
],
"filament_retraction_speed": [
"35"
],
"filament_deretraction_speed": [
"30"
],
"filament_start_gcode": [
"; Generic PLA @BabyBelt Pro — belt PLA, bed 75C"
]
}
@@ -0,0 +1,36 @@
{
"type": "filament",
"name": "eSUN PLA @BabyBelt Pro",
"inherits": "Generic PLA @BabyBelt Pro",
"from": "system",
"setting_id": "EH3X7oE0DU5tSpjW",
"filament_id": "OFkrxQC4",
"instantiation": "true",
"compatible_printers": [
"BabyBelt Pro 0.4 nozzle"
],
"filament_type": [
"PLA"
],
"filament_vendor": [
"eSUN"
],
"filament_settings_id": [
"eSUN PLA @BabyBelt Pro"
],
"nozzle_temperature_initial_layer": [
"200"
],
"nozzle_temperature": [
"200"
],
"enable_pressure_advance": [
"1"
],
"pressure_advance": [
"0.12"
],
"filament_max_volumetric_speed": [
"20"
]
}
@@ -0,0 +1,88 @@
{
"type": "machine",
"name": "BabyBelt Pro 0.4 nozzle",
"inherits": "fdm_belt_common",
"from": "system",
"setting_id": "34OWINlJpJgA9DwQ",
"instantiation": "true",
"printer_model": "BabyBelt Pro",
"printer_variant": "0.4",
"nozzle_diameter": [
"0.4"
],
"default_filament_profile": [
"Generic PLA @BabyBelt Pro"
],
"default_print_profile": "0.20mm Standard @BabyBelt Pro",
"printable_area": [
"0x0",
"95x0",
"95x500",
"0x500"
],
"printable_height": "100",
"best_object_pos": "0.5,0.05",
"nozzle_type": [
"hardened_steel"
],
"printer_extruder_id": [
"1"
],
"printer_extruder_variant": [
"Direct Drive Standard"
],
"thumbnails": [
"48x48/PNG",
"300x300/PNG"
],
"machine_max_acceleration_e": [
"500",
"5000"
],
"machine_max_acceleration_extruding": [
"500",
"20000"
],
"machine_max_acceleration_retracting": [
"500",
"5000"
],
"machine_max_acceleration_x": [
"500",
"20000"
],
"machine_max_acceleration_y": [
"500",
"20000"
],
"machine_max_junction_deviation": [
"0.01",
"0.01"
],
"machine_max_speed_x": [
"50",
"200"
],
"machine_max_speed_y": [
"50",
"200"
],
"machine_max_speed_z": [
"5",
"12"
],
"retraction_length": [
"1.5"
],
"retraction_speed": [
"20"
],
"deretraction_speed": [
"25"
],
"retract_lift_enforce": [
"Top and Bottom"
],
"support_chamber_temp_control": "0",
"machine_start_gcode": ";Start GCode\nPRINT_START ANGLE=[belt_slice_rotation_angle] EXTRUDER=[nozzle_temperature_initial_layer] BED=[hot_plate_temp_initial_layer] MATERIAL=[filament_type]\n"
}
@@ -0,0 +1,12 @@
{
"type": "machine_model",
"name": "BabyBelt Pro",
"model_id": "Printcepts_BabyBelt_Pro",
"nozzle_diameter": "0.4",
"machine_tech": "FFF",
"family": "Printcepts",
"bed_model": "",
"bed_texture": "BabyBelt Pro_bed_texture.svg",
"hotend_model": "",
"default_materials": "Generic PLA @BabyBelt Pro;Generic PETG @BabyBelt Pro"
}
@@ -0,0 +1,99 @@
{
"type": "machine",
"name": "fdm_belt_common",
"inherits": "fdm_klipper_common",
"from": "system",
"instantiation": "false",
"gcode_flavor": "klipper",
"single_extruder_multi_material": "0",
"default_filament_profile": [
"Generic PLA @System"
],
"default_print_profile": "0.20mm Standard @BabyBelt Pro",
"max_layer_height": [
"0.32"
],
"min_layer_height": [
"0.08"
],
"deretraction_speed": [
"30"
],
"extruder_colour": [
"#FCE94F"
],
"extruder_offset": [
"0x0"
],
"long_retractions_when_cut": [
"0"
],
"nozzle_diameter": [
"0.4"
],
"retract_before_wipe": [
"70%"
],
"retract_length_toolchange": [
"2"
],
"retract_lift_above": [
"0"
],
"retract_lift_below": [
"0"
],
"retract_lift_enforce": [
"All Surfaces"
],
"retract_restart_extra": [
"0"
],
"retract_restart_extra_toolchange": [
"0"
],
"retract_when_changing_layer": [
"1"
],
"retraction_distances_when_cut": [
"18"
],
"retraction_length": [
"0.8"
],
"retraction_minimum_travel": [
"1"
],
"retraction_speed": [
"30"
],
"travel_slope": [
"3"
],
"wipe": [
"1"
],
"wipe_distance": [
"1"
],
"z_hop": [
"0"
],
"z_hop_types": [
"Normal Lift"
],
"gcode_remap_x": "rev_x",
"gcode_remap_y": "pos_z",
"gcode_remap_z": "pos_y",
"printer_extruder_id": [
"1"
],
"belt_printer": "1",
"belt_slice_rotation": "x",
"belt_slice_rotation_angle": "45",
"belt_slice_rotation_global": "1",
"build_plate_tilt_x": "45",
"purge_in_prime_tower": "0",
"scan_first_layer": "0",
"auxiliary_fan": "0"
}
@@ -0,0 +1,140 @@
{
"type": "machine",
"name": "fdm_klipper_common",
"inherits": "fdm_machine_common",
"from": "system",
"instantiation": "false",
"gcode_flavor": "klipper",
"machine_max_acceleration_e": [
"5000",
"5000"
],
"machine_max_acceleration_extruding": [
"20000",
"20000"
],
"machine_max_acceleration_retracting": [
"5000",
"5000"
],
"machine_max_acceleration_travel": [
"20000",
"20000"
],
"machine_max_acceleration_x": [
"20000",
"20000"
],
"machine_max_acceleration_y": [
"20000",
"20000"
],
"machine_max_acceleration_z": [
"500",
"200"
],
"machine_max_speed_e": [
"25",
"25"
],
"machine_max_speed_x": [
"500",
"200"
],
"machine_max_speed_y": [
"500",
"200"
],
"machine_max_speed_z": [
"12",
"12"
],
"machine_max_jerk_e": [
"2.5",
"2.5"
],
"machine_max_jerk_x": [
"9",
"9"
],
"machine_max_jerk_y": [
"9",
"9"
],
"machine_max_jerk_z": [
"0.2",
"0.4"
],
"machine_min_extruding_rate": [
"0",
"0"
],
"machine_min_travel_rate": [
"0",
"0"
],
"max_layer_height": [
"0.32"
],
"min_layer_height": [
"0.08"
],
"printable_height": "250",
"extruder_clearance_radius": "65",
"extruder_clearance_height_to_rod": "36",
"extruder_clearance_height_to_lid": "140",
"printer_settings_id": "",
"printer_technology": "FFF",
"printer_variant": "0.4",
"retraction_minimum_travel": [
"1"
],
"retract_before_wipe": [
"70%"
],
"retract_when_changing_layer": [
"1"
],
"retraction_length": [
"0.8"
],
"retract_length_toolchange": [
"2"
],
"z_hop": [
"0.4"
],
"retract_restart_extra": [
"0"
],
"retract_restart_extra_toolchange": [
"0"
],
"retraction_speed": [
"30"
],
"deretraction_speed": [
"30"
],
"z_hop_types": "Normal Lift",
"single_extruder_multi_material": "1",
"change_filament_gcode": "",
"wipe": [
"1"
],
"default_filament_profile": [
"Generic PLA @System"
],
"default_print_profile": "0.20mm Standard @MyKlipper",
"bed_exclude_area": [
"0x0"
],
"machine_start_gcode": "M190 S[bed_temperature_initial_layer_single]\nM109 S[nozzle_temperature_initial_layer]\nPRINT_START EXTRUDER=[nozzle_temperature_initial_layer] BED=[bed_temperature_initial_layer_single]\n",
"machine_end_gcode": "PRINT_END",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n",
"machine_pause_gcode": "PAUSE",
"scan_first_layer": "0",
"nozzle_type": "undefine",
"auxiliary_fan": "0"
}
@@ -0,0 +1,118 @@
{
"type": "machine",
"name": "fdm_machine_common",
"from": "system",
"instantiation": "false",
"printer_technology": "FFF",
"deretraction_speed": [
"40"
],
"extruder_colour": [
"#FCE94F"
],
"extruder_offset": [
"0x0"
],
"gcode_flavor": "marlin",
"machine_max_acceleration_e": [
"5000"
],
"machine_max_acceleration_extruding": [
"10000"
],
"machine_max_acceleration_retracting": [
"1000"
],
"machine_max_acceleration_x": [
"10000"
],
"machine_max_acceleration_y": [
"10000"
],
"machine_max_acceleration_z": [
"500"
],
"machine_max_speed_e": [
"60"
],
"machine_max_speed_x": [
"500"
],
"machine_max_speed_y": [
"500"
],
"machine_max_speed_z": [
"10"
],
"machine_max_jerk_e": [
"5"
],
"machine_max_jerk_x": [
"8"
],
"machine_max_jerk_y": [
"8"
],
"machine_max_jerk_z": [
"0.4"
],
"machine_min_extruding_rate": [
"0"
],
"machine_min_travel_rate": [
"0"
],
"max_layer_height": [
"0.32"
],
"min_layer_height": [
"0.08"
],
"printable_height": "250",
"extruder_clearance_radius": "65",
"extruder_clearance_height_to_rod": "36",
"extruder_clearance_height_to_lid": "140",
"nozzle_diameter": [
"0.4"
],
"printer_settings_id": "",
"printer_variant": "0.4",
"retraction_minimum_travel": [
"2"
],
"retract_before_wipe": [
"70%"
],
"retract_when_changing_layer": [
"1"
],
"retraction_length": [
"1"
],
"retract_length_toolchange": [
"1"
],
"z_hop": [
"0"
],
"retract_restart_extra": [
"0"
],
"retract_restart_extra_toolchange": [
"0"
],
"retraction_speed": [
"60"
],
"single_extruder_multi_material": "1",
"change_filament_gcode": "",
"wipe": [
"1"
],
"default_print_profile": "",
"machine_start_gcode": "G0 Z20 F9000\nG92 E0; G1 E-10 F1200\nG28\nM970 Q1 A10 B10 C130 K0\nM970 Q1 A10 B131 C250 K1\nM974 Q1 S1 P0\nM970 Q0 A10 B10 C130 H20 K0\nM970 Q0 A10 B131 C250 K1\nM974 Q0 S1 P0\nM220 S100 ;Reset Feedrate\nM221 S100 ;Reset Flowrate\nG29 ;Home\nG90;\nG92 E0 ;Reset Extruder \nG1 Z2.0 F3000 ;Move Z Axis up \nG1 X10.1 Y20 Z0.28 F5000.0 ;Move to start position\nM109 S205;\nG1 X10.1 Y200.0 Z0.28 F1500.0 E15 ;Draw the first line\nG1 X10.4 Y200.0 Z0.28 F5000.0 ;Move to side a little\nG1 X10.4 Y20 Z0.28 F1500.0 E30 ;Draw the second line\nG92 E0 ;Reset Extruder \nG1 X110 Y110 Z2.0 F3000 ;Move Z Axis up",
"machine_end_gcode": "M400 ; wait for buffer to clear\nG92 E0 ; zero the extruder\nG1 E-4.0 F3600; retract \nG91\nG1 Z3;\nM104 S0 ; turn off hotend\nM140 S0 ; turn off bed\nM106 S0 ; turn off fan\nG90 \nG0 X110 Y200 F3600 \nprint_end",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n",
"machine_pause_gcode": "M601"
}
@@ -0,0 +1,23 @@
{
"type": "process",
"name": "0.20mm Standard @BabyBelt Pro",
"inherits": "fdm_process_common",
"from": "system",
"setting_id": "JGfGtqX6CWjCt437",
"instantiation": "true",
"layer_height": "0.2",
"initial_layer_print_height": "0.2",
"initial_layer_line_width": "0.42",
"wall_loops": "2",
"reduce_infill_retraction": "1",
"detect_overhang_wall": "1",
"skirt_loops": "0",
"skirt_distance": "0",
"sparse_infill_pattern": "grid",
"sparse_infill_speed": "200",
"support_base_pattern": "rectilinear",
"support_interface_pattern": "rectilinear",
"compatible_printers": [
"BabyBelt Pro 0.4 nozzle"
]
}
@@ -0,0 +1,106 @@
{
"type": "process",
"name": "fdm_process_common",
"from": "system",
"instantiation": "false",
"reduce_crossing_wall": "0",
"max_travel_detour_distance": "0",
"bottom_surface_pattern": "monotonic",
"bottom_shell_thickness": "0",
"bridge_speed": "50",
"brim_width": "5",
"brim_object_gap": "0.1",
"compatible_printers": [],
"compatible_printers_condition": "",
"print_sequence": "by layer",
"default_acceleration": "1000",
"initial_layer_acceleration": "500",
"top_surface_acceleration": "1000",
"travel_acceleration": "1000",
"inner_wall_acceleration": "1000",
"outer_wall_acceleration": "700",
"bridge_no_support": "0",
"draft_shield": "disabled",
"elefant_foot_compensation": "0",
"enable_arc_fitting": "0",
"wall_infill_order": "inner wall/outer wall/infill",
"infill_direction": "45",
"sparse_infill_density": "15%",
"sparse_infill_pattern": "crosshatch",
"initial_layer_print_height": "0.2",
"infill_combination": "0",
"infill_wall_overlap": "25%",
"interface_shells": "0",
"ironing_flow": "10%",
"ironing_spacing": "0.15",
"ironing_speed": "30",
"ironing_type": "no ironing",
"reduce_infill_retraction": "1",
"filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[initial_tool]}_{printer_model}_{print_time}.gcode",
"detect_overhang_wall": "1",
"slowdown_for_curled_perimeters": "1",
"overhang_1_4_speed": "0",
"overhang_2_4_speed": "50",
"overhang_3_4_speed": "30",
"overhang_4_4_speed": "10",
"line_width": "110%",
"inner_wall_line_width": "110%",
"outer_wall_line_width": "100%",
"top_surface_line_width": "93.75%",
"sparse_infill_line_width": "110%",
"initial_layer_line_width": "120%",
"internal_solid_infill_line_width": "120%",
"support_line_width": "96%",
"wall_loops": "3",
"print_settings_id": "",
"raft_layers": "0",
"seam_position": "aligned",
"skirt_distance": "2",
"skirt_height": "3",
"min_skirt_length": "4",
"skirt_loops": "0",
"minimum_sparse_infill_area": "15",
"spiral_mode": "0",
"standby_temperature_delta": "-5",
"enable_support": "0",
"resolution": "0.012",
"support_type": "normal(auto)",
"support_on_build_plate_only": "0",
"support_top_z_distance": "0.2",
"support_bottom_z_distance": "0.2",
"support_filament": "0",
"support_interface_loop_pattern": "0",
"support_interface_filament": "0",
"support_interface_top_layers": "2",
"support_interface_bottom_layers": "2",
"support_interface_spacing": "0.5",
"support_interface_speed": "80",
"support_base_pattern": "default",
"support_base_pattern_spacing": "2.5",
"support_speed": "150",
"support_threshold_angle": "30",
"support_object_xy_distance": "0.35",
"tree_support_branch_angle": "30",
"tree_support_wall_count": "0",
"detect_thin_wall": "0",
"top_surface_pattern": "monotonicline",
"top_shell_thickness": "0.8",
"enable_prime_tower": "1",
"wipe_tower_no_sparse_layers": "0",
"prime_tower_width": "60",
"xy_hole_compensation": "0",
"xy_contour_compensation": "0",
"layer_height": "0.2",
"bottom_shell_layers": "3",
"top_shell_layers": "4",
"bridge_flow": "1",
"initial_layer_speed": "45",
"initial_layer_infill_speed": "45",
"outer_wall_speed": "45",
"inner_wall_speed": "80",
"sparse_infill_speed": "150",
"internal_solid_infill_speed": "150",
"top_surface_speed": "50",
"gap_infill_speed": "30",
"travel_speed": "200"
}
+1
View File
@@ -26,6 +26,7 @@ struct SlopeDetection
bool actived;
float normal_z;
mat3 volume_world_normal_matrix;
vec3 up_direction;
};
uniform vec4 uniform_color;
+3 -2
View File
@@ -23,6 +23,7 @@ struct SlopeDetection
bool actived;
float normal_z;
mat3 volume_world_normal_matrix;
vec3 up_direction;
};
uniform mat4 view_model_matrix;
@@ -77,8 +78,8 @@ void main()
// Point in homogenous coordinates.
world_pos = volume_world_matrix * vec4(v_position, 1.0);
// z component of normal vector in world coordinate used for slope shading
world_normal_z = slope.actived ? (normalize(slope.volume_world_normal_matrix * v_normal)).z : 0.0;
// dot product of world normal with up direction, used for slope shading
world_normal_z = slope.actived ? dot(normalize(slope.volume_world_normal_matrix * v_normal), slope.up_direction) : 0.0;
gl_Position = projection_matrix * position;
if (is_outline) {
+2 -1
View File
@@ -37,6 +37,7 @@ struct SlopeDetection
bool actived;
float normal_z;
mat3 volume_world_normal_matrix;
vec3 up_direction;
};
uniform SlopeDetection slope;
@@ -85,7 +86,7 @@ void main()
color = LightBlue;
alpha = 1.0;
}
else if( transformed_normal.z < slope.normal_z - EPSILON)
else if( dot(transformed_normal, slope.up_direction) < slope.normal_z - EPSILON)
{
color = color * 0.5 + LightRed * 0.5;
alpha = 1.0;
+1
View File
@@ -24,6 +24,7 @@ struct SlopeDetection
bool actived;
float normal_z;
mat3 volume_world_normal_matrix;
vec3 up_direction;
};
uniform SlopeDetection slope;
void main()
+1
View File
@@ -29,6 +29,7 @@ struct SlopeDetection
bool actived;
float normal_z;
mat3 volume_world_normal_matrix;
vec3 up_direction;
};
uniform vec4 uniform_color;
+3 -2
View File
@@ -23,6 +23,7 @@ struct SlopeDetection
bool actived;
float normal_z;
mat3 volume_world_normal_matrix;
vec3 up_direction;
};
uniform mat4 view_model_matrix;
@@ -77,8 +78,8 @@ void main()
// Point in homogenous coordinates.
world_pos = volume_world_matrix * vec4(v_position, 1.0);
// z component of normal vector in world coordinate used for slope shading
world_normal_z = slope.actived ? (normalize(slope.volume_world_normal_matrix * v_normal)).z : 0.0;
// dot product of world normal with up direction, used for slope shading
world_normal_z = slope.actived ? dot(normalize(slope.volume_world_normal_matrix * v_normal), slope.up_direction) : 0.0;
gl_Position = projection_matrix * position;
if (is_outline) {
+2 -1
View File
@@ -37,6 +37,7 @@ struct SlopeDetection
bool actived;
float normal_z;
mat3 volume_world_normal_matrix;
vec3 up_direction;
};
uniform SlopeDetection slope;
@@ -87,7 +88,7 @@ void main()
color = LightBlue;
alpha = 1.0;
}
else if( transformed_normal.z < slope.normal_z - EPSILON)
else if( dot(transformed_normal, slope.up_direction) < slope.normal_z - EPSILON)
{
color = color * 0.5 + LightRed * 0.5;
alpha = 1.0;
+1
View File
@@ -24,6 +24,7 @@ struct SlopeDetection
bool actived;
float normal_z;
mat3 volume_world_normal_matrix;
vec3 up_direction;
};
uniform SlopeDetection slope;
void main()
+7 -1
View File
@@ -91,6 +91,12 @@ if (SLIC3R_GUI)
# list(REMOVE_ITEM wxWidgets_LIBRARIES oleacc)
find_package(wxInspector REQUIRED)
# wxInspector 1.0.0 installs its headers but accidentally declares the
# INSTALL_INTERFACE include directory PRIVATE, so its imported target does
# not expose them to consumers. Restore the package prefix include path until
# the upstream export is fixed.
get_filename_component(WXINSPECTOR_PREFIX "${wxInspector_DIR}/../../.." ABSOLUTE)
target_include_directories(wxInspector::wxInspector INTERFACE "${WXINSPECTOR_PREFIX}/include")
# wxInspector's exported interface names the release wxWidgets import
# libraries, which a Debug build cannot link. wx is linked above instead.
@@ -186,7 +192,7 @@ endif ()
# Add the Slic3r GUI library, libcurl, OpenGL and GLU libraries.
if (SLIC3R_GUI)
# target_link_libraries(OrcaSlicer ws2_32 uxtheme setupapi libslic3r_gui ${wxWidgets_LIBRARIES})
target_link_libraries(OrcaSlicer libslic3r_gui)
target_link_libraries(OrcaSlicer libslic3r_gui wxInspector::wxInspector)
if (MSVC)
# Generate debug symbols even in release mode.
target_link_options(OrcaSlicer PUBLIC "$<$<CONFIG:RELEASE>:/DEBUG>")
+46 -25
View File
@@ -141,18 +141,6 @@ using namespace nlohmann;
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GuiColor.hpp"
#include <GLFW/glfw3.h>
#include "libslic3r/AppConfig.hpp"
#include "libslic3r/BuildVolume.hpp"
#include "libslic3r/ExPolygon.hpp"
#include "libslic3r/Exception.hpp"
#include "libslic3r/GCode/GCodeProcessor.hpp"
#include "libslic3r/GCode/ToolOrdering.hpp"
#include "libslic3r/ObjectID.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/ProjectTask.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/Jobs/SendJob.hpp"
namespace fs = boost::filesystem;
@@ -3432,9 +3420,14 @@ int CLI::run(int argc, char **argv)
max_self_index = std::max(max_self_index, v);
min_self_index = std::min(min_self_index, v);
}
if (max_self_index > filament_count || min_self_index < 1) {
BOOST_LOG_TRIVIAL(warning) << boost::format("filament_self_index range [%1%, %2%] is invalid for filament_count %3%, regenerating")
% min_self_index % max_self_index % filament_count;
// And a project saved with FEWER filaments than are now loaded (a
// one-filament project sliced with two --load-filaments) leaves the tables half filled:
// the variant matching below then reads past filament_extruder_variant and
// set_with_restore_2 throws an uncaught size error. Regenerate in that case too.
if (max_self_index > filament_count || min_self_index < 1 || max_self_index < filament_count
|| (int) filament_self_index_opt->values.size() < filament_count) {
BOOST_LOG_TRIVIAL(warning) << boost::format("filament_self_index range [%1%, %2%] (size %4%) is invalid for filament_count %3%, regenerating")
% min_self_index % max_self_index % filament_count % filament_self_index_opt->values.size();
need_regenerate_self_index = true;
}
}
@@ -3525,6 +3518,10 @@ int CLI::run(int argc, char **argv)
std::vector<string>& filament_variants = curr_variant_opt->values;
filament_variants.resize(filament_count, get_extruder_variant_string(etDirectDrive, nvtStandard));
}
// See the filament_self_index note above: one variant per filament for
// the filaments the project did not know about.
if ((int) curr_variant_opt->values.size() < filament_count)
curr_variant_opt->values.resize(filament_count, get_extruder_variant_string(etDirectDrive, nvtStandard));
const ConfigOptionStrings *new_variant_opt = dynamic_cast<const ConfigOptionStrings*>(config.option("filament_extruder_variant", true));
std::vector<int> new_variant_indice;
@@ -3533,7 +3530,7 @@ int CLI::run(int argc, char **argv)
for (int i = 0; i < new_variant_count; i++)
{
for (int j = old_start_indice[filament_index - 1]; j < old_start_indice[filament_index - 1] + old_variant_count; j++)
for (int j = old_start_indice[filament_index - 1]; j < old_start_indice[filament_index - 1] + old_variant_count && j < (int) curr_variant_opt->values.size(); j++)
{
if (curr_variant_opt->values[j] == new_variant_opt->values[i]) {
new_variant_indice[i] = j;
@@ -3585,7 +3582,18 @@ int CLI::run(int argc, char **argv)
ConfigOptionVectorBase* opt_vec_dst = static_cast<ConfigOptionVectorBase*>(opt);
const ConfigOptionVectorBase* opt_vec_src = static_cast<const ConfigOptionVectorBase*>(source_opt);
//set with index
opt_vec_dst->set_with_restore_2(opt_vec_src, new_variant_indice, old_start_indice[filament_index - 1], old_variant_count);
try {
// A project with fewer filaments than are loaded: grow the
// destination to the filament's slot first (set_with_restore_2 only restores).
if (opt_vec_src->size() > 0 && opt_vec_dst->size() < size_t(old_start_indice[filament_index - 1] + old_variant_count))
opt_vec_dst->resize(size_t(old_start_indice[filament_index - 1] + old_variant_count), opt_vec_src);
opt_vec_dst->set_with_restore_2(opt_vec_src, new_variant_indice, old_start_indice[filament_index - 1], old_variant_count);
} catch (const std::exception &ex) { // Was an uncaught abort
BOOST_LOG_TRIVIAL(error) << boost::format("filament %1%: option %2% could not be applied: %3%") % filament_index % opt_key % ex.what();
boost::nowide::cerr << "filament " << filament_index << ": option " << opt_key << " could not be applied: " << ex.what() << std::endl;
record_exit_reson(outfile_dir, CLI_CONFIG_FILE_ERROR, 0, cli_errors[CLI_CONFIG_FILE_ERROR], sliced_info);
flush_and_exit(CLI_CONFIG_FILE_ERROR);
}
}
continue;
@@ -3631,7 +3639,16 @@ int CLI::run(int argc, char **argv)
if (filament_options_with_variant.find(opt_key) != filament_options_with_variant.end()) {
std::vector<int> temp_variant_indice;
temp_variant_indice.resize(new_variant_count, -1);
opt_vec_dst->set_with_restore_2(opt_vec_src, temp_variant_indice, old_start_indice[filament_index - 1], old_variant_count, true);
try {
if (opt_vec_src->size() > 0 && opt_vec_dst->size() < size_t(old_start_indice[filament_index - 1] + old_variant_count)) // See above
opt_vec_dst->resize(size_t(old_start_indice[filament_index - 1] + old_variant_count), opt_vec_src);
opt_vec_dst->set_with_restore_2(opt_vec_src, temp_variant_indice, old_start_indice[filament_index - 1], old_variant_count, true);
} catch (const std::exception &ex) { // Was an uncaught abort
BOOST_LOG_TRIVIAL(error) << boost::format("filament %1%: option %2% could not be applied: %3%") % filament_index % opt_key % ex.what();
boost::nowide::cerr << "filament " << filament_index << ": option " << opt_key << " could not be applied: " << ex.what() << std::endl;
record_exit_reson(outfile_dir, CLI_CONFIG_FILE_ERROR, 0, cli_errors[CLI_CONFIG_FILE_ERROR], sliced_info);
flush_and_exit(CLI_CONFIG_FILE_ERROR);
}
if (opt_key == "filament_extruder_variant")
new_variant_counts[filament_index - 1] = opt_vec_src->size();
@@ -4137,6 +4154,10 @@ int CLI::run(int argc, char **argv)
BOOST_LOG_TRIVIAL(info) << boost::format("%1%, set disable_wipe_tower_after_mapping back to false due to wrapping detect")%__LINE__;
}
// Belt printers never get the classic wipe tower (see Print::has_wipe_tower()), so reserve no space for it.
const ConfigOptionBool* belt_printer_opt = m_print_config.option<ConfigOptionBool>("belt_printer");
const bool is_belt_printer = belt_printer_opt && belt_printer_opt->value;
auto timelapse_type_opt = m_print_config.option("timelapse_type");
bool is_smooth_timelapse = false;
if (enable_timelapse && timelapse_type_opt && (timelapse_type_opt->getInt() == TimelapseType::tlSmooth))
@@ -4374,11 +4395,11 @@ int CLI::run(int argc, char **argv)
}
};
auto check_plate_wipe_tower = [get_print_sequence, is_smooth_timelapse](Slic3r::GUI::PartPlate* plate, int plate_index, DynamicPrintConfig& print_config, plate_obj_size_info_t &plate_obj_size_info) {
auto check_plate_wipe_tower = [get_print_sequence, is_smooth_timelapse, is_belt_printer](Slic3r::GUI::PartPlate* plate, int plate_index, DynamicPrintConfig& print_config, plate_obj_size_info_t &plate_obj_size_info) {
plate_obj_size_info.obj_bbox= plate->get_objects_bounding_box();
BOOST_LOG_TRIVIAL(info) << boost::format("plate %1%, object bbox: min {%2%, %3%, %4%} - max {%5%, %6%, %7%}")
%(plate_index+1) %plate_obj_size_info.obj_bbox.min.x() % plate_obj_size_info.obj_bbox.min.y() % plate_obj_size_info.obj_bbox.min.z() %plate_obj_size_info.obj_bbox.max.x() % plate_obj_size_info.obj_bbox.max.y() % plate_obj_size_info.obj_bbox.max.z();
if (!print_config.has("wipe_tower_x")) {
if (is_belt_printer || !print_config.has("wipe_tower_x")) {
plate_obj_size_info.has_wipe_tower = false;
BOOST_LOG_TRIVIAL(info) << boost::format("can not found wipe_tower_x in config, set to no wipe tower");
return;
@@ -5248,7 +5269,7 @@ int CLI::run(int argc, char **argv)
}
}
if ((!arrange_cfg.is_seq_print && (assemble_plate.filaments_count > 1))||(enable_wrapping_detect && !current_wrapping_exclude_area.empty()))
if (!is_belt_printer && ((!arrange_cfg.is_seq_print && (assemble_plate.filaments_count > 1)) || (enable_wrapping_detect && !current_wrapping_exclude_area.empty())))
{
//prepare the wipe tower
int plate_count = partplate_list.get_plate_count();
@@ -5400,7 +5421,7 @@ int CLI::run(int argc, char **argv)
bool is_seq_print = false;
get_print_sequence(cur_plate, m_print_config, is_seq_print);
if (!is_seq_print && (assemble_plate.filaments_count > 1) && !has_wipe_tower_position)
if (!is_belt_printer && !is_seq_print && (assemble_plate.filaments_count > 1) && !has_wipe_tower_position)
{
//prepare the wipe tower
auto printer_structure_opt = m_print_config.option<ConfigOptionEnum<PrinterStructure>>("printer_structure");
@@ -5568,7 +5589,7 @@ int CLI::run(int argc, char **argv)
};
const int max_filament_count = plate_count > 0 ? *std::max_element(plate_filament_counts.begin(), plate_filament_counts.end()) : 0;
if (plate_needs_wipe_tower(max_filament_count))
if (!is_belt_printer && plate_needs_wipe_tower(max_filament_count))
{
//prepare the wipe tower
auto printer_structure_opt = m_print_config.option<ConfigOptionEnum<PrinterStructure>>("printer_structure");
@@ -5675,7 +5696,7 @@ int CLI::run(int argc, char **argv)
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": found single object mode");
}
if (m_print_config.has("wipe_tower_x") && (is_smooth_timelapse || !arrange_cfg.is_seq_print || (selected.size() <= 1))) {
if (!is_belt_printer && m_print_config.has("wipe_tower_x") && (is_smooth_timelapse || !arrange_cfg.is_seq_print || (selected.size() <= 1))) {
float x;
float y;
if (duplicate_count > 0) {
@@ -6300,7 +6321,7 @@ int CLI::run(int argc, char **argv)
// The stored (or default) tower position may not fit the tower these plates
// need, and no CLI placement site runs on a plain slice - mirror the GUI's
// reload clamp and fit every plate's tower into the printable area first.
if (m_print_config.option<ConfigOptionBool>("enable_prime_tower", true)->value) {
if (!is_belt_printer && m_print_config.option<ConfigOptionBool>("enable_prime_tower", true)->value) {
for (int index = 0; index < partplate_list.get_plate_count(); index++) {
if ((plate_to_slice != 0) && (plate_to_slice != (index + 1)))
continue;
+1 -4
View File
@@ -5,6 +5,7 @@
#include "libslic3r/BoundingBox.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/Format/bbs_3mf.hpp"
#include "libslic3r/GCode/ThumbnailData.hpp"
#include <set>
#include <string>
#include <vector>
@@ -13,10 +14,6 @@
#include "libslic3r/Model.hpp"
#include "libslic3r/Format/AssembleList.hpp"
namespace Slic3r { class Preset; }
namespace Slic3r { struct PlateBBoxData; }
namespace Slic3r { struct ThumbnailData; }
namespace Slic3r {
namespace IO {
@@ -57,8 +57,6 @@
#include <iostream>
#include <set>
#include <string>
#include "libslic3r/AppConfig.hpp"
#include "libslic3r/Format/STEP.hpp"
namespace fs = boost::filesystem;
@@ -521,11 +519,16 @@ int slice_all_printers(const std::string &vendor, const std::string &outdir)
const std::string filament_name = bundle.filaments.get_selected_preset_name();
const std::string what = "Printer \"" + printer + "\"";
const std::string file_base = sanitize_filename(vendor_name) + "__" + sanitize_filename(printer);
// A belt printer has no wipe tower (it purges into a prism object on the belt), so its
// filament change is the plain tool change set_extruder() emits rather than the tower's block.
const bool belt = bundle.printers.get_selected_preset().config.opt_bool("belt_printer");
const std::string marker = belt ? "\nT1\n" : "CP TOOLCHANGE START";
if (const std::string out = slice_selection(bundle, what, false, outdir, file_base); out.empty())
++failures;
else if (out.find("CP TOOLCHANGE START") == std::string::npos) {
// The filament change never rode the tower, so change_filament_gcode was not exercised.
BOOST_LOG_TRIVIAL(error) << what << " sliced but the filament change never fired (no CP TOOLCHANGE START)";
else if (out.find(marker) == std::string::npos) {
// The filament change never fired, so change_filament_gcode was not exercised.
BOOST_LOG_TRIVIAL(error) << what << " sliced but the filament change never fired (no "
<< (belt ? "T1" : "CP TOOLCHANGE START") << ")";
++failures;
}
cover(bundle.prints.get_selected_preset());
-1
View File
@@ -26,7 +26,6 @@
#include <stdexcept>
#include <string>
#include <vector>
#include "libslic3r/PrintConfig.hpp"
using namespace Slic3r;
namespace fs = boost::filesystem;
@@ -20,7 +20,6 @@
#include <numeric>
#include <vector>
#include <utility>
#include "libslic3r/BoundingBox.hpp"
namespace Slic3r {
namespace Algorithm {
@@ -28,8 +28,6 @@
#include "libslic3r/Arachne/SkeletalTrapezoidationJoint.hpp"
#include "libslic3r/Arachne/utils/ExtrusionJunction.hpp"
#include "libslic3r/Arachne/utils/ExtrusionLine.hpp"
#include "libslic3r/Arachne/SkeletalTrapezoidationGraph.hpp"
#include "libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp"
#ifndef NDEBUG
#include "libslic3r/EdgeGrid.hpp"
@@ -11,6 +11,7 @@
#include <list>
#include <vector>
#include "utils/HalfEdgeGraph.hpp"
#include "utils/PolygonsSegmentIndex.hpp"
#include "utils/ExtrusionJunction.hpp"
#include "utils/ExtrusionLine.hpp"
+1
View File
@@ -11,6 +11,7 @@
#include <vector>
#include <utility>
#include "BeadingStrategy/BeadingStrategyFactory.hpp"
#include "libslic3r/libslic3r.h"
#include "libslic3r/Point.hpp"
#include "utils/ExtrusionLine.hpp"
@@ -14,6 +14,7 @@
#include "libslic3r/libslic3r.h"
#include "libslic3r/Point.hpp"
#include "libslic3r/Arachne/utils/ExtrusionJunction.hpp"
#include "libslic3r/BoundingBox.hpp"
#include "libslic3r/ExtrusionEntity.hpp"
#include "libslic3r/Line.hpp"
#include "libslic3r/Polygon.hpp"
@@ -19,7 +19,9 @@
#include "ExtrusionJunction.hpp"
#include "../../Polyline.hpp"
#include "../../Polygon.hpp"
#include "../../BoundingBox.hpp"
#include "../../ExtrusionEntity.hpp"
#include "../../Flow.hpp"
#include "libslic3r/libslic3r.h"
#include "libslic3r/Point.hpp"
@@ -15,6 +15,7 @@
#include "SparsePointGrid.hpp"
#include "PolygonsPointIndex.hpp"
#include "../../Polygon.hpp"
#include "libslic3r/Point.hpp"
#include "libslic3r/libslic3r.h"
+83 -10
View File
@@ -29,6 +29,7 @@
#include <libnest2d/utils/rotcalipers.hpp>
#include <numeric>
#include <ClipperUtils.hpp>
#include <boost/geometry/index/rtree.hpp>
#include <utility>
@@ -44,7 +45,6 @@
#include <boost/log/trivial.hpp>
#include <boost/multiprecision/integer.hpp>
#include <boost/rational.hpp>
#include "MultiMaterialSegmentation.hpp"
namespace libnest2d {
#if !defined(_MSC_VER) && defined(__SIZEOF_INT128__) && !defined(__APPLE__)
@@ -300,7 +300,13 @@ Points get_shrink_bedpts(const DynamicPrintConfig* print_cfg, const ArrangeParam
template<class PConf>
void fill_config(PConf& pcfg, const ArrangeParams &params) {
if (params.is_seq_print) {
if (params.is_belt) {
// Pack from the end of the belt that prints first.
pcfg.starting_point = !params.belt_reversed ? PConf::Alignment::BOTTOM_LEFT :
params.belt_axis == 1 ? PConf::Alignment::TOP_LEFT :
PConf::Alignment::BOTTOM_RIGHT;
}
else if (params.is_seq_print) {
// Start placing the items from the center of the print bed
pcfg.starting_point = PConf::Alignment::BOTTOM_LEFT;
}
@@ -445,6 +451,50 @@ protected:
return bindist;
}
// Belt printers pack from the end of the belt that prints first, and a corner
// packer's checks (pile inside the bin, pack origin) apply to them as well.
bool corner_packing() const { return params.is_belt || m_pconf.starting_point == PConfig::Alignment::BOTTOM_LEFT; }
static double at(const Box::PointType &pt, int i) { return double(i == 0 ? getX(pt) : getY(pt)); }
// Position along the belt in print order: increasing from the end that prints first.
double belt_pos(const Box::PointType &pt) const { return params.belt_reversed ? -at(pt, params.belt_axis) : at(pt, params.belt_axis); }
double belt_start(const Box &bb) const { return belt_pos(params.belt_reversed ? bb.maxCorner() : bb.minCorner()); }
double belt_end(const Box &bb) const { return belt_pos(params.belt_reversed ? bb.minCorner() : bb.maxCorner()); }
// The corner of the bin the belt pile grows from.
Box::PointType belt_origin() const
{
const Box bb = sl::boundingBox(m_bin);
auto o = bb.minCorner();
if (params.belt_reversed) {
if (params.belt_axis == 0) setX(o, getX(bb.maxCorner()));
else setY(o, getY(bb.maxCorner()));
}
return o;
}
// An item's far edge in print order is what it costs (so a row fills across the
// belt before the pile advances), with a slight pull toward the near lateral
// edge and the same penalty as the bottom-left heuristic for sitting outside
// the corner.
double dist_along_belt(const Box &ibb)
{
const Box bin = sl::boundingBox(m_bin);
const int l = 1 - params.belt_axis;
const double lat = at(ibb.minCorner(), l) - at(bin.minCorner(), l);
double d = belt_end(ibb) - belt_start(bin);
d += lat < 0 ? 10 * -lat : 0.1 * lat;
if (double behind = belt_start(ibb) - belt_start(bin); behind < 0)
d += 10 * -behind;
return norm(d);
}
double corner_bindist(const Box &ibb, const Slic3r::Point &origin_pack)
{
return params.is_belt ? dist_along_belt(ibb) : dist_for_BOTTOM_LEFT(ibb, origin_pack);
}
double dist_to_bin(const Box& ibb, const Slic3r::Point& origin_pack, typename Packer::PlacementConfig::Alignment starting_point_alignment)
{
double bindist = 0;
@@ -534,8 +584,8 @@ protected:
// The smalles distance from the arranged pile center:
double dist = norm(*(std::min_element(dists.begin(), dists.end())));
if (m_pconf.starting_point == PConfig::Alignment::BOTTOM_LEFT) {
double bindist = dist_for_BOTTOM_LEFT(ibb, origin_pack);
if (corner_packing()) {
double bindist = corner_bindist(ibb, origin_pack);
score = 0.2 * dist + 0.8 * bindist;
}
else {
@@ -592,8 +642,8 @@ protected:
break;
}
case LAST_BIG_ITEM: {
if (m_pconf.starting_point == PConfig::Alignment::BOTTOM_LEFT) {
score = dist_for_BOTTOM_LEFT(ibb, origin_pack);
if (corner_packing()) {
score = corner_bindist(ibb, origin_pack);
}
else {
if (m_pilebb.defined)
@@ -608,8 +658,8 @@ protected:
// already processed bigger items.
// No need to play around with the anchor points, the center will be
// just fine for small items
if (m_pconf.starting_point == PConfig::Alignment::BOTTOM_LEFT)
score = dist_for_BOTTOM_LEFT(ibb, origin_pack);
if (corner_packing())
score = corner_bindist(ibb, origin_pack);
else {
// Align mainly around existing items
score = 0.8 * norm(pl::distance(ibb.center(), bigbb.center()))+ 0.2*norm(pl::distance(ibb.center(), origin_pack));
@@ -710,6 +760,28 @@ protected:
score += 1 * (new_extruder_cnt-last_extruder_cnt);
}
// On a belt the parts print in belt order, so every colour change between
// parts is a filament change. Items arrive sorted by extruder and the pile
// grows from the leading end; keep each colour's run contiguous by charging
// an item for every packed item of another colour it does not fully follow,
// counting the tilted layers that reach belt_tilt_slope * height past that
// item's far edge.
if (params.is_belt && !params.is_seq_print) {
const std::set<int> item_colours(item.extrude_ids.begin(), item.extrude_ids.end());
const double item_start = belt_start(ibb);
for (Item &p : m_items) {
if (p.is_virt_object)
continue;
const std::set<int> p_colours(p.extrude_ids.begin(), p.extrude_ids.end());
const bool same_colour = std::includes(item_colours.begin(), item_colours.end(), p_colours.begin(), p_colours.end())
|| std::includes(p_colours.begin(), p_colours.end(), item_colours.begin(), item_colours.end());
if (same_colour)
continue;
if (item_start < belt_end(p.boundingBox()) + scaled(p.height * params.belt_tilt_slope))
score += 10.;
}
}
return std::make_tuple(score, fullbb);
}
@@ -786,7 +858,8 @@ public:
auto binbb = sl::boundingBox(m_bin);
auto starting_point = cfg.starting_point == PConfig::Alignment::BOTTOM_LEFT ? binbb.minCorner() : binbb.center();
auto starting_point = this->params.is_belt ? belt_origin() :
cfg.starting_point == PConfig::Alignment::BOTTOM_LEFT ? binbb.minCorner() : binbb.center();
// if we have wipe tower, items should be arranged around wipe tower
for (Item itm : items) {
if (itm.is_wipe_tower) {
@@ -937,7 +1010,7 @@ std::function<double(const Item &, const ItemGroup&)> AutoArranger<ExPolygon>::g
auto mp = m_merged_pile;
mp.emplace_back(itm.transformedShape());
auto chull = sl::convexHull(mp);
if (m_pconf.starting_point == PConfig::Alignment::BOTTOM_LEFT)
if (corner_packing())
{
if (!sl::isInside(chull, m_bin))
score += LARGE_COST_TO_REJECT;
+7
View File
@@ -146,6 +146,13 @@ struct ArrangeParams {
float nozzle_height = 0;
float printable_height = 256.0;
Vec2d align_center{ 0.5,0.5 };
// Belt printer: items print in the order they lie along the belt axis, from
// its low end unless belt_reversed, and a part's top prints
// belt_tilt_slope * height further along it than its base.
bool is_belt = false;
int belt_axis = 1; // 0 = X, 1 = Y
bool belt_reversed = false;
float belt_tilt_slope = 1.f; // cot(belt tilt angle), 0 when the belt is not tilted
ArrangePolygons excluded_regions; // regions cant't be used
ArrangePolygons nonprefered_regions; // regions can be used but not prefered
+536
View File
@@ -0,0 +1,536 @@
#include <limits>
#include "BeltBrim.hpp"
#include "ClipperUtils.hpp"
#include "Flow.hpp"
#include "Layer.hpp"
#include "Polygon.hpp"
#include "Print.hpp"
#include "ShortestPath.hpp"
#include "Support/BeltFloorContext.hpp"
#include <algorithm>
namespace Slic3r {
// ---------------------------------------------------------------- scaling
static inline Point scale_u_point(const Point &p, int from_axis, double factor)
{
// llround, not a cast: casting truncates toward zero, so a round trip would
// walk every vertex toward the origin by up to one unit per pass.
return from_axis == 0 ?
Point(coord_t(std::llround(double(p.x()) * factor)), p.y()) :
Point(p.x(), coord_t(std::llround(double(p.y()) * factor)));
}
static inline void scale_u_polygon(Polygon &poly, int from_axis, double factor)
{
for (Point &p : poly.points)
p = scale_u_point(p, from_axis, factor);
}
ExPolygons belt_scale_u(const ExPolygons &src, const BeltBrimFrame &frame, double factor)
{
ExPolygons out = src;
for (ExPolygon &ex : out) {
scale_u_polygon(ex.contour, frame.from_axis, factor);
for (Polygon &hole : ex.holes)
scale_u_polygon(hole, frame.from_axis, factor);
}
return out;
}
Polylines belt_scale_u(const Polylines &src, const BeltBrimFrame &frame, double factor)
{
Polylines out = src;
for (Polyline &pl : out)
for (Point &p : pl.points)
p = scale_u_point(p, frame.from_axis, factor);
return out;
}
// ---------------------------------------------------------------- sweep
ExPolygons sweep_ex(const ExPolygons &src, const Point &t)
{
if (src.empty())
return {};
if (t == Point(0, 0))
return src;
// One parallelogram per boundary edge. Together with P and P + t these
// cover the Minkowski sum exactly: for any q = p + s*t with p in P and
// s in [0, 1], let s* be the smallest lambda >= 0 with q - lambda*t in P.
// Either s* == 0 (so q is in P) or q - s* * t lies on some boundary edge e,
// putting q in that edge's parallelogram. Hole edges must be included, or
// holes narrower than t along t would wrongly survive the sweep.
Polygons quads;
for (const ExPolygon &ex : src)
for (size_t c = 0; c < ex.num_contours(); ++ c)
for (const Line &e : ex.contour_or_hole(c).lines()) {
if (e.a == e.b)
continue;
Polygon q;
q.points = { e.a, e.b, e.b + t, e.a + t };
// The non-zero fill rule counts a clockwise ring as -1, which
// would punch a hole instead of adding material. Edges parallel
// to t give a zero-area quad; Clipper discards those harmlessly.
if (q.is_clockwise())
q.reverse();
quads.emplace_back(std::move(q));
}
ExPolygons shifted = src;
for (ExPolygon &ex : shifted)
ex.translate(t);
// union_ex(ExPolygons, Polygons) uses pftNonZero, which is the fill rule the
// argument above relies on.
return union_ex(union_ex(src, shifted), quads);
}
// ---------------------------------------------------------------- brim region
ExPolygons belt_brim_region(const ExPolygons &footprint_flat,
bool has_outer,
bool has_inner,
coord_t brim_width,
coord_t object_gap,
coord_t leading,
coord_t lateral,
const BeltBrimFrame &frame)
{
if (footprint_flat.empty() || (! has_outer && ! has_inner))
return {};
ExPolygons out;
if (has_outer) {
// Offset the outer ring from the contours only, so a hole cannot punch
// through it. Same reasoning as the plate brim in Brim.cpp.
Polygons contours;
contours.reserve(footprint_flat.size());
for (const ExPolygon &ex : footprint_flat)
contours.emplace_back(ex.contour);
// Inner and outer boundary offset from the same polygon, to avoid
// round-off mismatch between them.
ExPolygons inner = offset_ex(contours, float(object_gap), jtRound, SCALED_RESOLUTION);
// Close the interior before offsetting outwards. A belt contact patch is often a
// narrow, broken-up strip, and the offset rings of two islands less than
// 2 x brim_width apart merge and fill the space between them - space that lies
// UNDER the part, which is not what "outer brim" means. Closing also swallows
// holes in the patch for the same reason. Concavity-filling only, so an apron or
// any other outward protrusion is untouched.
ExPolygons envelope = brim_width > 0 ? closing_ex(inner, float(brim_width)) : inner;
ExPolygons base = envelope;
if (leading > 0) {
// Sweep downhill from the gapped keep-out, so the apron is contiguous with
// the ring instead of starting inside the gap.
const Point t = frame.from_axis == 0 ?
Point(frame.downhill_sign() * leading, 0) :
Point(0, frame.downhill_sign() * leading);
base = union_ex(base, sweep_ex(envelope, t));
}
if (lateral > 0) {
// Across the belt, both ways. Swept from `base` so the apron is widened
// too, and in the flattened frame the cross-belt axis is unscaled, so this
// distance is already a true on-belt distance.
const Point t = frame.from_axis == 0 ? Point(0, lateral) : Point(lateral, 0);
ExPolygons widened = union_ex(sweep_ex(base, t), sweep_ex(base, Point(-t.x(), -t.y())));
base = union_ex(base, to_polygons(widened));
}
ExPolygons outer = offset_ex(base, float(brim_width), jtRound, SCALED_RESOLUTION);
expolygons_append(out, diff_ex(outer, envelope));
}
if (has_inner) {
// Holes reversed so a negative offset grows inward, mirroring Brim.cpp.
// No apron here: an apron growing into a hole interior is never useful.
Polygons holes;
for (const ExPolygon &ex : footprint_flat)
polygons_append(holes, ex.holes);
polygons_reverse(holes);
if (! holes.empty()) {
ExPolygons hole_inner = offset_ex(holes, - float(brim_width + object_gap));
ExPolygons hole_outer = offset_ex(holes, - float(object_gap));
expolygons_append(out, intersection_ex(diff_ex(hole_outer, hole_inner), holes));
}
}
return union_ex(out);
}
// ---------------------------------------------------------------- line lattice
std::vector<coord_t> belt_brim_line_positions(coord_t u_lo,
coord_t u_hi,
coord_t pitch_u,
coord_t u_anchor)
{
std::vector<coord_t> out;
if (pitch_u <= 0 || u_hi <= u_lo)
return out;
// Walk the lattice from just below u_lo. Integer arithmetic throughout, so
// the half-open interval needs no epsilon: a point landing exactly on u_hi
// belongs to the next band.
int64_t k = int64_t(std::floor(double(u_lo - u_anchor) / double(pitch_u))) - 1;
while (u_anchor + coord_t(k) * pitch_u < u_lo)
++ k;
for (;; ++ k) {
const coord_t u = u_anchor + coord_t(k) * pitch_u;
if (u >= u_hi)
break;
out.emplace_back(u);
}
return out;
}
// ---------------------------------------------------------------- pipeline
// A band of the belt surface as an explicit box, clamped to `bounds` along the
// shear axis. Deliberately not BeltFloorContext::surface_polygon(): those
// half-planes span +-1000 mm, which is wasteful to clip against and dangerous to
// feed through the flattening scale.
static Polygon band_box(const BoundingBox &bounds, int from_axis, coordf_t u_lo, coordf_t u_hi)
{
coord_t lo = scale_(u_lo);
coord_t hi = scale_(u_hi);
const coord_t bmin = from_axis == 0 ? bounds.min.x() : bounds.min.y();
const coord_t bmax = from_axis == 0 ? bounds.max.x() : bounds.max.y();
lo = std::max(lo, bmin);
hi = std::min(hi, bmax);
Polygon poly;
if (hi <= lo)
return poly;
if (from_axis == 0)
poly.points = { Point(lo, bounds.min.y()), Point(hi, bounds.min.y()),
Point(hi, bounds.max.y()), Point(lo, bounds.max.y()) };
else
poly.points = { Point(bounds.min.x(), lo), Point(bounds.max.x(), lo),
Point(bounds.max.x(), hi), Point(bounds.min.x(), hi) };
return poly;
}
// Everything the per-band line generator needs, gathered once per object.
struct BeltBrimContext
{
BeltFloorContext ctx;
BeltBrimFrame frame;
ExPolygons region; // brim region, object-local slicing XY
BoundingBox region_bbox;
Flow brim_flow;
coord_t pitch_u = 0;
coord_t u_anchor = 0;
double in_plane_pitch = 0.; // mm
};
// Emit the cross-belt brim lines that belong to the band [print_z - height, print_z].
static void belt_brim_band_paths(const BeltBrimContext &bc,
coordf_t print_z,
coordf_t height,
const Polygons &obstacles,
ExtrusionEntityCollection &out,
ExPolygons &areas_out)
{
coordf_t u_lo = bc.ctx.cutoff_u(print_z - height);
coordf_t u_hi = bc.ctx.cutoff_u(print_z);
if (u_lo > u_hi)
std::swap(u_lo, u_hi);
// How wide this band is measured ON the belt, versus one nominal bead.
const double band_in_plane = (u_hi - u_lo) * bc.frame.u_stretch();
// Fraction of the layer height at which a line sits above the belt. Toward the
// downhill edge, so the sheet is reasonably thick while the nozzle stays clear of
// the belt itself.
static constexpr double BAND_CLEARANCE_FRACTION = 0.75;
std::vector<coord_t> us;
double uniform_clearance = 0.; // 0 => derive per line from its own position
double line_pitch = bc.in_plane_pitch;
// One line also serves a band up to half a bead wider than the nominal pitch (a 0.3 mm
// first layer at 45 degrees): its flow is matched to the band, so the bead is that much
// wider. Two lattice lines in such a band would land almost on top of each other.
if (band_in_plane <= 1.5 * bc.in_plane_pitch + EPSILON) {
// Steep belt, which is the normal case: the band is narrower than one bead, so
// exactly one line fits. Place it at a FIXED fraction of the band rather than
// on a nominal-spacing lattice. On a lattice each line lands at an arbitrary
// point in its band, the clearance sweeps [0, height] from band to band, and the
// bead width therefore varies by 2x - visible as ragged, uneven brim lines.
// Anchoring to the band makes the clearance identical everywhere, so every bead
// is the same width.
//
// The spacing is then whatever the bands give (height / sin(tilt) on the belt)
// rather than the nominal bead spacing, so the flow below is matched to THAT
// pitch. Matched flow at the real pitch is what keeps the sheet uniform and
// gap-free; using nominal flow at band spacing would over-feed it.
us.push_back(scale_(bc.ctx.cutoff_u(print_z - BAND_CLEARANCE_FRACTION * height)));
uniform_clearance = BAND_CLEARANCE_FRACTION * height;
line_pitch = band_in_plane;
} else {
// Shallow belt: the band is wider than a bead, so it takes several lines and they
// have to sit on the nominal lattice. Their clearances then differ, and so do
// their widths - unavoidable here, but shallow belts are the rare case.
us = belt_brim_line_positions(scale_(u_lo), scale_(u_hi), bc.pitch_u, bc.u_anchor);
}
if (us.empty())
return;
const Polygons region_polys = to_polygons(bc.region);
// One lattice line at a time: the clearance - and therefore the extrusion
// volume - is a property of the line's u, so the pieces of different lines
// must not be pooled before the flow is resolved.
// Overshoot the region so the clip, not the line's ends, decides the extent.
const coord_t margin = coord_t(SCALED_EPSILON) + 1;
coord_t u_prev = std::numeric_limits<coord_t>::min();
for (coord_t u : us) {
// Nozzle-to-belt clearance for this line. Constant along the line, because the
// belt height depends only on the shear-axis coordinate. Band-anchored lines
// share one clearance by construction; lattice lines (shallow belts, or a first
// layer thick enough that the band is wider than a bead) each get their own.
//
// A lattice line can fall where the belt is only a hair below the band's print_z.
// The bead there would be laid scraping the belt while its flow is sized for a
// taller cell, so it is moved uphill to the same fraction of the band the
// single-line case uses. (The clearance is along slice Z; the real gap under the
// nozzle is clearance x cos(tilt), 0.53 h at 45 degrees for the 0.75 fraction.)
double clearance = uniform_clearance;
if (clearance <= 0.) {
const Point probe = bc.frame.from_axis == 0 ? Point(u, 0) : Point(0, u);
clearance = print_z - bc.ctx.floor_print_z(probe);
if (clearance < BAND_CLEARANCE_FRACTION * height) {
clearance = BAND_CLEARANCE_FRACTION * height;
u = scale_(bc.ctx.cutoff_u(print_z - clearance));
}
clearance = std::min(clearance, height);
}
// A line moved uphill can land on, or almost on, its neighbour; two beads closer
// than half a pitch would be laid into the same cell.
if (u_prev != std::numeric_limits<coord_t>::min() && std::abs(u - u_prev) < bc.pitch_u / 2)
continue;
u_prev = u;
Polyline line;
if (bc.frame.from_axis == 0)
line.points = { Point(u, coord_t(bc.region_bbox.min.y() - margin)),
Point(u, coord_t(bc.region_bbox.max.y() + margin)) };
else
line.points = { Point(coord_t(bc.region_bbox.min.x() - margin), u),
Point(coord_t(bc.region_bbox.max.x() + margin), u) };
Polylines pieces = intersection_pl(Polylines{ line }, region_polys);
if (! obstacles.empty())
pieces = diff_pl(pieces, obstacles);
if (pieces.empty())
continue;
// with_cross_section, not with_height: it reaches the prescribed volume while
// KEEPING the extrusion spacing, so the bead is sized to fill exactly one
// pitch x clearance cell of the sheet.
const Flow f = bc.brim_flow.with_cross_section(float(line_pitch * clearance));
// Footprint of these beads, for the first-layer convex hull and bbox.
for (const Polygon &p : offset(pieces, 0.5f * float(f.scaled_width())))
areas_out.emplace_back(ExPolygon(p));
extrusion_entities_append_paths(out.entities, chain_polylines(std::move(pieces)),
erBrim, f.mm3_per_mm(), f.width(), float(clearance));
}
}
// Union of everything extruded at `print_z` that the brim must keep clear of, expressed
// in `self`'s local slicing frame. Includes `self` itself: its slice at this Z can
// overhang outside the belt footprint and land in the brim ring, which the flattened
// brim_object_gap - a belt-plane separation - does not cover.
//
// THREADING: this runs inside posSupportMaterial, which Print::process() executes for all
// objects in a tbb::parallel_for (Print.cpp). Object slices are finished by then and safe
// to read across objects, but SUPPORT layers are not: another object's thread may be
// inside clear_support_layers() - which deletes the SupportLayer pointers - right now, so
// touching a foreign object's support_layers() here is a use-after-free. Only this
// object's own supports are consulted; they are complete, because make_belt_brim() runs at
// the tail of this object's own generate_support_material(). The cost is that the brim
// does not dodge a *different* object's support at the same Z, which needs the objects to
// overlap in the belt direction in the first place.
// `region_bbox` bounds the brim; anything outside it cannot clip a brim line, so whole
// objects are skipped without materialising their polygons. On a typical plate the
// objects do not overlap and every foreign object drops out here, which matters because
// this runs once per band - hundreds of times per object.
static Polygons belt_brim_obstacles(const Print &print, const PrintObject &self,
const BoundingBox &region_bbox, coordf_t print_z, coordf_t tol)
{
const Point shift_self = self.instances().empty() ? Point(0, 0)
: self.instances().front().shift_without_plate_offset();
Polygons out;
for (const PrintObject *o : print.objects()) {
const bool is_self = (o == &self);
for (const PrintInstance &inst : o->instances()) {
const Point delta = inst.shift_without_plate_offset() - shift_self;
if (const Layer *l = o->get_layer_at_printz(print_z, tol)) {
BoundingBox lb = get_extents(l->lslices);
lb.translate(delta.x(), delta.y());
if (lb.overlap(region_bbox)) {
Polygons ps = to_polygons(l->lslices);
for (Polygon &p : ps)
p.translate(delta);
polygons_append(out, std::move(ps));
}
}
if (! is_self)
continue;
if (const SupportLayer *sl = o->get_support_layer_at_printz(print_z, tol)) {
Polygons ps = sl->support_fills.polygons_covered_by_spacing();
for (Polygon &p : ps)
p.translate(delta);
polygons_append(out, std::move(ps));
}
}
}
if (out.size() < 2)
return out; // union_() of 0 or 1 polygons is pure overhead
return union_(out);
}
void make_belt_brim(PrintObject &object)
{
object.clear_belt_brim();
if (! object.has_belt_brim())
return;
const Print &print = *object.print();
BeltBrimContext bc;
if (! bc.ctx.init(object.slicing_parameters(), print.config()))
return;
bc.frame = BeltBrimFrame{ bc.ctx.shear_factor(), bc.ctx.from_axis() };
const size_t nlayers = object.layers().size();
if (nlayers == 0)
return;
// 1. Belt footprint: the union of each layer's slice clipped to that layer's
// own contact band. This is the object's bottom face, which on a belt is
// spread over every layer instead of sitting in layer 0.
ExPolygons footprint_acc;
for (size_t i = 0; i < nlayers; ++ i) {
const Layer &layer = *object.layers()[i];
if (layer.lslices.empty())
continue;
// print_z - height, not the previous layer's print_z: variable layer
// heights make the latter wrong.
coordf_t u_lo = bc.ctx.cutoff_u(layer.print_z - layer.height);
coordf_t u_hi = bc.ctx.cutoff_u(layer.print_z);
if (u_lo > u_hi)
std::swap(u_lo, u_hi);
BoundingBox bb = get_extents(layer.lslices);
bb.offset(scale_(1.));
const Polygon band = band_box(bb, bc.frame.from_axis, u_lo, u_hi);
if (band.empty())
continue;
expolygons_append(footprint_acc, intersection_ex(layer.lslices, Polygons{ band }));
}
const ExPolygons footprint = union_ex(footprint_acc);
if (footprint.empty())
return;
// 2. Brim region, offset in the flattened (true on-belt) metric.
const PrintObjectConfig &cfg = object.config();
bc.brim_flow = print.brim_flow();
const double flow_w = bc.brim_flow.scaled_spacing() * SCALING_FACTOR;
// Quantize to an even number of lines, as the plate brim does.
const coord_t width = scale_(std::floor(cfg.brim_width.value / flow_w / 2) * flow_w * 2);
const coord_t leading = scale_(cfg.leading_brim_length.value);
const coord_t lateral = scale_(cfg.extra_brim_width.value);
const coord_t gap = scale_(cfg.brim_object_gap.value);
// Belt printers collapse Auto / Mouse ear / Painted to outer-only: the auto width
// heuristic and flat ear discs have no meaning on a tilted plane. Leading-edge-only
// is an outer brim too; it is narrowed down to the first contact below.
const BrimType bt = cfg.brim_type.value;
const bool has_outer = bt == btOuterOnly || bt == btOuterAndInner
|| bt == btAutoBrim || bt == btEar || bt == btPainted
|| bt == btLeadingEdgeOnly;
const bool has_inner = bt == btInnerOnly || bt == btOuterAndInner;
bc.region = belt_unflatten(
belt_brim_region(belt_flatten(footprint, bc.frame), has_outer, has_inner,
width, gap, leading, lateral, bc.frame),
bc.frame);
if (bt == btLeadingEdgeOnly && ! bc.region.empty()) {
// Keep only what lies at or downhill of the object's FIRST contact with the
// belt, so the part is supported as it lands and nothing is printed alongside
// it afterwards. The cut is the uphill edge of the first layer's contact band:
// everything past it belongs to later contacts.
const coordf_t u_cut = bc.ctx.cutoff_u(object.layers().front()->print_z);
BoundingBox keep_bb = get_extents(bc.region);
keep_bb.offset(scale_(1.));
const bool low_side = bc.frame.shear > 0.; // downhill is -u
const Polygon keep = band_box(keep_bb, bc.frame.from_axis,
low_side ? unscale<double>(bc.frame.from_axis == 0 ? keep_bb.min.x() : keep_bb.min.y()) : u_cut,
low_side ? u_cut : unscale<double>(bc.frame.from_axis == 0 ? keep_bb.max.x() : keep_bb.max.y()));
bc.region = keep.empty() ? ExPolygons{} : intersection_ex(bc.region, Polygons{ keep });
}
if (bc.region.empty())
return;
bc.region_bbox = get_extents(bc.region);
// 3. Line lattice. Fixed pitch in the flattened metric, anchored at the
// footprint's leading-most edge so lines stay collinear across
// disconnected islands and across the apron prologue.
bc.pitch_u = std::max<coord_t>(1, coord_t(bc.brim_flow.scaled_spacing() * bc.frame.cos_tilt()));
bc.in_plane_pitch = unscale<double>(bc.pitch_u) * bc.frame.u_stretch();
{
const BoundingBox fbb = get_extents(footprint);
const bool low_side = bc.frame.shear > 0.;
bc.u_anchor = bc.frame.from_axis == 0 ? (low_side ? fbb.min.x() : fbb.max.x())
: (low_side ? fbb.min.y() : fbb.max.y());
}
// 4. Bands coincident with an object layer.
std::vector<ExtrusionEntityCollection> by_layer(nlayers);
std::vector<ExPolygons> areas_by_layer(nlayers);
for (size_t i = 0; i < nlayers; ++ i) {
const Layer &layer = *object.layers()[i];
const Polygons obstacles = belt_brim_obstacles(print, object, bc.region_bbox, layer.print_z, 0.5 * layer.height);
belt_brim_band_paths(bc, layer.print_z, layer.height, obstacles, by_layer[i], areas_by_layer[i]);
}
// 5. Apron prologue: the part of the region downhill of the object's first
// layer, which has no object layer to ride on.
std::vector<BeltBrimBand> prologue;
{
const Layer &first = *object.layers().front();
const coordf_t h = first.height;
const bool low_side = bc.frame.shear > 0.;
const coord_t u_lead_s = bc.frame.from_axis == 0
? (low_side ? bc.region_bbox.min.x() : bc.region_bbox.max.x())
: (low_side ? bc.region_bbox.min.y() : bc.region_bbox.max.y());
const coordf_t u_lead = unscale<double>(u_lead_s);
// print_z at which the belt surface crosses the region's leading edge.
const coordf_t z_lead = bc.ctx.shear_factor() * u_lead
+ bc.ctx.floor_offset() + bc.ctx.z_shift();
if (h > EPSILON)
for (coordf_t z = first.print_z - h; z > z_lead - h; z -= h) {
const Polygons obstacles = belt_brim_obstacles(print, object, bc.region_bbox, z, 0.5 * h);
BeltBrimBand band;
band.print_z = z;
band.height = h;
belt_brim_band_paths(bc, z, h, obstacles, band.fills, band.areas);
if (! band.fills.empty())
prologue.emplace_back(std::move(band));
}
// Lowest Z first, so collect_layers_to_print sees them in print order.
std::reverse(prologue.begin(), prologue.end());
}
object.set_belt_brim(std::move(by_layer), std::move(areas_by_layer), std::move(prologue));
}
} // namespace Slic3r
+169
View File
@@ -0,0 +1,169 @@
#ifndef slic3r_BeltBrim_hpp_
#define slic3r_BeltBrim_hpp_
#include "ExPolygon.hpp"
#include "ExtrusionEntityCollection.hpp"
#include "Point.hpp"
#include "Polyline.hpp"
#include <cmath>
#include <vector>
// Belt-printer brim geometry.
//
// A belt printer slices in a ROTATED frame, so the belt surface is not the
// Z=0 bed plane but a tilted plane in slicing space:
//
// z_slicing(u) = shear * u + floor_offset + z_shift, u = X or Y
//
// where `shear == tan(tilt)` (SlicingParameters::belt_floor_shear_factor) and
// the axis is selected by SlicingParameters::belt_floor_from_axis. See
// Support/BeltFloorContext.hpp for the canonical accessors.
//
// Consequences that drive everything in this file:
//
// * A horizontal slicing layer touches the belt only along a narrow strip at
// its leading edge, `layer_height / shear` wide (~0.2 mm at 45 degrees).
// The object's belt footprint - its bottom face - is therefore spread over
// every layer, not contained in layer 0.
// * Distances measured in slicing XY are NOT on-belt distances: moving `du`
// along the shear axis travels `du / cos(tilt)` across the belt. So brim
// offsets have to be taken in a "flattened" space where the shear axis is
// stretched by `1 / cos(tilt)`, then mapped back.
// * Brim ahead of the part (downhill) lies at slicing Z BELOW the object's
// first layer, because the object's layer 0 is precisely its leading
// contact with the belt.
//
// Everything here is pure geometry on ExPolygons/Polylines so it can be unit
// tested without a Print. Keep user-visible strings out of this file: it is
// not listed in localization/i18n/list.txt.
namespace Slic3r {
// Tilt window within which the BELT plane, not the bed plane, is the adhesion
// surface. Below ~1 degree a belt is a flat bed as far as adhesion goes, and the
// contact band would be layer_height/sin(tilt) - tens of millimetres - so the
// ordinary plate brim is both correct and cheaper. Above ~85 degrees the whole
// brim compresses into a sliver and is not worth generating.
inline constexpr double BELT_BRIM_MIN_TILT_DEG = 1.;
inline constexpr double BELT_BRIM_MAX_TILT_DEG = 85.;
// Description of the tilted belt plane, reduced to what the brim geometry needs.
struct BeltBrimFrame
{
// tan(tilt). Sign selects which way is downhill.
double shear = 0.;
// 0 = X, 1 = Y. Matches BeltFloorContext::from_axis().
int from_axis = 1;
// 1 / cos(tilt). Stretch factor that turns a projected distance along
// `from_axis` into the true distance travelled across the belt.
double u_stretch() const { return std::sqrt(1. + shear * shear); }
// cos(tilt). The inverse mapping.
double cos_tilt() const { return 1. / this->u_stretch(); }
// Downhill is where the belt surface is lower, i.e. printed earlier, i.e.
// the leading edge of the part. For shear > 0 that is -u.
int downhill_sign() const { return shear > 0. ? -1 : +1; }
};
// Scale only the `from_axis` component by `factor`, rounding to nearest.
//
// Deliberately not MultiPoint::scale(fx, fy) / ExPolygon::scale(fx, fy): those
// truncate toward zero, which is asymmetric about the origin and loses up to a
// full coordinate unit per vertex on every round trip.
ExPolygons belt_scale_u(const ExPolygons &src, const BeltBrimFrame &frame, double factor);
Polylines belt_scale_u(const Polylines &src, const BeltBrimFrame &frame, double factor);
// Into / out of the space where Euclidean offsets equal true on-belt distances.
inline ExPolygons belt_flatten(const ExPolygons &src, const BeltBrimFrame &frame)
{ return belt_scale_u(src, frame, frame.u_stretch()); }
inline ExPolygons belt_unflatten(const ExPolygons &src, const BeltBrimFrame &frame)
{ return belt_scale_u(src, frame, frame.cos_tilt()); }
// Minkowski sum of `src` with the segment [0, t]: the region swept by sliding
// `src` along t. Used to grow the brim downhill for "extra brim width".
//
// Implemented as union_(P, P + t, {parallelogram per boundary edge}) over ALL
// contours including holes, with every parallelogram forced counter-clockwise
// so the non-zero fill rule closes holes narrower than t along the sweep
// direction. A hole survives exactly when it is wider than |t| measured along
// t - not when it is wider in its narrowest Euclidean direction.
ExPolygons sweep_ex(const ExPolygons &src, const Point &t);
// Brim region for one already-flattened belt footprint. All lengths are scaled
// and measured in the flattened (true on-belt) metric.
//
// `has_outer` / `has_inner` are the resolved BrimType: belt printers collapse
// Auto / Mouse ear / Painted to outer-only, so the caller does that mapping and
// this function never needs PrintConfig.
//
// Two directional extras are applied to the footprint before the outer offset, so
// each one buys reach in one direction only:
//
// `leading` (leading_brim_length) sweeps the footprint DOWNHILL along the belt,
// so every leading-facing edge gains an apron ahead of it.
// `lateral` (extra_brim_width) sweeps it BOTH WAYS across the belt, widening
// the brim sideways without pushing it further ahead or behind.
//
// Neither is applied to the inner (hole) ring.
ExPolygons belt_brim_region(const ExPolygons &footprint_flat,
bool has_outer,
bool has_inner,
coord_t brim_width,
coord_t object_gap,
coord_t leading,
coord_t lateral,
const BeltBrimFrame &frame);
// Brim line positions for one layer band.
//
// Lines sit on a fixed lattice `u_anchor + k * pitch_u` so the on-belt spacing
// between neighbouring brim lines is constant regardless of how the lattice
// falls across layer bands. Snapping to band centres instead would quantise
// the spacing to whole bands and under-deposit by ~35% at 45 degrees.
//
// The band is half-open, [u_lo, u_hi), so every lattice point belongs to
// exactly one band: none duplicated at a boundary, none dropped. A band
// narrower than the pitch simply yields nothing; a band much wider (shallow
// tilt) yields several lines.
std::vector<coord_t> belt_brim_line_positions(coord_t u_lo,
coord_t u_hi,
coord_t pitch_u,
coord_t u_anchor);
// ---------------------------------------------------------------- pipeline
// One brim-only layer printed BEFORE the object's first layer, carrying the
// apron that has to be stuck to the belt ahead of the part.
//
// Deliberately not a Layer subclass. A synthetic Layer would inherit id()
// semantics that leak into initial-layer temperature selection, the spiral vase
// probe, gradual interpolation, avoid-crossing-perimeters and cooling, all of
// which key off Layer::id() == 0 or off a layer's regions. A plain record
// carries only what the emitter needs.
//
// `height` is the LAYER height, used for the Z move and ordering metadata only.
// Each extrusion path inside `fills` carries its own height, equal to that
// line's nozzle-to-belt clearance, which varies across the band.
struct BeltBrimBand
{
coordf_t print_z = 0.;
coordf_t height = 0.;
// erBrim paths in the object's local slicing frame, untranslated.
ExtrusionEntityCollection fills;
// Footprint of those paths, for the first-layer convex hull / bbox.
ExPolygons areas;
};
class PrintObject;
// Generate the belt brim for one object: fills its per-object-layer bands and
// its apron prologue. No-op unless PrintObject::has_belt_brim().
//
// Runs inside posSupportMaterial rather than the brim step, because the prologue
// print_z values must exist before ToolOrdering is built at psWipeTower.
void make_belt_brim(PrintObject &object);
} // namespace Slic3r
#endif // slic3r_BeltBrim_hpp_
+69
View File
@@ -0,0 +1,69 @@
#include "BeltGCode.hpp"
#include "GCodeWriter.hpp"
#include "GCode/BeltKinematics.hpp"
#include "BeltTransform.hpp"
#include "Print.hpp"
namespace Slic3r {
void BeltGCode::init_belt_writer(Print &print)
{
// Axis remap and build volume max are set by base GCode after init_belt_writer
// returns; set_kinematics() replays them, so install order does not matter.
install_belt_kinematics(m_writer, print.config());
m_writer.set_force_normal_lift(true);
}
void BeltGCode::write_belt_header(GCodeOutputStream &file, const Print &print)
{
const auto &full_cfg = print.full_print_config();
// Slicing rotation: the belt tilt (axis + angle) and the single source of truth
// for the physical tilt the G-code viewer uses to enable belt view.
file.write_format("; belt_slice_rotation = %s\n", full_cfg.opt_serialize("belt_slice_rotation").c_str());
file.write_format("; belt_slice_rotation_angle = %.1f\n", print.config().belt_slice_rotation_angle.value);
file.write_format("; belt_slice_rotation_global = %d\n", print.config().belt_slice_rotation_global.value ? 1 : 0);
// Pre-slice remap configs
file.write_format("; preslice_remap_x = %s\n", full_cfg.opt_serialize("preslice_remap_x").c_str());
file.write_format("; preslice_remap_y = %s\n", full_cfg.opt_serialize("preslice_remap_y").c_str());
file.write_format("; preslice_remap_z = %s\n", full_cfg.opt_serialize("preslice_remap_z").c_str());
file.write_format("; preslice_remap_global = %d\n", print.config().preslice_remap_global.value ? 1 : 0);
file.write_format("; belt_preslice_global = %d\n", print.config().belt_preslice_global.value ? 1 : 0);
// Machine-frame transform: shear (tan) + scale (1/cos) derived from the belt
// tilt angle (or belt_frame_tilt_angle when decoupled).
file.write_format("; belt_frame_tilt_decouple = %d\n", print.config().belt_frame_tilt_decouple.value ? 1 : 0);
file.write_format("; belt_frame_tilt_angle = %.1f\n", print.config().belt_frame_tilt_angle.value);
}
void BeltGCode::on_set_origin(const PrintObject * /*obj*/, const Point & /*inst_shift*/)
{
// Global pre-slice mode: adjust origin using computed correction.
// Transform the origin through the belt pipeline so that
// back_transform(T * origin) = origin (correct machine position).
//
// Flags that trigger this path:
// belt_preslice_global — full pipeline (rotation * remap) is global
// preslice_remap_global — only the pre-slice remap is global
// belt_slice_rotation_global — slicing rotation treated as global (matches
// the per-instance Z-offset added in PrintObjectSlice.cpp)
// The XY origin adjustment uses the FULL forward transform, because the
// back_transform applied during G-code emission is always the inverse of
// the full pipeline.
bool use_global = m_config.belt_preslice_global.value
|| (m_config.preslice_remap_global.value
&& BeltTransformPipeline::has_preslice_remap(m_config))
|| (m_config.belt_slice_rotation_global.value
&& m_config.belt_slice_rotation.value != BeltRotationAxis::None
&& std::abs(m_config.belt_slice_rotation_angle.value) > EPSILON);
if (!use_global)
return;
// Adjust origin: transform through belt forward pipeline so that
// the back-transform correctly recovers model-space positions.
Transform3d T = BeltTransformPipeline::build_forward_transform(m_config);
Vec2d cur_origin = this->origin();
Vec3d origin3d(cur_origin.x(), cur_origin.y(), 0.);
Vec3d adjusted = T.linear() * origin3d;
this->set_origin(Vec2d(adjusted.x(), adjusted.y()));
}
} // namespace Slic3r
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include "GCode.hpp"
namespace Slic3r {
// Belt-printer-specific GCode export.
//
// Inherits from GCode and overrides virtual hooks to:
// - Install a BeltKinematics on the GCodeWriter
// - Write belt configuration to the G-code header
// - Adjust the origin for global pre-slice transforms when switching instances
// (Arc fitting is disabled for belt printers by BeltKinematics::supports_arc_moves(),
// which the base GCode::should_disable_arc_fitting() consults -- no override needed.)
class BeltGCode : public GCode
{
protected:
void init_belt_writer(Print &print) override;
void write_belt_header(GCodeOutputStream &file, const Print &print) override;
void on_set_origin(const PrintObject *obj, const Point &inst_shift) override;
};
} // namespace Slic3r
+417
View File
@@ -0,0 +1,417 @@
// ORCA-Belt: backend of the belt purge tower (the belt replacement for the
// classic wipe/prime tower).
//
// Kept in its own translation unit so the belt-purge logic stays out of the way
// of unrelated upstream changes to Print.cpp / PrintObjectSlice.cpp and carries
// no regression risk for normal printers: none of these methods do anything
// unless the print is a belt printer with the belt purge tower enabled.
//
// Print::has_belt_purge_tower() - is the belt purge tower active?
// Print::_align_belt_purge_layers() - snap the prism's layer grid onto the
// printed objects' grid
// Print::_plan_belt_purge() - route filament-change purging into the
// prism (flush-into-objects), no wipe tower
// PrintObject::belt_shift_layer_grid() - shift a sliced layer grid
// PrintObject::belt_truncate_layers_above() - cancel the prism past the last swap
//
// (Declarations live in Print.hpp alongside the rest of the Print interface.)
#include "Print.hpp"
#include "PrintConfig.hpp"
#include "Exception.hpp"
#include "GCode/ToolOrdering.hpp"
#include "Layer.hpp"
#include "ExtrusionEntity.hpp"
#include "ExtrusionEntityCollection.hpp"
#include "I18N.hpp"
#include "format.hpp"
#include "LocalesUtils.hpp"
#include "libslic3r.h"
#include <algorithm>
#include <cmath>
#include <limits>
#include <boost/log/trivial.hpp>
namespace Slic3r {
// Belt purge prism: purging after filament changes is routed into a sliced
// prism object via the flush-into-objects machinery instead of a wipe tower.
bool Print::has_belt_purge_tower() const
{
// Its own purge-tower "type", gated by the belt-only enable_belt_purge_tower
// option (not the classic enable_prime_tower).
if (!(m_config.belt_printer.value
&& m_config.enable_belt_purge_tower.value
&& !m_config.spiral_mode.value
&& m_config.filament_diameter.values.size() > 1))
return false;
return std::any_of(m_objects.begin(), m_objects.end(), [](const PrintObject *object) {
return object->config().belt_purge_tower_object.value;
});
}
// Belt mode: align ALL objects on the plate (the printed objects AND the purge
// prism) onto one common layer grid, so the prism can absorb every toolchange.
//
// After belt slicing each object's layer print_z carries a per-object global z
// offset (mesh-vertex-scan belt_z_shift + instance-Y-dependent terms), so
// objects at different belt-Y positions get layer grids with DIFFERENT residues
// (mod layer height). Purge marking looks absorbers up with
// get_layer_at_printz(lt.print_z, EPSILON), so a toolchange on object B only
// absorbs into the prism if the prism has a layer at B's print_z. Snapping only
// the prism to one object therefore worked for a single (assembled) multi-color
// object but failed with multiple separate objects — the prism could follow only
// one grid, and toolchanges on the others went unabsorbed ("multiple layer
// grids" warning).
//
// Fix: pick one reference grid (the tallest object) and shift every object onto
// it. Each shift is at most half a layer height — a sub-100µm move along the
// belt, the very same mechanism the per-object global_z_offset already uses, and
// it keeps each object internally consistent (belt_shift_layer_grid moves the
// object's layers, its support layers, and its belt floor together). Equal layer
// height across objects is enforced by Print::validate(), so once residues match
// every object steps on the same lattice {ref_offset + k*h} and every toolchange
// layer coincides with a prism layer.
void Print::_align_belt_purge_layers()
{
PrintObject *prism = nullptr;
for (PrintObject *po : m_objects)
if (po->config().belt_purge_tower_object.value && !po->layers().empty()) {
prism = po;
break;
}
if (prism == nullptr || prism->layers().empty())
return;
const double h = prism->config().layer_height.value;
if (h <= EPSILON)
return;
// Grid residue of an object's layer grid: identical for all of an object's
// layers above the first since they step by h.
auto grid_offset = [h](const PrintObject *po) -> double {
if (po->layers().empty())
return 0.;
const double z = po->layers().front()->print_z;
return z - std::floor(z / h) * h; // in [0, h)
};
// Reference grid: the tallest non-prism object (proxy for the object with
// the most toolchange layers — minimizes how far the rest must move).
const PrintObject *ref = nullptr;
double ref_top = -std::numeric_limits<double>::max();
for (const PrintObject *po : m_objects) {
if (po->config().belt_purge_tower_object.value || po->layers().empty())
continue;
const double top = po->layers().back()->print_z;
if (top > ref_top) {
ref_top = top;
ref = po;
}
}
if (ref == nullptr)
return;
const double ref_offset = grid_offset(ref);
// Snap every object (printed objects AND the prism) onto the reference grid.
for (PrintObject *po : m_objects) {
if (po->layers().empty())
continue;
double delta = ref_offset - grid_offset(po);
if (delta > 0.5 * h)
delta -= h;
else if (delta <= -0.5 * h)
delta += h;
po->belt_shift_layer_grid(delta); // no-op for the reference object (delta ~ 0)
}
}
// Belt mode replacement for _make_wipe_tower(): plan filament-change purging
// into the belt purge prism (and any other flush_into_* object) using the
// flush-into-objects machinery, without generating classic wipe tower G-code.
// The toolchange itself is emitted by GCode::set_extruder() via the
// change_filament_gcode macro; the overrides marked here make the new
// filament's first extrusions land in the purge prism.
void Print::_plan_belt_purge()
{
m_wipe_tower_data.clear();
// psWipeTower may be invalidated without posSlice (for example after a
// filament-map or tool-ordering change). Restore a prism shortened by the
// previous plan so a newly higher toolchange can use its original layers.
for (PrintObject *po : m_objects)
if (po->config().belt_purge_tower_object.value)
po->belt_undo_purge_plan();
// Must run before ToolOrdering is built: LayerTools merge per-object layer
// print_z values, and the prism only absorbs purge where its (snapped)
// layers coincide with the toolchange layers.
this->_align_belt_purge_layers();
const unsigned int number_of_extruders = (unsigned int) m_config.filament_colour.values.size();
// No initial priming extrusions: there is no tower to prime on.
m_wipe_tower_data.tool_ordering = ToolOrdering(*this, (unsigned int) -1, false);
m_wipe_tower_data.tool_ordering.sort_and_build_data(*this, (unsigned int) -1, false);
if (m_wipe_tower_data.tool_ordering.empty() || m_wipe_tower_data.tool_ordering.last_extruder() == unsigned(-1))
throw Slic3r::SlicingError("The print is empty. The model is not printable with current print settings.");
// Is there any filament change at all? Not ToolOrdering::has_wipe_tower(): that reads the
// FIRST layer's flag, and on a belt the first layer may be a brim apron band, which carries
// neither object nor support and so never gets the flag even when the print changes filament.
{
bool any_change = false;
unsigned int cur = m_wipe_tower_data.tool_ordering.first_extruder();
for (const auto &lt : m_wipe_tower_data.tool_ordering.layer_tools())
for (const unsigned int e : lt.extruders)
if (e != cur) { any_change = true; cur = e; }
if (! any_change)
return;
}
this->throw_if_canceled();
// Flush volumes per filament pair, mirroring the generic wipe tower path:
// full flush matrix for single extruder multi material with purging enabled,
// plain prime volume otherwise.
std::vector<float> flush_matrix(cast<float>(
get_flush_volumes_matrix(m_config.flush_volumes_matrix.values, 0, m_config.nozzle_diameter.values.size())));
std::vector<std::vector<float>> wipe_volumes;
for (unsigned int i = 0; i < number_of_extruders; ++i)
wipe_volumes.push_back(std::vector<float>(flush_matrix.begin() + i * number_of_extruders,
flush_matrix.begin() + (i + 1) * number_of_extruders));
const bool use_flush_matrix = m_config.purge_in_prime_tower && m_config.single_extruder_multi_material;
const float flush_multiplier = (float) m_config.flush_multiplier.get_at(0);
// Cancel the purge prism early: pre-scan the tool ordering for the highest
// print_z that actually has a toolchange, then drop the prism's layers above
// it so the tower stops at the last color swap (saves filament/time). This
// MUST happen before the marking loop below: ensure_perimeters_infills_order
// force-overrides the prism's extrusions on every layer (it is a dedicated
// flush object), so truncating afterwards would leave dangling overrides
// pointing into deleted layers.
{
// The tool ordering covers the WHOLE print, and the prism is a printed
// object in it. Left unbounded, the scan below sees the prism's own
// toolchanges on layers above every model object -- the prism runs past
// them by design (ramp/height compensation at the tilted ends) -- so
// last_tc_z lands at the prism's own top and the truncation cancels
// nothing. The tower ends up justifying its own existence.
//
// Nothing above the tallest printed object can require a color change,
// so bound the scan there. On MCTEST5 that is 197 toolchanges spanning
// z=154.00..193.20 with the tallest object topping out at 153.80, i.e.
// 39.4 mm of tower that no swap ever needed.
// Support layers count too: on a belt they can extend above the object's
// own top, and a toolchange there is a real one.
double obj_top_z = -1.;
for (const PrintObject *po : m_objects) {
if (po->config().belt_purge_tower_object.value)
continue;
if (!po->layers().empty())
obj_top_z = std::max(obj_top_z, po->layers().back()->print_z);
if (!po->support_layers().empty())
obj_top_z = std::max(obj_top_z, po->support_layers().back()->print_z);
}
double last_tc_z = -1.;
unsigned int cur_ext = m_wipe_tower_data.tool_ordering.first_extruder();
for (const auto &lt : m_wipe_tower_data.tool_ordering.layer_tools()) {
// layer_tools() is ordered by print_z ascending.
if (obj_top_z >= 0. && lt.print_z > obj_top_z + EPSILON)
break;
for (const unsigned int e : lt.extruders)
if (e != cur_ext) { last_tc_z = lt.print_z; cur_ext = e; }
}
// Deliberately NOT cancelling the prism outright when no object toolchange
// exists: belt_truncate_layers_above(0.) empties m_layers, and an object
// with zero layers is not something the rest of the pipeline expects. The
// GUI already declines to create a prism unless more than one filament is
// in use, so this case is a stale prism, not a hot path -- leave it whole
// rather than risk a zero-layer object.
if (last_tc_z >= 0.)
for (PrintObject *po : m_objects)
if (po->config().belt_purge_tower_object.value && !po->layers().empty()) {
po->belt_truncate_layers_above(last_tc_z);
break;
}
}
// The prism only absorbs purge at toolchange layers whose print_z coincides
// with one of its own layers.
PrintObject *prism_po = nullptr;
for (PrintObject *po : m_objects)
if (po->config().belt_purge_tower_object.value && !po->layers().empty()) { prism_po = po; break; }
float total_leftover = 0.f;
float worst_layer_leftover = 0.f;
double worst_layer_z = 0.;
unsigned int current_extruder_id = m_wipe_tower_data.tool_ordering.first_extruder();
for (auto &layer_tools : m_wipe_tower_data.tool_ordering.layer_tools()) {
float layer_leftover = 0.f;
for (const unsigned int extruder_id : layer_tools.extruders) {
if (extruder_id == current_extruder_id)
continue;
float volume_to_wipe = use_flush_matrix ?
wipe_volumes[current_extruder_id][extruder_id] * flush_multiplier :
(float) m_config.prime_volume;
float leftover = layer_tools.wiping_extrusions().mark_wiping_extrusions(*this, current_extruder_id, extruder_id,
volume_to_wipe);
layer_leftover += leftover;
current_extruder_id = extruder_id;
}
// Plastic saving: drop the prism's fills that no toolchange on this layer
// claimed. At this point the prism's OVERRIDDEN fills are exactly the
// purge; the rest would print as solid infill in the prism's own filament
// for nothing -- which is the whole prism on a layer with no toolchange
// (141 of 692 layers on MCTEST5 before the truncation fix). Perimeters are
// left alone so the bar keeps a continuous wall along the belt.
//
// Non-destructive: the entities are stashed with their positions and put
// back by belt_restore_dropped_fills() at the top of the next plan. An
// earlier version deleted them outright, which broke replanning when a
// later tool ordering needed what this one had not claimed -- that is why
// it was removed rather than kept.
if (prism_po != nullptr) {
const auto &we = layer_tools.wiping_extrusions();
prism_po->belt_drop_unclaimed_fills(
prism_po->get_layer_at_printz(layer_tools.print_z, EPSILON),
[&we, prism_po](const ExtrusionEntity *e) { return we.is_entity_overridden(e, prism_po, 0); });
}
layer_tools.wiping_extrusions().ensure_perimeters_infills_order(*this);
if (layer_leftover > 0.f) {
total_leftover += layer_leftover;
if (layer_leftover > worst_layer_leftover) {
worst_layer_leftover = layer_leftover;
worst_layer_z = layer_tools.print_z;
}
}
this->throw_if_canceled();
}
if (total_leftover > 1.f) {
this->active_step_add_warning(
PrintStateBase::WarningLevel::CRITICAL,
Slic3r::format(_u8L("The belt purge tower cannot absorb the full purge volume: %1% mm³ in total could not "
"be purged (worst layer: %2% mm³ at height %3%). The print may show color bleeding. "
"Increase the belt purge tower width, or reduce flushing volumes."),
int(std::ceil(total_leftover)), int(std::ceil(worst_layer_leftover)),
Slic3r::float_to_string_decimal_point(worst_layer_z, 2)));
}
}
// Belt mode: shift the sliced layer grid by delta. Mirrors the global_z_offset
// application in slice() — layer print_z and belt_floor_z_shift move together
// so belt floor clipping stays consistent with the shifted grid. Used by
// Print::_align_belt_purge_layers() to snap the purge prism onto the printed
// objects' layer grid; |delta| <= half a layer height, i.e. a sub-layer shift
// of the prism along the belt.
void PrintObject::belt_shift_layer_grid(double delta)
{
if (std::abs(delta) < EPSILON)
return;
for (Layer *layer : m_layers)
layer->print_z += delta;
for (SupportLayer *layer : m_support_layers)
layer->print_z += delta;
// The brim's apron bands below the first layer carry their own print_z.
for (BeltBrimBand &band : m_belt_brim_prologue)
band.print_z += delta;
m_slicing_params.belt_floor_z_shift += delta;
}
// Belt mode: drop layers strictly above z (used to cancel the purge prism early
// once there are no more toolchanges above z, so the tower stops at the last
// color swap instead of wasting filament up the rest of the belt). Each layer's
// cross-section is already sliced, so removing upper layers does not affect the
// last toolchange's coverage. Deletes the Layer objects and clears the new top
// layer's upper-layer link. Returns the number of layers removed.
size_t PrintObject::belt_truncate_layers_above(coordf_t z)
{
// A repeated plan always starts from the restored full layer set.
assert(m_belt_truncated_layers.empty());
size_t keep = m_layers.size();
while (keep > 0 && m_layers[keep - 1]->print_z > z + EPSILON)
--keep;
if (keep >= m_layers.size())
return 0;
const size_t removed = m_layers.size() - keep;
m_belt_truncated_layers.assign(m_layers.begin() + keep, m_layers.end());
m_layers.resize(keep);
if (!m_layers.empty())
m_layers.back()->upper_layer = nullptr;
return removed;
}
// Plastic saving on the purge prism: keep only the fills a toolchange claimed.
//
// Called per layer from _plan_belt_purge(), after the real-purge marking and
// BEFORE ensure_perimeters_infills_order() -- that pass force-overrides every
// remaining fill on the prism (it is a dedicated flush object), so afterwards
// everything looks claimed and nothing could be distinguished.
size_t PrintObject::belt_drop_unclaimed_fills(Layer *layer, const std::function<bool(const ExtrusionEntity*)> &claimed)
{
if (layer == nullptr)
return 0;
size_t dropped = 0;
for (size_t ri = 0; ri < layer->regions().size(); ++ri) {
LayerRegion *lr = layer->get_region(ri);
auto &ents = lr->fills.entities;
ExtrusionEntitiesPtr keep;
keep.reserve(ents.size());
for (size_t i = 0; i < ents.size(); ++i) {
if (claimed(ents[i])) {
keep.emplace_back(ents[i]);
} else {
// Stash with its original index so the restore is exact.
m_belt_dropped_fills.push_back(BeltDroppedFill{ layer, ri, i, ents[i] });
++dropped;
}
}
ents = std::move(keep);
}
return dropped;
}
void PrintObject::belt_restore_dropped_fills()
{
if (m_belt_dropped_fills.empty())
return;
// Ascending index per (layer, region): inserting in that order lands every
// entity back at its original position, because each insertion shifts only
// the entries after it, which are themselves still to be inserted.
std::stable_sort(m_belt_dropped_fills.begin(), m_belt_dropped_fills.end(),
[](const BeltDroppedFill &a, const BeltDroppedFill &b) {
if (a.layer != b.layer) return a.layer < b.layer;
if (a.region_idx != b.region_idx) return a.region_idx < b.region_idx;
return a.index < b.index;
});
for (const BeltDroppedFill &d : m_belt_dropped_fills) {
auto &ents = d.layer->get_region(d.region_idx)->fills.entities;
ents.insert(ents.begin() + std::min(d.index, ents.size()), d.entity);
}
m_belt_dropped_fills.clear();
}
void PrintObject::belt_restore_truncated_layers()
{
if (m_belt_truncated_layers.empty())
return;
m_layers.insert(m_layers.end(), m_belt_truncated_layers.begin(), m_belt_truncated_layers.end());
m_belt_truncated_layers.clear();
for (size_t i = 0; i < m_layers.size(); ++i) {
m_layers[i]->lower_layer = i == 0 ? nullptr : m_layers[i - 1];
m_layers[i]->upper_layer = i + 1 < m_layers.size() ? m_layers[i + 1] : nullptr;
}
}
} // namespace Slic3r
+66
View File
@@ -0,0 +1,66 @@
#include "BeltSliceStrategy.hpp"
#include "Model.hpp"
#include <limits>
namespace Slic3r {
void BeltSliceStrategy::apply_preslice_transforms(Transform3d &trafo,
const PrintConfig &config,
const ModelVolumePtrs &model_volumes,
double *out_belt_min_z)
{
// 1. Standalone pre-slice axis remap (works without belt mode).
const bool has_remap = BeltTransformPipeline::has_preslice_remap(config);
if (has_remap)
trafo = BeltTransformPipeline::build_preslice_remap(config) * trafo;
// 2. Belt rotation — the sole mesh-side belt transform (matching
// BeltTransformPipeline::build_forward_transform). Only active in
// belt-printer mode.
bool has_rotation = false;
if (config.belt_printer.value) {
const Matrix3d rot = BeltTransformPipeline::build_rotation_matrix(config, &has_rotation);
if (has_rotation) {
Transform3d belt_xform = Transform3d::Identity();
belt_xform.linear() = rot;
trafo = belt_xform * trafo;
}
}
if (!has_remap && !has_rotation)
return;
// 3. Z-shift — detect if the mesh clips below the build plate after the
// transforms and lift it. Each mesh vertex must be brought into object space
// via mv->get_matrix() before applying the full trafo (which is in object
// space). Missing this on assemblies (where per-volume get_matrix() positions
// each volume within the object) would compute min_z against mesh-local vertex
// coordinates rather than object-space coordinates, so volumes translated along
// the slicer's Z axis would be silently excluded from the bound check.
double min_z = std::numeric_limits<double>::max();
for (const ModelVolume *mv : model_volumes) {
if (!mv->is_model_part()) continue;
Transform3d vol_trafo = trafo * mv->get_matrix();
const auto &its = mv->mesh().its;
for (const stl_vertex &v : its.vertices) {
Vec3d vm = v.cast<double>();
Vec3d pt = vol_trafo * vm;
min_z = std::min(min_z, pt.z());
}
}
const double z_shift_val = (min_z < 0. && min_z != std::numeric_limits<double>::max()) ? -min_z : 0.;
if (z_shift_val > 0.) {
Transform3d z_shift = Transform3d::Identity();
z_shift.matrix()(2, 3) = z_shift_val;
trafo = z_shift * trafo;
}
// out_belt_min_z is only meaningful in belt mode; the standalone-remap path
// never reported it.
if (out_belt_min_z && config.belt_printer.value) {
*out_belt_min_z = (min_z != std::numeric_limits<double>::max()) ? min_z : 0.;
}
}
} // namespace Slic3r
+36
View File
@@ -0,0 +1,36 @@
#pragma once
#include "libslic3r.h"
#include "Point.hpp"
#include "BeltTransform.hpp"
#include "PrintConfig.hpp"
#include "Model.hpp"
namespace Slic3r {
// Belt printer / pre-slice transform strategy.
//
// Composes, in order, the pre-slice mesh transforms applied before slicing:
// 1. Pre-slice axis remap (standalone — works without belt mode)
// 2. Belt rotation (the sole mesh-side belt transform; shear & scale are a
// g-code-side stage, see MachineFrameTransform)
// 3. Per-object Z-shift that lifts the mesh above the build plate
//
// Isolates this belt/remap-specific logic from the generic slicing pipeline in
// PrintObjectSlice.cpp.
class BeltSliceStrategy
{
public:
// Apply the pre-slice remap + belt rotation + Z-shift to `trafo` in place.
// No-op when neither a remap nor a belt rotation is configured.
//
// out_belt_min_z (if non-null) receives the minimum mesh Z after the
// transforms, but only in belt-printer mode — the standalone-remap path
// never reported it.
static void apply_preslice_transforms(Transform3d &trafo,
const PrintConfig &config,
const ModelVolumePtrs &model_volumes,
double *out_belt_min_z = nullptr);
};
} // namespace Slic3r
+223
View File
@@ -0,0 +1,223 @@
#include "BeltTransform.hpp"
#include "Model.hpp"
#include <limits>
namespace Slic3r {
// ---- Matrix builders ------------------------------------------------------
Transform3d BeltTransformPipeline::build_preslice_remap(const PrintConfig &config)
{
Transform3d pre_remap = Transform3d::Identity();
if (!has_preslice_remap(config))
return pre_remap;
int pre_rx = int(config.preslice_remap_x.value);
int pre_ry = int(config.preslice_remap_y.value);
int pre_rz = int(config.preslice_remap_z.value);
// Each remap value selects a source axis and sign.
auto remap_column = [](int r) -> Vec3d {
int axis = r % 3;
Vec3d col = Vec3d::Zero();
if (r < 3) col[axis] = 1.0; // +axis
else if (r < 6) col[axis] = -1.0; // -axis
else col[axis] = -1.0; // Rev: max - pos = -(pos - max)
return col;
};
Matrix3d remap_lin;
remap_lin.col(0) = remap_column(pre_rx);
remap_lin.col(1) = remap_column(pre_ry);
remap_lin.col(2) = remap_column(pre_rz);
pre_remap.linear() = remap_lin;
// Translation for Rev modes (needs build volume extents).
if (pre_rx >= 6 || pre_ry >= 6 || pre_rz >= 6) {
BoundingBoxf bbox_bed(config.printable_area.values);
Vec3d vol_max(bbox_bed.max.x(), bbox_bed.max.y(),
config.printable_height.value);
Vec3d remap_trans = Vec3d::Zero();
auto add_rev = [&](int r, int out) {
if (r >= 6) remap_trans[out] = vol_max[r % 3];
};
add_rev(pre_rx, 0);
add_rev(pre_ry, 1);
add_rev(pre_rz, 2);
pre_remap.translation() = remap_trans;
}
return pre_remap;
}
Matrix3d BeltTransformPipeline::build_rotation_matrix(const PrintConfig &config, bool *has_rot_out)
{
BeltRotationAxis axis = config.belt_slice_rotation.value;
double angle_deg = config.belt_slice_rotation_angle.value;
bool active = axis != BeltRotationAxis::None && std::abs(angle_deg) > EPSILON;
if (has_rot_out) *has_rot_out = active;
if (!active)
return Matrix3d::Identity();
double angle_rad = Geometry::deg2rad(angle_deg);
Vec3d unit_axis;
switch (axis) {
case BeltRotationAxis::X: unit_axis = Vec3d::UnitX(); break;
case BeltRotationAxis::Y: unit_axis = Vec3d::UnitY(); break;
case BeltRotationAxis::Z: unit_axis = Vec3d::UnitZ(); break;
default: return Matrix3d::Identity();
}
return Eigen::AngleAxisd(angle_rad, unit_axis).toRotationMatrix();
}
Transform3d BeltTransformPipeline::build_forward_transform(const PrintConfig &config)
{
// Mesh-side belt transform: rotation applied after the pre-slice axis remap.
// (Shear & scale are a g-code-side stage, not part of the mesh transform.)
Transform3d pre_remap = build_preslice_remap(config);
Matrix3d rot = build_rotation_matrix(config);
Transform3d combined = Transform3d::Identity();
combined.linear() = rot;
combined = combined * pre_remap;
return combined;
}
// ---- Bounding box remap ---------------------------------------------------
BoundingBoxf3 BeltTransformPipeline::remap_bbox(const BoundingBoxf3 &bb, const PrintConfig &config)
{
int pre_rx = int(config.preslice_remap_x.value);
int pre_ry = int(config.preslice_remap_y.value);
int pre_rz = int(config.preslice_remap_z.value);
if (pre_rx == int(RemapAxis::PosX) &&
pre_ry == int(RemapAxis::PosY) &&
pre_rz == int(RemapAxis::PosZ))
return bb; // Identity remap.
auto remap_coord = [](int r, const Vec3d &v) -> double {
int axis = r % 3;
if (r < 3) return v[axis];
return -v[axis];
};
Vec3d mn = bb.min.cast<double>(), mx = bb.max.cast<double>();
BoundingBoxf3 rbb;
for (int i = 0; i < 8; ++i) {
Vec3d c((i & 1) ? mx.x() : mn.x(),
(i & 2) ? mx.y() : mn.y(),
(i & 4) ? mx.z() : mn.z());
Vec3d rc(remap_coord(pre_rx, c), remap_coord(pre_ry, c), remap_coord(pre_rz, c));
if (i == 0) rbb = BoundingBoxf3(rc, rc);
else rbb.merge(rc);
}
return rbb;
}
BoundingBoxf3 BeltTransformPipeline::remap_bbox(const ModelObject &model_object, const PrintConfig &config)
{
return remap_bbox(model_object.raw_bounding_box(), config);
}
// ---- Belt floor parameters ------------------------------------------------
// Shared implementation for both PrintConfig and DynamicPrintConfig.
// Template avoids duplicating the math for the two config types.
namespace {
template<typename Config>
BeltTransformPipeline::BeltHeightResult compute_belt_height_and_floor_impl(
const Config &config, const BoundingBoxf3 &bb, double original_height)
{
BeltTransformPipeline::BeltHeightResult result;
result.object_height = original_height;
// Extract the mesh rotation from config (the sole mesh-side belt transform).
BeltRotationAxis rot_axis;
double rot_angle;
if constexpr (std::is_same_v<Config, PrintConfig>) {
rot_axis = config.belt_slice_rotation.value;
rot_angle = config.belt_slice_rotation_angle.value;
} else {
// DynamicPrintConfig path
auto get_float = [&](const char *key) {
auto *opt = config.template option<ConfigOptionFloat>(key);
return opt ? opt->value : 0.0;
};
auto get_rot_axis = [&](const char *key) {
auto *opt = config.template option<ConfigOptionEnum<BeltRotationAxis>>(key);
return opt ? opt->value : BeltRotationAxis::None;
};
rot_axis = get_rot_axis("belt_slice_rotation");
rot_angle = get_float("belt_slice_rotation_angle");
}
bool has_rotation = rot_axis != BeltRotationAxis::None && std::abs(rot_angle) > EPSILON;
if (!has_rotation)
return result;
// Rotation path: sweep the 8 bbox corners through R to get the rotated height,
// then derive the belt floor (the image of machine-Z = 0 under R).
double angle_rad = Geometry::deg2rad(rot_angle);
Vec3d unit_axis;
switch (rot_axis) {
case BeltRotationAxis::X: unit_axis = Vec3d::UnitX(); break;
case BeltRotationAxis::Y: unit_axis = Vec3d::UnitY(); break;
case BeltRotationAxis::Z: unit_axis = Vec3d::UnitZ(); break;
default: unit_axis = Vec3d::UnitX(); break;
}
Matrix3d R = Eigen::AngleAxisd(angle_rad, unit_axis).toRotationMatrix();
double min_rz = std::numeric_limits<double>::max();
double max_rz = std::numeric_limits<double>::lowest();
for (int i = 0; i < 8; ++i) {
Vec3d c((i & 1) ? bb.max.x() : bb.min.x(),
(i & 2) ? bb.max.y() : bb.min.y(),
(i & 4) ? bb.max.z() : bb.min.z());
double z = (R * c).z();
min_rz = std::min(min_rz, z);
max_rz = std::max(max_rz, z);
}
result.object_height = max_rz - min_rz;
// Belt floor in slicer-frame is the image of z_machine = 0 under R.
// R(+α, X): point (·, y, 0) → (·, cos α · y, sin α · y) ⇒ z = tan(α) · y_s
// R(+α, Y): point (x, ·, 0) → (cos α · x, ·, -sin α · x) ⇒ z = -tan(α) · x_s
// R(+α, Z): point (·, ·, 0) → (·, ·, 0); no tilt → no floor
double sin_a = std::sin(angle_rad), cos_a = std::cos(angle_rad);
switch (rot_axis) {
case BeltRotationAxis::X:
result.floor_params.shear_factor = (std::abs(cos_a) > EPSILON) ? sin_a / cos_a : 0.;
result.floor_params.from_axis = 1; // Y
break;
case BeltRotationAxis::Y:
result.floor_params.shear_factor = (std::abs(cos_a) > EPSILON) ? -sin_a / cos_a : 0.;
result.floor_params.from_axis = 0; // X
break;
case BeltRotationAxis::Z:
default:
result.floor_params.shear_factor = 0.0;
result.floor_params.from_axis = 1;
break;
}
result.floor_params.z_shift = bb.min.z() + ((min_rz < 0.) ? -min_rz : 0.);
return result;
}
} // anonymous namespace
BeltTransformPipeline::BeltHeightResult BeltTransformPipeline::compute_belt_height_and_floor(
const PrintConfig &config, const BoundingBoxf3 &remapped_bbox, double original_height)
{
return compute_belt_height_and_floor_impl(config, remapped_bbox, original_height);
}
BeltTransformPipeline::BeltHeightResult BeltTransformPipeline::compute_belt_height_and_floor(
const DynamicPrintConfig &config, const BoundingBoxf3 &remapped_bbox, double original_height)
{
return compute_belt_height_and_floor_impl(config, remapped_bbox, original_height);
}
} // namespace Slic3r
+152
View File
@@ -0,0 +1,152 @@
#pragma once
#include "libslic3r.h"
#include "Point.hpp"
#include "BoundingBox.hpp"
#include "PrintConfig.hpp"
#include "Geometry.hpp"
#include <cmath>
namespace Slic3r {
class ModelObject;
// Shared belt-printer transform math.
//
// The pre-slice pipeline applied in PrintObjectSlice.cpp is:
// trafo_out = z_shift * rotation * pre_remap * trafo_in
//
// Rotation is the sole mesh-side belt transform; shear & scale are applied
// to the g-code instead (see MachineFrameTransform). This class provides the
// building blocks so every call site uses the same implementation. z_shift is
// object-dependent (computed from mesh vertex bounds) and is NOT included in
// build_forward_transform(). The machine-frame shear/scale is derived directly
// from the tilt angle in MachineFrameTransform and no longer lives here.
//
// Design note: this mesh-rotation approach replaced an earlier pre-shear
// method (now removed). While that initial pre-shear method was instrumental
// in getting belt printer slicing off the ground in the first place, its place is
// in the past. A big thank you goes to the Unlayered3D team, who recommended
// switching to a pre-slice rotation stage instead. Doing so keeps the slicing
// operation isometric — no distortion of the sliced geometry — while the
// non-orthogonal machine-axis compensation is confined to a g-code-side shear/scale
// derived from the same tilt angle.
//
// This fixed a number of issues, including several issues noticed by hotcubcar
// regarding adaptive infills not working, gyroid becoming anisotropic, and more
// that were all mostly resolved as a result of the switch.
//
// This also means that the pre-slice rotation transform methodology can be used
// more cleanly on non-belt printers.
// - HarrierPigeon (Joseph Robertson)
class BeltTransformPipeline
{
public:
// ---- Identity checks --------------------------------------------------
static bool has_preslice_remap(const PrintConfig &config)
{
return int(config.preslice_remap_x.value) != int(RemapAxis::PosX) ||
int(config.preslice_remap_y.value) != int(RemapAxis::PosY) ||
int(config.preslice_remap_z.value) != int(RemapAxis::PosZ);
}
// Overload accepting DynamicPrintConfig (used in static slicing_parameters).
static bool has_preslice_remap(const DynamicPrintConfig &config)
{
auto get_int = [&](const char *key) -> int {
auto *opt = config.option<ConfigOptionEnum<RemapAxis>>(key);
return opt ? int(opt->value) : 0;
};
return get_int("preslice_remap_x") != int(RemapAxis::PosX) ||
get_int("preslice_remap_y") != int(RemapAxis::PosY) ||
get_int("preslice_remap_z") != int(RemapAxis::PosZ);
}
static bool has_rotation(const PrintConfig &config)
{
return config.belt_slice_rotation.value != BeltRotationAxis::None &&
std::abs(config.belt_slice_rotation_angle.value) > EPSILON;
}
// Physical belt tilt derived from the slicing rotation — the single source of
// truth for bed rendering, support gravity tilt and the bed-exclusion
// projection. Returns the tilt magnitude in degrees split onto the X and Y
// build-plate tilt axes according to the rotation axis:
// rotation about X → tilt_x = angle (gantry tilts in the YZ plane)
// rotation about Y → tilt_y = angle (gantry tilts in the XZ plane)
// rotation about Z / None → no tilt (in-plane spin doesn't tilt the belt)
// The magnitude uses abs(angle) so a negative rotation still reports a positive
// physical tilt.
struct PhysicalTilt { double tilt_x_deg = 0.; double tilt_y_deg = 0.; };
static PhysicalTilt physical_tilt(BeltRotationAxis axis, double angle_deg)
{
PhysicalTilt t;
double mag = std::abs(angle_deg);
switch (axis) {
case BeltRotationAxis::X: t.tilt_x_deg = mag; break;
case BeltRotationAxis::Y: t.tilt_y_deg = mag; break;
default: break; // Z / None: no physical tilt
}
return t;
}
static PhysicalTilt physical_tilt(const PrintConfig &config)
{
return physical_tilt(config.belt_slice_rotation.value,
config.belt_slice_rotation_angle.value);
}
// ---- Matrix builders --------------------------------------------------
// Build the pre-slice axis remap transform (includes Rev-mode translation).
static Transform3d build_preslice_remap(const PrintConfig &config);
// Build the 3x3 rotation matrix from belt_slice_rotation* config.
// Returns Identity if rotation axis is None or angle is ~0.
// Also sets has_rot_out if non-null.
static Matrix3d build_rotation_matrix(const PrintConfig &config, bool *has_rot_out = nullptr);
// Combined forward transform (rotation * pre_remap) — the mesh-side belt
// transform that BeltSliceStrategy applies and BeltBackTransform inverts.
// Does NOT include the per-object Z-shift.
static Transform3d build_forward_transform(const PrintConfig &config);
// ---- Bounding box remap -----------------------------------------------
// Remap a bounding box through the pre-slice axis remap.
// Returns the original bbox if remap is identity.
static BoundingBoxf3 remap_bbox(const BoundingBoxf3 &bb, const PrintConfig &config);
static BoundingBoxf3 remap_bbox(const ModelObject &model_object, const PrintConfig &config);
// ---- Belt floor parameters --------------------------------------------
struct BeltFloorParams {
double shear_factor = 0.0;
int from_axis = 1;
double z_shift = 0.0;
};
// Result of computing belt height + floor params.
struct BeltHeightResult {
double object_height; // Effective object height after shear/scale
BeltFloorParams floor_params;
};
// Compute effective object height and belt floor parameters from config
// and pre-remapped bounding box. original_height is the input height
// (bb.size().z() or model_object.max_z()).
static BeltHeightResult compute_belt_height_and_floor(
const PrintConfig &config, const BoundingBoxf3 &remapped_bbox,
double original_height);
// Overload for DynamicPrintConfig (used by static slicing_parameters).
static BeltHeightResult compute_belt_height_and_floor(
const DynamicPrintConfig &config, const BoundingBoxf3 &remapped_bbox,
double original_height);
};
} // namespace Slic3r
+14 -6
View File
@@ -32,11 +32,6 @@
#include <boost/log/trivial.hpp>
#include <utility>
#include <vector>
#include "Config.hpp"
#include "ExtrusionEntityCollection.hpp"
#include "MultiMaterialSegmentation.hpp"
#include "SurfaceCollection.hpp"
#include "TriangleMesh.hpp"
#ifndef NDEBUG
// #define BRIM_DEBUG_TO_SVG
@@ -474,7 +469,9 @@ static ExPolygons outer_inner_brim_area(const Print& print,
const bool use_brim_ears = object->config().brim_type == btPainted;
const bool use_inner_brim_ears = (use_auto_brim_ears || use_brim_ears) && !object->config().brim_ears_outer_only.value;
const bool has_inner_brim = brim_type == btInnerOnly || brim_type == btOuterAndInner || use_inner_brim_ears;
const bool has_outer_brim = brim_type == btOuterOnly || brim_type == btOuterAndInner || brim_type == btAutoBrim || use_auto_brim_ears || use_brim_ears;
// btLeadingEdgeOnly is a belt-printer mode; on a flat bed there is no leading
// edge, so it degrades to an ordinary outer brim rather than silently to none.
const bool has_outer_brim = brim_type == btOuterOnly || brim_type == btOuterAndInner || brim_type == btAutoBrim || brim_type == btLeadingEdgeOnly || use_auto_brim_ears || use_brim_ears;
coord_t ear_detection_length = scale_(object->config().brim_ears_detection_length.value);
coordf_t brim_ears_max_angle = object->config().brim_ears_max_angle.value;
//ORCA: Select brim base slices from EFC-compensated outline when enabled.
@@ -889,6 +886,17 @@ void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_
std::vector<unsigned int>& printExtruders,
std::map<ObjectInstanceID, ExPolygons>* objectBrimAreasByInstanceOut)
{
// Belt printers never use the flat plate brim.
//
// With a tilted belt the brim has to be laid onto the belt plane over many layers,
// which BeltBrim.cpp does during posSupportMaterial. With an untilted belt this
// could in principle fall through and produce an ordinary brim, but it would never
// reach the G-code: the plate brim is emitted out of skirt_brim_groups(), which
// _make_skirt() builds, and that returns early for every belt printer. Running the
// generator anyway would just burn time on geometry nobody prints.
if (print.config().belt_printer.value)
return;
std::map<ObjectInstanceID, ExPolygons> brimAreaMap;
Flow flow = print.brim_flow();
ExPolygons islands_area_ex = outer_inner_brim_area(print,
+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.
-4
View File
@@ -90,10 +90,6 @@
#include <string>
#include <vector>
#include <utility>
#include "libslic3r/Color.hpp"
#include "libslic3r/TriangleMesh.hpp"
class Geom2d_TrimmedCurve;
namespace Slic3r {
-2
View File
@@ -62,8 +62,6 @@
#include <vector>
#include <utility>
class Geom_TrimmedCurve;
namespace Slic3r {
// Single source of truth for the weld tolerance the viewport and the kernel share.
-1
View File
@@ -11,7 +11,6 @@
#include <unordered_map>
#include <vector>
#include <utility>
#include "libslic3r/Point.hpp"
namespace Slic3r {
+1 -4
View File
@@ -6,12 +6,9 @@
// hand-rolled SketchConstraints: full constraint set, real DoF counting, and
// over-constrained (bad-constraint) detection. Solves on a fixed 2D XY workplane.
#include "libslic3r/CAD/SketchEngine.hpp"
#include <vector>
namespace Slic3r { enum class SketchPointRole; }
namespace Slic3r { struct SketchEntity; }
namespace Slic3r { struct SketchEntityConstraintDef; }
namespace Slic3r {
struct SketchSolveResult {
+21
View File
@@ -85,6 +85,17 @@ set(lisbslic3r_sources
BoundingBox.hpp
BridgeDetector.cpp
BridgeDetector.hpp
BeltBrim.cpp
BeltBrim.hpp
BeltGCode.cpp
BeltGCode.hpp
BeltPurge.cpp
BeltSliceStrategy.cpp
BeltSliceStrategy.hpp
BeltTransform.cpp
BeltTransform.hpp
FirstLayerPlane.cpp
FirstLayerPlane.hpp
Brim.cpp
BrimEarsPoint.hpp
Brim.hpp
@@ -232,6 +243,14 @@ set(lisbslic3r_sources
GCode/AdaptivePAProcessor.hpp
GCode/AvoidCrossingPerimeters.cpp
GCode/AvoidCrossingPerimeters.hpp
GCode/BeltBackTransform.cpp
GCode/BeltBackTransform.hpp
GCode/MachineFrameTransform.cpp
GCode/MachineFrameTransform.hpp
GCode/BeltKinematics.cpp
GCode/BeltKinematics.hpp
GCode/MachineKinematics.cpp
GCode/MachineKinematics.hpp
GCode/ConflictChecker.cpp
GCode/ConflictChecker.hpp
GCode/CoolingBuffer.cpp
@@ -456,6 +475,8 @@ set(lisbslic3r_sources
SlicingAdaptive.hpp
Slicing.cpp
Slicing.hpp
Support/BeltFloorContext.cpp
Support/BeltFloorContext.hpp
Support/SupportCommon.cpp
Support/SupportCommon.hpp
Support/SupportLayer.hpp
+2
View File
@@ -5,8 +5,10 @@
#include <cstddef>
#include <utility>
#include <math.h>
#include "Geometry.hpp"
#include "Point.hpp"
#include "Line.hpp"
#include "Polygon.hpp"
#include "libslic3r.h"
+1 -2
View File
@@ -2,11 +2,10 @@
#define slic3r_Circle_hpp_
#include "Point.hpp"
#include "Line.hpp"
#include "libslic3r.h"
#include <cmath>
namespace Slic3r { class Line; }
namespace Slic3r {
constexpr double ZERO_TOLERANCE = 0.000005;
+1 -1
View File
@@ -12,6 +12,7 @@
#include "ClipperUtils.hpp"
#include "BoundingBox.hpp"
#include "ExPolygon.hpp"
#include "Geometry.hpp"
#include "Point.hpp"
#include "Polygon.hpp"
#include "Polyline.hpp"
@@ -23,7 +24,6 @@
#include <clipper2/clipper.h>
#include <utility>
#include <vector>
#include "ExtrusionEntity.hpp"
// #define CLIPPER_UTILS_DEBUG
-1
View File
@@ -3,7 +3,6 @@
#include <clipper2/clipper2_z.hpp>
#include <cstddef>
#include "Point.hpp"
namespace Slic3r {
namespace ClipperZUtils {
-1
View File
@@ -13,7 +13,6 @@
#include <libslic3r/ClipperUtils.hpp>
#include <libslic3r/Point.hpp>
#include <libslic3r/ExPolygon.hpp>
#include "Polygon.hpp"
namespace Slic3r {
-3
View File
@@ -14,9 +14,6 @@
#include <math.h>
#include <string>
#include <utility>
#include "Config.hpp"
#include "Polyline.hpp"
#include "PrintConfig.hpp"
namespace Slic3r {
+3
View File
@@ -1,4 +1,7 @@
#include "CustomGCode.hpp"
#include "Config.hpp"
#include "GCode.hpp"
#include "GCodeWriter.hpp"
#include <vector>
#include <utility>
#include <cstddef>
+1 -3
View File
@@ -6,9 +6,7 @@
#include <vector>
#include <admesh/stl.h> // indexed_triangle_set
#include "ExPolygon.hpp"
namespace Slic3r::Emboss { class IProject3d; }
namespace Slic3r::Emboss { class IProjection; }
#include "Emboss.hpp" // IProjection
namespace Slic3r{
-1
View File
@@ -19,7 +19,6 @@
#include <optional>
#include <math.h>
#include <cmath>
#include "BoundingBox.hpp"
namespace Slic3r {
+1 -1
View File
@@ -8,6 +8,7 @@
#include "ElephantFootCompensation.hpp"
#include "Flow.hpp"
#include "Geometry.hpp"
#include "SVG.hpp"
#include "Utils.hpp"
#include <algorithm>
@@ -18,7 +19,6 @@
#include <utility>
#include <math.h>
#include <limits>
#include "ExtrusionEntity.hpp"
// #define CONTOUR_DISTANCE_DEBUG_SVG
@@ -1,6 +1,7 @@
#ifndef slic3r_ElephantFootCompensation_hpp_
#define slic3r_ElephantFootCompensation_hpp_
#include "libslic3r.h"
#include "ExPolygon.hpp"
#include <vector>
+2
View File
@@ -1,5 +1,6 @@
#include "BoundingBox.hpp"
#include "ExPolygon.hpp"
#include "Exception.hpp"
#include "Geometry/MedialAxis.hpp"
#include "Point.hpp"
#include "MultiPoint.hpp"
@@ -7,6 +8,7 @@
#include "Line.hpp"
#include "ClipperUtils.hpp"
#include "Polyline.hpp"
#include "SVG.hpp"
#include "libslic3r.h"
#include <algorithm>
#include <cassert>
+5
View File
@@ -403,6 +403,11 @@ inline void translate(ExPolygons &expolys, const Point &p) {
expoly.translate(p);
}
inline void translate(Polygons &polys, const Point &p) {
for (Polygon &poly : polys)
poly.translate(p);
}
inline void polygons_append(Polygons &dst, const ExPolygon &src)
{
dst.reserve(dst.size() + src.holes.size() + 1);
-1
View File
@@ -5,7 +5,6 @@
#include <vector>
#include <utility>
#include <algorithm>
#include "Polygon.hpp"
using namespace Slic3r;
// IMPROVE: use one dimensional vector for polygons offset with searching by std::lower_bound
-1
View File
@@ -6,7 +6,6 @@
#include <cassert>
#include <algorithm>
#include <cmath>
#include "Config.hpp"
namespace Slic3r {

Some files were not shown because too many files have changed in this diff Show More