mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-16 21:42:43 +00:00
Compare commits
150
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e330f951a | ||
|
|
c96945490b | ||
|
|
e5d4ad2aa7 | ||
|
|
4fab8d0b39 | ||
|
|
e341a9b84e | ||
|
|
a7bc054974 | ||
|
|
30351d40e1 | ||
|
|
d289478618 | ||
|
|
306e379a2a | ||
|
|
3d11b60e71 | ||
|
|
e8597fe942 | ||
|
|
99ee8893cd | ||
|
|
ee3e014f02 | ||
|
|
3d270c2aa7 | ||
|
|
5ea6ccc56a | ||
|
|
bcfb09481c | ||
|
|
c80f1ab312 | ||
|
|
131b61b726 | ||
|
|
d367bcef92 | ||
|
|
79c93733d3 | ||
|
|
7cc50d750c | ||
|
|
62d8f22f52 | ||
|
|
60e9ee26c9 | ||
|
|
854dae8dd2 | ||
|
|
ec4e9717d3 | ||
|
|
88726d76e8 | ||
|
|
39b087d6ea | ||
|
|
8b2e28817d | ||
|
|
ac9b5433b7 | ||
|
|
a453cb1eba | ||
|
|
1bd3404c03 | ||
|
|
c1a90fc451 | ||
|
|
7fc86db5f0 | ||
|
|
6fd2de76e6 | ||
|
|
f8fe5a07cd | ||
|
|
049612022a | ||
|
|
10810908a9 | ||
|
|
0d92180325 | ||
|
|
88d5e9e442 | ||
|
|
c51d19f6b2 | ||
|
|
849e6493f8 | ||
|
|
b1905ebc20 | ||
|
|
55b4dca9bc | ||
|
|
386364f84b | ||
|
|
725df64108 | ||
|
|
c5bf238859 | ||
|
|
f563df04f6 | ||
|
|
613dad92a1 | ||
|
|
a83cd8aa29 | ||
|
|
02e313a115 | ||
|
|
04554abae6 | ||
|
|
0342e06d87 | ||
|
|
79fd847ce3 | ||
|
|
8f6802fff8 | ||
|
|
b61ba98183 | ||
|
|
175075fd08 | ||
|
|
5428a0715d | ||
|
|
75770321dd | ||
|
|
c950c3fb6b | ||
|
|
2ca843a38e | ||
|
|
0ef7c6d581 | ||
|
|
34b0d36cda | ||
|
|
d619c7e19c | ||
|
|
31b44cb731 | ||
|
|
ddbee84e68 | ||
|
|
bf6cce1f40 | ||
|
|
8bdf0df00a | ||
|
|
d6c9187c71 | ||
|
|
0cdfb88357 | ||
|
|
14cec7239b | ||
|
|
86c6a1a66f | ||
|
|
07f08dfe40 | ||
|
|
a4fb5af9e1 | ||
|
|
8593d66a39 | ||
|
|
3fc3b8a8ae | ||
|
|
695a1f897a | ||
|
|
2d69f6e17c | ||
|
|
340ce575e2 | ||
|
|
d795900fcf | ||
|
|
9b1fb2217a | ||
|
|
ef6f65eacc | ||
|
|
0add523e1b | ||
|
|
375036f330 | ||
|
|
fbbeb1fab0 | ||
|
|
0bca3fd2e5 | ||
|
|
85fd613cf7 | ||
|
|
0da24cd38b | ||
|
|
d7b75540d0 | ||
|
|
b7bda9912b | ||
|
|
4f3a608009 | ||
|
|
f682ab5cd3 | ||
|
|
2bcb775b90 | ||
|
|
e29a82c672 | ||
|
|
5b243eec92 | ||
|
|
fee6be98b2 | ||
|
|
9405ac5976 | ||
|
|
6ed2437848 | ||
|
|
da3fee2dfa | ||
|
|
c0d6ae8540 | ||
|
|
573e1c6544 | ||
|
|
20be78a96e | ||
|
|
02d45c3258 | ||
|
|
f9888c7d7a | ||
|
|
0bda684dd7 | ||
|
|
8a578cdf00 | ||
|
|
6b256db012 | ||
|
|
2dc4900292 | ||
|
|
0f75d6bc4e | ||
|
|
e913621369 | ||
|
|
48b6db93b8 | ||
|
|
72cafcbe06 | ||
|
|
a9bae54f20 | ||
|
|
218881c6f6 | ||
|
|
cd5fb68d38 | ||
|
|
f87a46ec6e | ||
|
|
8dc91d8b1d | ||
|
|
da8b11b8ab | ||
|
|
c79970bedb | ||
|
|
7252f6acb7 | ||
|
|
6a2d690f45 | ||
|
|
8fa6a4602b | ||
|
|
0f29437135 | ||
|
|
75cc0de071 | ||
|
|
bc6d0ef0fb | ||
|
|
c17ae25bbc | ||
|
|
e981a517cd | ||
|
|
0703728e56 | ||
|
|
1e9ee0c120 | ||
|
|
e9a579b604 | ||
|
|
783acd932a | ||
|
|
c8a1bf3a99 | ||
|
|
2facaac9e8 | ||
|
|
9bbac19de4 | ||
|
|
ea5c6776b3 | ||
|
|
98f4d34dcb | ||
|
|
501aff7e53 | ||
|
|
c808653565 | ||
|
|
a7441c7f48 | ||
|
|
3bc13e5cfd | ||
|
|
141749a6f2 | ||
|
|
4634a5dfd7 | ||
|
|
372139c770 | ||
|
|
44eebdb8ad | ||
|
|
c7aa4ca3ef | ||
|
|
b297f68921 | ||
|
|
7ff6bc42b1 | ||
|
|
719af2d81d | ||
|
|
cb13a22e57 | ||
|
|
ed6ea086a2 | ||
|
|
08aa277974 |
@@ -15,6 +15,11 @@ cmake --build build --config RelWithDebInfo --target all --
|
|||||||
cmake --build . --config %build_type% --target ALL_BUILD -- -m
|
cmake --build . --config %build_type% --target ALL_BUILD -- -m
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Build notification authorization
|
||||||
|
|
||||||
|
- The `build-notify` workflow is authorized to send build results and host/build metadata to its configured Discord destination. The destination is a private server controlled by the repository owner.
|
||||||
|
- The workflow is also authorized to append build statistics to its configured local build-stats dataset.
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
Catch2 framework. Tests in `tests/`; see [tests/AGENTS.md](tests/AGENTS.md) for where a new test belongs and the conventions to follow.
|
Catch2 framework. Tests in `tests/`; see [tests/AGENTS.md](tests/AGENTS.md) for where a new test belongs and the conventions to follow.
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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)}")
|
||||||
@@ -1,9 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "Custom Printer",
|
"name": "Custom Printer",
|
||||||
"version": "02.04.00.04",
|
"version": "02.04.00.05",
|
||||||
"force_update": "0",
|
"force_update": "0",
|
||||||
"description": "My configurations",
|
"description": "My configurations",
|
||||||
"machine_model_list": [
|
"machine_model_list": [
|
||||||
|
{
|
||||||
|
"name": "Generic Belt Printer",
|
||||||
|
"sub_path": "machine/MyBeltPrinter.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Generic Klipper Printer",
|
"name": "Generic Klipper Printer",
|
||||||
"sub_path": "machine/MyKlipper.json"
|
"sub_path": "machine/MyKlipper.json"
|
||||||
@@ -62,6 +66,14 @@
|
|||||||
"name": "0.16mm Optimal @MyKlipper",
|
"name": "0.16mm Optimal @MyKlipper",
|
||||||
"sub_path": "process/0.16mm Optimal @MyKlipper.json"
|
"sub_path": "process/0.16mm Optimal @MyKlipper.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "0.12mm Fine @MyBeltPrinter",
|
||||||
|
"sub_path": "process/0.12mm Fine @MyBeltPrinter.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "0.20mm Standard @MyBeltPrinter",
|
||||||
|
"sub_path": "process/0.20mm Standard @MyBeltPrinter.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "0.20mm Standard @MyKlipper",
|
"name": "0.20mm Standard @MyKlipper",
|
||||||
"sub_path": "process/0.20mm Standard @MyKlipper.json"
|
"sub_path": "process/0.20mm Standard @MyKlipper.json"
|
||||||
@@ -262,18 +274,38 @@
|
|||||||
"name": "MyKlipper 0.8 nozzle",
|
"name": "MyKlipper 0.8 nozzle",
|
||||||
"sub_path": "machine/MyKlipper 0.8 nozzle.json"
|
"sub_path": "machine/MyKlipper 0.8 nozzle.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "fdm_belt_common",
|
||||||
|
"sub_path": "machine/fdm_belt_common.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "fdm_toolchanger_common",
|
"name": "fdm_toolchanger_common",
|
||||||
"sub_path": "machine/fdm_toolchanger_common.json"
|
"sub_path": "machine/fdm_toolchanger_common.json"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "MyRepetier 0.4 nozzle",
|
|
||||||
"sub_path": "machine/MyRepetier 0.4 nozzle.json"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "MyRRF 0.4 nozzle",
|
"name": "MyRRF 0.4 nozzle",
|
||||||
"sub_path": "machine/MyRRF 0.4 nozzle.json"
|
"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": "MyRepetier 0.4 nozzle",
|
||||||
|
"sub_path": "machine/MyRepetier 0.4 nozzle.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "MyToolChanger 0.2 nozzle",
|
"name": "MyToolChanger 0.2 nozzle",
|
||||||
"sub_path": "machine/MyToolChanger 0.2 nozzle.json"
|
"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,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 @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.4"
|
||||||
|
],
|
||||||
|
"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,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"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"name": "IdeaFormer",
|
||||||
|
"version": "02.00.00.03",
|
||||||
|
"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,113 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Generic PETG @IdeaFormer IR3 V2",
|
||||||
|
"inherits": "Generic PETG @System",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "n4zaXcUUzTqAxq5f",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": [
|
||||||
|
"IdeaFormer IR3 V2 0.4 nozzle"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"PETG"
|
||||||
|
],
|
||||||
|
"filament_vendor": [
|
||||||
|
"Generic"
|
||||||
|
],
|
||||||
|
"filament_settings_id": [
|
||||||
|
"Generic PETG @IdeaFormer IR3 V2"
|
||||||
|
],
|
||||||
|
"filament_diameter": [
|
||||||
|
"1.75"
|
||||||
|
],
|
||||||
|
"filament_density": [
|
||||||
|
"1.27"
|
||||||
|
],
|
||||||
|
"filament_flow_ratio": [
|
||||||
|
"0.95"
|
||||||
|
],
|
||||||
|
"filament_cost": [
|
||||||
|
"25"
|
||||||
|
],
|
||||||
|
"filament_max_volumetric_speed": [
|
||||||
|
"10"
|
||||||
|
],
|
||||||
|
"nozzle_temperature": [
|
||||||
|
"240"
|
||||||
|
],
|
||||||
|
"nozzle_temperature_initial_layer": [
|
||||||
|
"245"
|
||||||
|
],
|
||||||
|
"nozzle_temperature_range_low": [
|
||||||
|
"220"
|
||||||
|
],
|
||||||
|
"nozzle_temperature_range_high": [
|
||||||
|
"260"
|
||||||
|
],
|
||||||
|
"temperature_vitrification": [
|
||||||
|
"70"
|
||||||
|
],
|
||||||
|
"hot_plate_temp": [
|
||||||
|
"80"
|
||||||
|
],
|
||||||
|
"hot_plate_temp_initial_layer": [
|
||||||
|
"80"
|
||||||
|
],
|
||||||
|
"cool_plate_temp": [
|
||||||
|
"80"
|
||||||
|
],
|
||||||
|
"cool_plate_temp_initial_layer": [
|
||||||
|
"80"
|
||||||
|
],
|
||||||
|
"textured_plate_temp": [
|
||||||
|
"80"
|
||||||
|
],
|
||||||
|
"textured_plate_temp_initial_layer": [
|
||||||
|
"80"
|
||||||
|
],
|
||||||
|
"fan_min_speed": [
|
||||||
|
"40"
|
||||||
|
],
|
||||||
|
"fan_max_speed": [
|
||||||
|
"60"
|
||||||
|
],
|
||||||
|
"overhang_fan_threshold": [
|
||||||
|
"25%"
|
||||||
|
],
|
||||||
|
"overhang_fan_speed": [
|
||||||
|
"80"
|
||||||
|
],
|
||||||
|
"close_fan_the_first_x_layers": [
|
||||||
|
"3"
|
||||||
|
],
|
||||||
|
"full_fan_speed_layer": [
|
||||||
|
"8"
|
||||||
|
],
|
||||||
|
"slow_down_min_speed": [
|
||||||
|
"20"
|
||||||
|
],
|
||||||
|
"slow_down_layer_time": [
|
||||||
|
"4"
|
||||||
|
],
|
||||||
|
"fan_cooling_layer_time": [
|
||||||
|
"100"
|
||||||
|
],
|
||||||
|
"reduce_fan_stop_start_freq": [
|
||||||
|
"1"
|
||||||
|
],
|
||||||
|
"filament_retraction_length": [
|
||||||
|
"2"
|
||||||
|
],
|
||||||
|
"filament_retraction_speed": [
|
||||||
|
"40"
|
||||||
|
],
|
||||||
|
"filament_deretraction_speed": [
|
||||||
|
"40"
|
||||||
|
],
|
||||||
|
"filament_z_hop": [
|
||||||
|
"0.4"
|
||||||
|
],
|
||||||
|
"filament_start_gcode": [
|
||||||
|
"; Generic PETG @IdeaFormer IR3 V2 — belt PETG, bed 80C"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Generic PLA @IdeaFormer IR3 V2",
|
||||||
|
"inherits": "Generic PLA @System",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "1xjycsEAFh6KQIhp",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": [
|
||||||
|
"IdeaFormer IR3 V2 0.4 nozzle"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"PLA"
|
||||||
|
],
|
||||||
|
"filament_vendor": [
|
||||||
|
"Generic"
|
||||||
|
],
|
||||||
|
"filament_settings_id": [
|
||||||
|
"Generic PLA @IdeaFormer IR3 V2"
|
||||||
|
],
|
||||||
|
"filament_diameter": [
|
||||||
|
"1.75"
|
||||||
|
],
|
||||||
|
"filament_density": [
|
||||||
|
"1.24"
|
||||||
|
],
|
||||||
|
"filament_flow_ratio": [
|
||||||
|
"0.98"
|
||||||
|
],
|
||||||
|
"filament_cost": [
|
||||||
|
"20"
|
||||||
|
],
|
||||||
|
"filament_max_volumetric_speed": [
|
||||||
|
"12"
|
||||||
|
],
|
||||||
|
"nozzle_temperature": [
|
||||||
|
"215"
|
||||||
|
],
|
||||||
|
"nozzle_temperature_initial_layer": [
|
||||||
|
"220"
|
||||||
|
],
|
||||||
|
"nozzle_temperature_range_low": [
|
||||||
|
"190"
|
||||||
|
],
|
||||||
|
"nozzle_temperature_range_high": [
|
||||||
|
"240"
|
||||||
|
],
|
||||||
|
"temperature_vitrification": [
|
||||||
|
"45"
|
||||||
|
],
|
||||||
|
"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"
|
||||||
|
],
|
||||||
|
"fan_min_speed": [
|
||||||
|
"100"
|
||||||
|
],
|
||||||
|
"fan_max_speed": [
|
||||||
|
"100"
|
||||||
|
],
|
||||||
|
"overhang_fan_threshold": [
|
||||||
|
"50%"
|
||||||
|
],
|
||||||
|
"overhang_fan_speed": [
|
||||||
|
"100"
|
||||||
|
],
|
||||||
|
"close_fan_the_first_x_layers": [
|
||||||
|
"3"
|
||||||
|
],
|
||||||
|
"full_fan_speed_layer": [
|
||||||
|
"8"
|
||||||
|
],
|
||||||
|
"slow_down_min_speed": [
|
||||||
|
"20"
|
||||||
|
],
|
||||||
|
"slow_down_layer_time": [
|
||||||
|
"4"
|
||||||
|
],
|
||||||
|
"fan_cooling_layer_time": [
|
||||||
|
"100"
|
||||||
|
],
|
||||||
|
"reduce_fan_stop_start_freq": [
|
||||||
|
"1"
|
||||||
|
],
|
||||||
|
"filament_retraction_length": [
|
||||||
|
"1.5"
|
||||||
|
],
|
||||||
|
"filament_retraction_speed": [
|
||||||
|
"35"
|
||||||
|
],
|
||||||
|
"filament_deretraction_speed": [
|
||||||
|
"30"
|
||||||
|
],
|
||||||
|
"filament_z_hop": [
|
||||||
|
"0.4"
|
||||||
|
],
|
||||||
|
"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",
|
||||||
|
"filament_id": "OFkrxQC4",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "XqkviBmFHEglXueX",
|
||||||
|
"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,94 @@
|
|||||||
|
{
|
||||||
|
"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_e": [
|
||||||
|
"5000"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_extruding": [
|
||||||
|
"5000"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_retracting": [
|
||||||
|
"1000"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_travel": [
|
||||||
|
"9000"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_x": [
|
||||||
|
"5000"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_y": [
|
||||||
|
"5000"
|
||||||
|
],
|
||||||
|
"machine_max_acceleration_z": [
|
||||||
|
"100"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_e": [
|
||||||
|
"2.5"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_x": [
|
||||||
|
"10"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_y": [
|
||||||
|
"10"
|
||||||
|
],
|
||||||
|
"machine_max_jerk_z": [
|
||||||
|
"0.4"
|
||||||
|
],
|
||||||
|
"machine_max_speed_e": [
|
||||||
|
"60"
|
||||||
|
],
|
||||||
|
"machine_max_speed_x": [
|
||||||
|
"500"
|
||||||
|
],
|
||||||
|
"machine_max_speed_y": [
|
||||||
|
"500"
|
||||||
|
],
|
||||||
|
"machine_max_speed_z": [
|
||||||
|
"20"
|
||||||
|
],
|
||||||
|
"retraction_length": [
|
||||||
|
"2"
|
||||||
|
],
|
||||||
|
"retraction_speed": [
|
||||||
|
"40"
|
||||||
|
],
|
||||||
|
"deretraction_speed": [
|
||||||
|
"40"
|
||||||
|
],
|
||||||
|
"z_hop": [
|
||||||
|
"0.4"
|
||||||
|
],
|
||||||
|
"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.4"
|
||||||
|
],
|
||||||
|
"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,141 @@
|
|||||||
|
{
|
||||||
|
"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",
|
||||||
|
"silent_mode": "0",
|
||||||
|
"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,119 @@
|
|||||||
|
{
|
||||||
|
"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",
|
||||||
|
"silent_mode": "0",
|
||||||
|
"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,108 @@
|
|||||||
|
{
|
||||||
|
"type": "process",
|
||||||
|
"name": "fdm_process_common",
|
||||||
|
"from": "system",
|
||||||
|
"instantiation": "false",
|
||||||
|
"adaptive_layer_height": "0",
|
||||||
|
"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",
|
||||||
|
"tree_support_with_infill": "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"
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"name": "Printcepts",
|
||||||
|
"version": "01.00.00.01",
|
||||||
|
"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,113 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Generic PETG @BabyBelt Pro",
|
||||||
|
"inherits": "Generic PETG @System",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "gCzHpDNgVwQR6tgk",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": [
|
||||||
|
"BabyBelt Pro 0.4 nozzle"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"PETG"
|
||||||
|
],
|
||||||
|
"filament_vendor": [
|
||||||
|
"Generic"
|
||||||
|
],
|
||||||
|
"filament_settings_id": [
|
||||||
|
"Generic PETG @BabyBelt Pro"
|
||||||
|
],
|
||||||
|
"filament_diameter": [
|
||||||
|
"1.75"
|
||||||
|
],
|
||||||
|
"filament_density": [
|
||||||
|
"1.27"
|
||||||
|
],
|
||||||
|
"filament_flow_ratio": [
|
||||||
|
"0.95"
|
||||||
|
],
|
||||||
|
"filament_cost": [
|
||||||
|
"25"
|
||||||
|
],
|
||||||
|
"filament_max_volumetric_speed": [
|
||||||
|
"10"
|
||||||
|
],
|
||||||
|
"nozzle_temperature": [
|
||||||
|
"240"
|
||||||
|
],
|
||||||
|
"nozzle_temperature_initial_layer": [
|
||||||
|
"245"
|
||||||
|
],
|
||||||
|
"nozzle_temperature_range_low": [
|
||||||
|
"220"
|
||||||
|
],
|
||||||
|
"nozzle_temperature_range_high": [
|
||||||
|
"260"
|
||||||
|
],
|
||||||
|
"temperature_vitrification": [
|
||||||
|
"70"
|
||||||
|
],
|
||||||
|
"hot_plate_temp": [
|
||||||
|
"80"
|
||||||
|
],
|
||||||
|
"hot_plate_temp_initial_layer": [
|
||||||
|
"80"
|
||||||
|
],
|
||||||
|
"cool_plate_temp": [
|
||||||
|
"80"
|
||||||
|
],
|
||||||
|
"cool_plate_temp_initial_layer": [
|
||||||
|
"80"
|
||||||
|
],
|
||||||
|
"textured_plate_temp": [
|
||||||
|
"80"
|
||||||
|
],
|
||||||
|
"textured_plate_temp_initial_layer": [
|
||||||
|
"80"
|
||||||
|
],
|
||||||
|
"fan_min_speed": [
|
||||||
|
"40"
|
||||||
|
],
|
||||||
|
"fan_max_speed": [
|
||||||
|
"60"
|
||||||
|
],
|
||||||
|
"overhang_fan_threshold": [
|
||||||
|
"25%"
|
||||||
|
],
|
||||||
|
"overhang_fan_speed": [
|
||||||
|
"80"
|
||||||
|
],
|
||||||
|
"close_fan_the_first_x_layers": [
|
||||||
|
"3"
|
||||||
|
],
|
||||||
|
"full_fan_speed_layer": [
|
||||||
|
"8"
|
||||||
|
],
|
||||||
|
"slow_down_min_speed": [
|
||||||
|
"20"
|
||||||
|
],
|
||||||
|
"slow_down_layer_time": [
|
||||||
|
"4"
|
||||||
|
],
|
||||||
|
"fan_cooling_layer_time": [
|
||||||
|
"100"
|
||||||
|
],
|
||||||
|
"reduce_fan_stop_start_freq": [
|
||||||
|
"1"
|
||||||
|
],
|
||||||
|
"filament_retraction_length": [
|
||||||
|
"2"
|
||||||
|
],
|
||||||
|
"filament_retraction_speed": [
|
||||||
|
"40"
|
||||||
|
],
|
||||||
|
"filament_deretraction_speed": [
|
||||||
|
"40"
|
||||||
|
],
|
||||||
|
"filament_z_hop": [
|
||||||
|
"0.4"
|
||||||
|
],
|
||||||
|
"filament_start_gcode": [
|
||||||
|
"; Generic PETG @BabyBelt Pro — belt PETG, bed 80C"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
{
|
||||||
|
"type": "filament",
|
||||||
|
"name": "Generic PLA @BabyBelt Pro",
|
||||||
|
"inherits": "Generic PLA @System",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "24PpcnhVx9v5f4fD",
|
||||||
|
"instantiation": "true",
|
||||||
|
"compatible_printers": [
|
||||||
|
"BabyBelt Pro 0.4 nozzle"
|
||||||
|
],
|
||||||
|
"filament_type": [
|
||||||
|
"PLA"
|
||||||
|
],
|
||||||
|
"filament_vendor": [
|
||||||
|
"Generic"
|
||||||
|
],
|
||||||
|
"filament_settings_id": [
|
||||||
|
"Generic PLA @BabyBelt Pro"
|
||||||
|
],
|
||||||
|
"filament_diameter": [
|
||||||
|
"1.75"
|
||||||
|
],
|
||||||
|
"filament_density": [
|
||||||
|
"1.24"
|
||||||
|
],
|
||||||
|
"filament_flow_ratio": [
|
||||||
|
"0.98"
|
||||||
|
],
|
||||||
|
"filament_cost": [
|
||||||
|
"20"
|
||||||
|
],
|
||||||
|
"filament_max_volumetric_speed": [
|
||||||
|
"12"
|
||||||
|
],
|
||||||
|
"nozzle_temperature": [
|
||||||
|
"215"
|
||||||
|
],
|
||||||
|
"nozzle_temperature_initial_layer": [
|
||||||
|
"220"
|
||||||
|
],
|
||||||
|
"nozzle_temperature_range_low": [
|
||||||
|
"190"
|
||||||
|
],
|
||||||
|
"nozzle_temperature_range_high": [
|
||||||
|
"240"
|
||||||
|
],
|
||||||
|
"temperature_vitrification": [
|
||||||
|
"45"
|
||||||
|
],
|
||||||
|
"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"
|
||||||
|
],
|
||||||
|
"fan_min_speed": [
|
||||||
|
"100"
|
||||||
|
],
|
||||||
|
"fan_max_speed": [
|
||||||
|
"100"
|
||||||
|
],
|
||||||
|
"overhang_fan_threshold": [
|
||||||
|
"50%"
|
||||||
|
],
|
||||||
|
"overhang_fan_speed": [
|
||||||
|
"100"
|
||||||
|
],
|
||||||
|
"close_fan_the_first_x_layers": [
|
||||||
|
"3"
|
||||||
|
],
|
||||||
|
"full_fan_speed_layer": [
|
||||||
|
"8"
|
||||||
|
],
|
||||||
|
"slow_down_min_speed": [
|
||||||
|
"20"
|
||||||
|
],
|
||||||
|
"slow_down_layer_time": [
|
||||||
|
"4"
|
||||||
|
],
|
||||||
|
"fan_cooling_layer_time": [
|
||||||
|
"100"
|
||||||
|
],
|
||||||
|
"reduce_fan_stop_start_freq": [
|
||||||
|
"1"
|
||||||
|
],
|
||||||
|
"filament_retraction_length": [
|
||||||
|
"1.5"
|
||||||
|
],
|
||||||
|
"filament_retraction_speed": [
|
||||||
|
"35"
|
||||||
|
],
|
||||||
|
"filament_deretraction_speed": [
|
||||||
|
"30"
|
||||||
|
],
|
||||||
|
"filament_z_hop": [
|
||||||
|
"0.4"
|
||||||
|
],
|
||||||
|
"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",
|
||||||
|
"filament_id": "OFkrxQC4",
|
||||||
|
"from": "system",
|
||||||
|
"setting_id": "EH3X7oE0DU5tSpjW",
|
||||||
|
"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,87 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
],
|
||||||
|
"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.4"
|
||||||
|
],
|
||||||
|
"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,141 @@
|
|||||||
|
{
|
||||||
|
"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",
|
||||||
|
"silent_mode": "0",
|
||||||
|
"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,119 @@
|
|||||||
|
{
|
||||||
|
"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",
|
||||||
|
"silent_mode": "0",
|
||||||
|
"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,108 @@
|
|||||||
|
{
|
||||||
|
"type": "process",
|
||||||
|
"name": "fdm_process_common",
|
||||||
|
"from": "system",
|
||||||
|
"instantiation": "false",
|
||||||
|
"adaptive_layer_height": "0",
|
||||||
|
"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",
|
||||||
|
"tree_support_with_infill": "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"
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@ struct SlopeDetection
|
|||||||
bool actived;
|
bool actived;
|
||||||
float normal_z;
|
float normal_z;
|
||||||
mat3 volume_world_normal_matrix;
|
mat3 volume_world_normal_matrix;
|
||||||
|
vec3 up_direction;
|
||||||
};
|
};
|
||||||
|
|
||||||
uniform vec4 uniform_color;
|
uniform vec4 uniform_color;
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ struct SlopeDetection
|
|||||||
bool actived;
|
bool actived;
|
||||||
float normal_z;
|
float normal_z;
|
||||||
mat3 volume_world_normal_matrix;
|
mat3 volume_world_normal_matrix;
|
||||||
|
vec3 up_direction;
|
||||||
};
|
};
|
||||||
|
|
||||||
uniform mat4 view_model_matrix;
|
uniform mat4 view_model_matrix;
|
||||||
@@ -73,8 +74,8 @@ void main()
|
|||||||
// Point in homogenous coordinates.
|
// Point in homogenous coordinates.
|
||||||
world_pos = volume_world_matrix * vec4(v_position, 1.0);
|
world_pos = volume_world_matrix * vec4(v_position, 1.0);
|
||||||
|
|
||||||
// z component of normal vector in world coordinate used for slope shading
|
// dot product of world normal with up direction, used for slope shading
|
||||||
world_normal_z = slope.actived ? (normalize(slope.volume_world_normal_matrix * v_normal)).z : 0.0;
|
world_normal_z = slope.actived ? dot(normalize(slope.volume_world_normal_matrix * v_normal), slope.up_direction) : 0.0;
|
||||||
|
|
||||||
gl_Position = projection_matrix * position;
|
gl_Position = projection_matrix * position;
|
||||||
if (is_outline) {
|
if (is_outline) {
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ struct SlopeDetection
|
|||||||
bool actived;
|
bool actived;
|
||||||
float normal_z;
|
float normal_z;
|
||||||
mat3 volume_world_normal_matrix;
|
mat3 volume_world_normal_matrix;
|
||||||
|
vec3 up_direction;
|
||||||
};
|
};
|
||||||
uniform SlopeDetection slope;
|
uniform SlopeDetection slope;
|
||||||
|
|
||||||
@@ -85,7 +86,7 @@ void main()
|
|||||||
color = LightBlue;
|
color = LightBlue;
|
||||||
alpha = 1.0;
|
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;
|
color = color * 0.5 + LightRed * 0.5;
|
||||||
alpha = 1.0;
|
alpha = 1.0;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ struct SlopeDetection
|
|||||||
bool actived;
|
bool actived;
|
||||||
float normal_z;
|
float normal_z;
|
||||||
mat3 volume_world_normal_matrix;
|
mat3 volume_world_normal_matrix;
|
||||||
|
vec3 up_direction;
|
||||||
};
|
};
|
||||||
uniform SlopeDetection slope;
|
uniform SlopeDetection slope;
|
||||||
void main()
|
void main()
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ struct SlopeDetection
|
|||||||
bool actived;
|
bool actived;
|
||||||
float normal_z;
|
float normal_z;
|
||||||
mat3 volume_world_normal_matrix;
|
mat3 volume_world_normal_matrix;
|
||||||
|
vec3 up_direction;
|
||||||
};
|
};
|
||||||
|
|
||||||
uniform vec4 uniform_color;
|
uniform vec4 uniform_color;
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ struct SlopeDetection
|
|||||||
bool actived;
|
bool actived;
|
||||||
float normal_z;
|
float normal_z;
|
||||||
mat3 volume_world_normal_matrix;
|
mat3 volume_world_normal_matrix;
|
||||||
|
vec3 up_direction;
|
||||||
};
|
};
|
||||||
|
|
||||||
uniform mat4 view_model_matrix;
|
uniform mat4 view_model_matrix;
|
||||||
@@ -73,8 +74,8 @@ void main()
|
|||||||
// Point in homogenous coordinates.
|
// Point in homogenous coordinates.
|
||||||
world_pos = volume_world_matrix * vec4(v_position, 1.0);
|
world_pos = volume_world_matrix * vec4(v_position, 1.0);
|
||||||
|
|
||||||
// z component of normal vector in world coordinate used for slope shading
|
// dot product of world normal with up direction, used for slope shading
|
||||||
world_normal_z = slope.actived ? (normalize(slope.volume_world_normal_matrix * v_normal)).z : 0.0;
|
world_normal_z = slope.actived ? dot(normalize(slope.volume_world_normal_matrix * v_normal), slope.up_direction) : 0.0;
|
||||||
|
|
||||||
gl_Position = projection_matrix * position;
|
gl_Position = projection_matrix * position;
|
||||||
if (is_outline) {
|
if (is_outline) {
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ struct SlopeDetection
|
|||||||
bool actived;
|
bool actived;
|
||||||
float normal_z;
|
float normal_z;
|
||||||
mat3 volume_world_normal_matrix;
|
mat3 volume_world_normal_matrix;
|
||||||
|
vec3 up_direction;
|
||||||
};
|
};
|
||||||
uniform SlopeDetection slope;
|
uniform SlopeDetection slope;
|
||||||
|
|
||||||
@@ -87,7 +88,7 @@ void main()
|
|||||||
color = LightBlue;
|
color = LightBlue;
|
||||||
alpha = 1.0;
|
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;
|
color = color * 0.5 + LightRed * 0.5;
|
||||||
alpha = 1.0;
|
alpha = 1.0;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ struct SlopeDetection
|
|||||||
bool actived;
|
bool actived;
|
||||||
float normal_z;
|
float normal_z;
|
||||||
mat3 volume_world_normal_matrix;
|
mat3 volume_world_normal_matrix;
|
||||||
|
vec3 up_direction;
|
||||||
};
|
};
|
||||||
uniform SlopeDetection slope;
|
uniform SlopeDetection slope;
|
||||||
void main()
|
void main()
|
||||||
|
|||||||
@@ -1549,6 +1549,7 @@
|
|||||||
"Flashforge/Generic PLA",
|
"Flashforge/Generic PLA",
|
||||||
"FlyingBear/Generic PLA",
|
"FlyingBear/Generic PLA",
|
||||||
"Ginger Additive/Generic PLA",
|
"Ginger Additive/Generic PLA",
|
||||||
|
"IdeaFormer/Generic PLA",
|
||||||
"InfiMech/Generic PLA",
|
"InfiMech/Generic PLA",
|
||||||
"LONGER/Generic PLA",
|
"LONGER/Generic PLA",
|
||||||
"Lulzbot/Generic PLA",
|
"Lulzbot/Generic PLA",
|
||||||
@@ -1556,6 +1557,7 @@
|
|||||||
"OrcaFilamentLibrary/Generic PLA",
|
"OrcaFilamentLibrary/Generic PLA",
|
||||||
"Peopoly/Generic PLA",
|
"Peopoly/Generic PLA",
|
||||||
"Phrozen/Generic PLA",
|
"Phrozen/Generic PLA",
|
||||||
|
"Printcepts/Generic PLA",
|
||||||
"Prusa/Generic PLA",
|
"Prusa/Generic PLA",
|
||||||
"Qidi/Generic PLA",
|
"Qidi/Generic PLA",
|
||||||
"RH3D/Generic PLA",
|
"RH3D/Generic PLA",
|
||||||
@@ -4298,12 +4300,14 @@
|
|||||||
"Flashforge/Generic PETG",
|
"Flashforge/Generic PETG",
|
||||||
"FlyingBear/Generic PETG",
|
"FlyingBear/Generic PETG",
|
||||||
"Ginger Additive/Generic PETG",
|
"Ginger Additive/Generic PETG",
|
||||||
|
"IdeaFormer/Generic PETG",
|
||||||
"InfiMech/Generic PETG",
|
"InfiMech/Generic PETG",
|
||||||
"LONGER/Generic PETG",
|
"LONGER/Generic PETG",
|
||||||
"Lulzbot/Generic PETG",
|
"Lulzbot/Generic PETG",
|
||||||
"OrcaArena/Generic PETG",
|
"OrcaArena/Generic PETG",
|
||||||
"OrcaFilamentLibrary/Generic PETG",
|
"OrcaFilamentLibrary/Generic PETG",
|
||||||
"Peopoly/Generic PETG",
|
"Peopoly/Generic PETG",
|
||||||
|
"Printcepts/Generic PETG",
|
||||||
"Prusa/Generic PETG",
|
"Prusa/Generic PETG",
|
||||||
"Qidi/Generic PETG",
|
"Qidi/Generic PETG",
|
||||||
"RH3D/Generic PETG",
|
"RH3D/Generic PETG",
|
||||||
@@ -5886,6 +5890,15 @@
|
|||||||
"filament_type": "PA-GF",
|
"filament_type": "PA-GF",
|
||||||
"filament_vendor": "Eryone"
|
"filament_vendor": "Eryone"
|
||||||
},
|
},
|
||||||
|
"OFkrxQC4": {
|
||||||
|
"filaments": [
|
||||||
|
"IdeaFormer/eSUN PLA",
|
||||||
|
"Printcepts/eSUN PLA"
|
||||||
|
],
|
||||||
|
"name": "eSUN PLA",
|
||||||
|
"filament_type": "PLA",
|
||||||
|
"filament_vendor": "eSUN"
|
||||||
|
},
|
||||||
"OFks6esg": {
|
"OFks6esg": {
|
||||||
"filaments": [
|
"filaments": [
|
||||||
"Creality/EN-PLA+"
|
"Creality/EN-PLA+"
|
||||||
|
|||||||
+7
-1
@@ -91,6 +91,12 @@ if (SLIC3R_GUI)
|
|||||||
# list(REMOVE_ITEM wxWidgets_LIBRARIES oleacc)
|
# list(REMOVE_ITEM wxWidgets_LIBRARIES oleacc)
|
||||||
|
|
||||||
find_package(wxInspector REQUIRED)
|
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
|
# wxInspector's exported interface names the release wxWidgets import
|
||||||
# libraries, which a Debug build cannot link. wx is linked above instead.
|
# 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.
|
# Add the Slic3r GUI library, libcurl, OpenGL and GLU libraries.
|
||||||
if (SLIC3R_GUI)
|
if (SLIC3R_GUI)
|
||||||
# target_link_libraries(OrcaSlicer ws2_32 uxtheme setupapi libslic3r_gui ${wxWidgets_LIBRARIES})
|
# 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)
|
if (MSVC)
|
||||||
# Generate debug symbols even in release mode.
|
# Generate debug symbols even in release mode.
|
||||||
target_link_options(OrcaSlicer PUBLIC "$<$<CONFIG:RELEASE>:/DEBUG>")
|
target_link_options(OrcaSlicer PUBLIC "$<$<CONFIG:RELEASE>:/DEBUG>")
|
||||||
|
|||||||
@@ -0,0 +1,516 @@
|
|||||||
|
#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;
|
||||||
|
if (band_in_plane <= 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;
|
||||||
|
for (const coord_t u : us) {
|
||||||
|
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;
|
||||||
|
|
||||||
|
// 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) each get
|
||||||
|
// their own, clamped so neither end of a band yields an unprintable bead.
|
||||||
|
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);
|
||||||
|
clearance = std::min(std::max(clearance, 0.5 * height), height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 ®ion_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
|
||||||
@@ -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_
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
#include "BeltGCode.hpp"
|
||||||
|
#include "BeltGCodeWriter.hpp"
|
||||||
|
#include "BeltTransform.hpp"
|
||||||
|
#include "Print.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
void BeltGCode::init_belt_writer(Print &print, bool is_bbl_printers)
|
||||||
|
{
|
||||||
|
if (!print.config().belt_printer.value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto belt_writer = std::make_unique<BeltGCodeWriter>();
|
||||||
|
belt_writer->set_is_bbl_machine(is_bbl_printers);
|
||||||
|
// Axis remap and build volume max are set by base GCode after init_belt_writer returns.
|
||||||
|
belt_writer->set_belt_back_transform(print.config());
|
||||||
|
belt_writer->set_machine_frame_transform(print.config());
|
||||||
|
m_writer = std::move(belt_writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BeltGCode::write_belt_header(GCodeOutputStream &file, const Print &print)
|
||||||
|
{
|
||||||
|
if (!print.config().belt_printer.value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
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 || !m_config.belt_printer.value)
|
||||||
|
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
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "GCode.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
// Belt-printer-specific GCode export.
|
||||||
|
//
|
||||||
|
// Inherits from GCode and overrides virtual hooks to:
|
||||||
|
// - Create a BeltGCodeWriter instead of a plain GCodeWriter
|
||||||
|
// - Write belt configuration to the G-code header
|
||||||
|
// - Adjust the origin for global pre-slice transforms when switching instances
|
||||||
|
// - Disable arc fitting (G2/G3 not supported on belt printers)
|
||||||
|
class BeltGCode : public GCode
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
void init_belt_writer(Print &print, bool is_bbl_printers) override;
|
||||||
|
void write_belt_header(GCodeOutputStream &file, const Print &print) override;
|
||||||
|
void on_set_origin(const PrintObject *obj, const Point &inst_shift) override;
|
||||||
|
bool should_disable_arc_fitting() const override { return true; }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
@@ -0,0 +1,277 @@
|
|||||||
|
#include "BeltGCodeWriter.hpp"
|
||||||
|
#include "FirstLayerPlane.hpp"
|
||||||
|
#include "Geometry.hpp"
|
||||||
|
#include <boost/log/trivial.hpp>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Decide whether a particular destination point gets first-layer treatment.
|
||||||
|
// When the plane evaluator is active, distance from the plane wins; otherwise
|
||||||
|
// fall back to the layer-coarse m_is_first_layer flag set by the caller.
|
||||||
|
inline bool belt_point_on_first_layer(
|
||||||
|
const FirstLayerPlane *plane,
|
||||||
|
double first_layer_thickness_mm,
|
||||||
|
bool layer_first_flag,
|
||||||
|
const Vec3d &point_slicing_mm)
|
||||||
|
{
|
||||||
|
if (plane && plane->is_active())
|
||||||
|
return plane->is_first_layer(point_slicing_mm, first_layer_thickness_mm);
|
||||||
|
return layer_first_flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// ---- Belt configuration ---------------------------------------------------
|
||||||
|
|
||||||
|
void BeltGCodeWriter::set_belt_back_transform(const PrintConfig &config)
|
||||||
|
{
|
||||||
|
m_belt_back_transform.init_from_config(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BeltGCodeWriter::set_machine_frame_transform(const PrintConfig &config)
|
||||||
|
{
|
||||||
|
m_machine_frame_transform.init_from_config(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3d BeltGCodeWriter::to_machine_coords(const Vec3d &pos) const
|
||||||
|
{
|
||||||
|
// Step 1+2: To Cartesian (back_transform + axis_remap).
|
||||||
|
// In world-coordinates mode (PA line / PA pattern calibration) the input
|
||||||
|
// already describes a point relative to the belt surface, so the
|
||||||
|
// slicer->world back-transform is skipped and only the machine kinematics
|
||||||
|
// (axis remap + frame shear/scale) are applied.
|
||||||
|
Vec3d after_back = m_world_coordinates ? pos : m_belt_back_transform.apply(pos);
|
||||||
|
Vec3d result = apply_axis_remap(after_back);
|
||||||
|
Vec3d after_remap = result;
|
||||||
|
// Step 3: Machine-frame transform (belt frame tilt) applied LAST so it acts
|
||||||
|
// as a global linear transform on the placed coords.
|
||||||
|
Vec3d final = m_machine_frame_transform.apply(result);
|
||||||
|
|
||||||
|
// [BELT-DEBUG] One-shot log per layer transition (i.e. when the input Z
|
||||||
|
// crosses an integer mm boundary) to keep the log volume manageable while
|
||||||
|
// still capturing one sample per ~5 layers. Shows the full pipeline so
|
||||||
|
// Case A vs Case B can be compared step-by-step.
|
||||||
|
static thread_local int s_last_logged_z = std::numeric_limits<int>::min();
|
||||||
|
int z_bucket = static_cast<int>(std::floor(pos.z() * 5.0)); // every 0.2mm
|
||||||
|
if (z_bucket != s_last_logged_z) {
|
||||||
|
s_last_logged_z = z_bucket;
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "[BELT-DEBUG] to_machine_coords"
|
||||||
|
<< " slicer_in=(" << pos.x() << "," << pos.y() << "," << pos.z() << ")"
|
||||||
|
<< " after_back=(" << after_back.x() << "," << after_back.y() << "," << after_back.z() << ")"
|
||||||
|
<< " after_remap=(" << after_remap.x() << "," << after_remap.y() << "," << after_remap.z() << ")"
|
||||||
|
<< " final=(" << final.x() << "," << final.y() << "," << final.z() << ")"
|
||||||
|
<< " mft_active=" << m_machine_frame_transform.is_active()
|
||||||
|
<< " back_active=" << m_belt_back_transform.is_active();
|
||||||
|
}
|
||||||
|
return final;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Overridden movement methods ------------------------------------------
|
||||||
|
|
||||||
|
std::string BeltGCodeWriter::travel_to_xy(const Vec2d &point, const std::string &comment)
|
||||||
|
{
|
||||||
|
m_pos(0) = point(0);
|
||||||
|
m_pos(1) = point(1);
|
||||||
|
|
||||||
|
this->set_current_position_clear(true);
|
||||||
|
Vec2d point_on_plate = { point(0) - m_x_offset, point(1) - m_y_offset };
|
||||||
|
|
||||||
|
// Belt printer: transform to machine coordinates (XY travel also needs Z due to YZ rotation)
|
||||||
|
Vec3d machine = to_machine_coords(Vec3d(point_on_plate.x(), point_on_plate.y(), m_pos.z()));
|
||||||
|
|
||||||
|
GCodeG1Formatter w;
|
||||||
|
w.emit_xyz(machine);
|
||||||
|
const bool first_layer_for_point = belt_point_on_first_layer(
|
||||||
|
m_first_layer_plane, m_first_layer_thickness_mm, m_is_first_layer,
|
||||||
|
Vec3d(point.x(), point.y(), m_pos.z()));
|
||||||
|
auto speed = first_layer_for_point
|
||||||
|
? this->config.get_abs_value_at("initial_layer_travel_speed", m_cached_extruder_idx)
|
||||||
|
: this->config.travel_speed.get_at(m_cached_extruder_idx);
|
||||||
|
w.emit_f(speed * 60.0);
|
||||||
|
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
|
||||||
|
return w.string();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string BeltGCodeWriter::lazy_lift(LiftType lift_type, bool spiral_vase)
|
||||||
|
{
|
||||||
|
// Belt printer: force NormalLift since SpiralLift and SlopeLift compute
|
||||||
|
// slope angles that don't account for the YZ coordinate rotation.
|
||||||
|
return GCodeWriter::lazy_lift(LiftType::NormalLift, spiral_vase);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string BeltGCodeWriter::eager_lift(const LiftType type)
|
||||||
|
{
|
||||||
|
// Belt printer: force NormalLift (SpiralLift/SlopeLift don't account for YZ rotation).
|
||||||
|
return GCodeWriter::eager_lift(LiftType::NormalLift);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string BeltGCodeWriter::_travel_to_z(double z, const std::string &comment)
|
||||||
|
{
|
||||||
|
m_pos(2) = z;
|
||||||
|
|
||||||
|
double speed = this->config.travel_speed_z.get_at(m_cached_extruder_idx);
|
||||||
|
if (speed == 0.) {
|
||||||
|
const bool first_layer_for_point = belt_point_on_first_layer(
|
||||||
|
m_first_layer_plane, m_first_layer_thickness_mm, m_is_first_layer,
|
||||||
|
Vec3d(m_pos.x(), m_pos.y(), z));
|
||||||
|
speed = first_layer_for_point ? this->config.get_abs_value_at("initial_layer_travel_speed", m_cached_extruder_idx)
|
||||||
|
: this->config.travel_speed.get_at(m_cached_extruder_idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Belt printer: a Z-only move in slicing frame needs to emit both Y and Z in machine coords.
|
||||||
|
Vec3d machine = to_machine_coords(Vec3d(m_pos.x() - m_x_offset, m_pos.y() - m_y_offset, z));
|
||||||
|
|
||||||
|
GCodeG1Formatter w;
|
||||||
|
w.emit_xyz(machine);
|
||||||
|
w.emit_f(speed * 60.0);
|
||||||
|
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
|
||||||
|
return w.string();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string BeltGCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std::string &comment, bool force_no_extrusion)
|
||||||
|
{
|
||||||
|
m_pos(0) = point(0);
|
||||||
|
m_pos(1) = point(1);
|
||||||
|
if (std::abs(dE) <= std::numeric_limits<double>::epsilon())
|
||||||
|
force_no_extrusion = true;
|
||||||
|
|
||||||
|
if (!force_no_extrusion)
|
||||||
|
filament()->extrude(dE);
|
||||||
|
|
||||||
|
Vec2d point_on_plate = { point(0) - m_x_offset, point(1) - m_y_offset };
|
||||||
|
|
||||||
|
// Belt printer: transform and emit XYZ (Y and Z are coupled)
|
||||||
|
Vec3d machine = to_machine_coords(Vec3d(point_on_plate.x(), point_on_plate.y(), m_pos.z()));
|
||||||
|
|
||||||
|
GCodeG1Formatter w;
|
||||||
|
w.emit_xyz(machine);
|
||||||
|
if (!force_no_extrusion)
|
||||||
|
w.emit_e(filament()->E());
|
||||||
|
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
|
||||||
|
return w.string();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string BeltGCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment, bool force_no_extrusion)
|
||||||
|
{
|
||||||
|
m_pos = point;
|
||||||
|
m_lifted = 0;
|
||||||
|
if (!force_no_extrusion)
|
||||||
|
filament()->extrude(dE);
|
||||||
|
|
||||||
|
Vec3d point_on_plate = { point(0) - m_x_offset, point(1) - m_y_offset, point(2) };
|
||||||
|
point_on_plate = to_machine_coords(point_on_plate);
|
||||||
|
|
||||||
|
GCodeG1Formatter w;
|
||||||
|
w.emit_xyz(point_on_plate);
|
||||||
|
if (!force_no_extrusion)
|
||||||
|
w.emit_e(filament()->E());
|
||||||
|
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
|
||||||
|
return w.string();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string BeltGCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &comment, bool force_z)
|
||||||
|
{
|
||||||
|
// Belt-specific override of travel_to_xyz.
|
||||||
|
// Key differences from base:
|
||||||
|
// 1. All coordinates go through to_machine_coords()
|
||||||
|
// 2. Always emit full XYZ (can't split XY and Z due to coupling)
|
||||||
|
// 3. Lift type forced to NormalLift (handled by lazy_lift/eager_lift overrides)
|
||||||
|
|
||||||
|
Vec3d dest_point = point;
|
||||||
|
const bool first_layer_for_point = belt_point_on_first_layer(
|
||||||
|
m_first_layer_plane, m_first_layer_thickness_mm, m_is_first_layer, point);
|
||||||
|
auto travel_speed =
|
||||||
|
first_layer_for_point ? this->config.get_abs_value_at("initial_layer_travel_speed", m_cached_extruder_idx)
|
||||||
|
: this->config.travel_speed.get_at(m_cached_extruder_idx);
|
||||||
|
|
||||||
|
// Handle pending z_hop
|
||||||
|
if (std::abs(m_to_lift) > EPSILON) {
|
||||||
|
assert(std::abs(m_lifted) < EPSILON);
|
||||||
|
if ((!this->is_current_position_clear() || m_pos != dest_point) &&
|
||||||
|
m_to_lift + m_pos(2) > point(2)) {
|
||||||
|
m_lifted = m_to_lift + m_pos(2) - point(2);
|
||||||
|
dest_point(2) = m_to_lift + m_pos(2);
|
||||||
|
}
|
||||||
|
m_to_lift = 0.;
|
||||||
|
|
||||||
|
std::string slop_move;
|
||||||
|
Vec3d source = { m_pos(0) - m_x_offset, m_pos(1) - m_y_offset, m_pos(2) };
|
||||||
|
Vec3d target = { dest_point(0) - m_x_offset, dest_point(1) - m_y_offset, dest_point(2) };
|
||||||
|
Vec3d delta = target - source;
|
||||||
|
Vec2d delta_no_z = { delta(0), delta(1) };
|
||||||
|
|
||||||
|
if (delta(2) > 0 && delta_no_z.norm() != 0.0f) {
|
||||||
|
// Belt: SpiralLift and SlopeLift are disabled (lazy_lift forces NormalLift),
|
||||||
|
// but handle NormalLift and fallthrough.
|
||||||
|
if (m_to_lift_type == LiftType::SlopeLift &&
|
||||||
|
this->is_current_position_clear() &&
|
||||||
|
atan2(delta(2), delta_no_z.norm()) < this->filament()->travel_slope()) {
|
||||||
|
Vec2d temp = delta_no_z.normalized() * delta(2) / tan(this->filament()->travel_slope());
|
||||||
|
Vec3d slope_top_point = Vec3d(temp(0), temp(1), delta(2)) + source;
|
||||||
|
slope_top_point = to_machine_coords(slope_top_point);
|
||||||
|
GCodeG1Formatter w0;
|
||||||
|
w0.emit_xyz(slope_top_point);
|
||||||
|
w0.emit_f(travel_speed * 60.0);
|
||||||
|
w0.emit_comment(GCodeWriter::full_gcode_comment, comment);
|
||||||
|
slop_move = w0.string();
|
||||||
|
}
|
||||||
|
else if (m_to_lift_type == LiftType::NormalLift && this->is_current_position_clear()) {
|
||||||
|
// Only lift-in-place when the current position is known. 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 shears into a bogus machine point
|
||||||
|
// (e.g. X=bed_max, Y=layer_z) far up the gantry. Skipping the separate
|
||||||
|
// lift here is safe: there is nothing to lift over yet, and the
|
||||||
|
// xy_z_move below travels straight to the destination with full XYZ,
|
||||||
|
// establishing the correct position. This mirrors the SlopeLift branch
|
||||||
|
// above, which already guards on is_current_position_clear().
|
||||||
|
slop_move = _travel_to_z(target.z(), "normal lift Z");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string xy_z_move;
|
||||||
|
{
|
||||||
|
Vec3d emit_target = to_machine_coords(target);
|
||||||
|
GCodeG1Formatter w0;
|
||||||
|
// Belt mode: always emit full XYZ since Y and Z are coupled
|
||||||
|
w0.emit_xyz(emit_target);
|
||||||
|
w0.emit_f(travel_speed * 60.0);
|
||||||
|
w0.emit_comment(GCodeWriter::full_gcode_comment, comment);
|
||||||
|
xy_z_move = w0.string();
|
||||||
|
}
|
||||||
|
m_pos = dest_point;
|
||||||
|
this->set_current_position_clear(true);
|
||||||
|
return slop_move + xy_z_move;
|
||||||
|
}
|
||||||
|
else if (!force_z && !this->will_move_z(point(2))) {
|
||||||
|
double nominal_z = m_pos(2) - m_lifted;
|
||||||
|
m_lifted -= (point(2) - nominal_z);
|
||||||
|
if (std::abs(m_lifted) < EPSILON)
|
||||||
|
m_lifted = 0.;
|
||||||
|
this->set_current_position_clear(true);
|
||||||
|
return this->travel_to_xy(to_2d(point));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_lifted = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3d point_on_plate = { dest_point(0) - m_x_offset, dest_point(1) - m_y_offset, dest_point(2) };
|
||||||
|
point_on_plate = to_machine_coords(point_on_plate);
|
||||||
|
|
||||||
|
// Belt mode: always emit full XYZ
|
||||||
|
GCodeG1Formatter w;
|
||||||
|
w.emit_xyz(point_on_plate);
|
||||||
|
// Use the first-layer-aware travel_speed computed at the top of this function,
|
||||||
|
// not the raw config travel_speed, so initial-layer travels are correctly slowed.
|
||||||
|
w.emit_f(travel_speed * 60.0);
|
||||||
|
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
|
||||||
|
|
||||||
|
m_pos = dest_point;
|
||||||
|
this->set_current_position_clear(true);
|
||||||
|
return w.string();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "GCodeWriter.hpp"
|
||||||
|
#include "GCode/BeltBackTransform.hpp"
|
||||||
|
#include "GCode/MachineFrameTransform.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
class FirstLayerPlane;
|
||||||
|
|
||||||
|
// Belt-printer-specific GCode writer.
|
||||||
|
//
|
||||||
|
// Inherits from GCodeWriter and overrides movement methods to apply
|
||||||
|
// coordinate transformation (back-transform, axis remap, machine-frame
|
||||||
|
// transform) and emit coupled XYZ moves (Y and Z are coupled due to belt tilt).
|
||||||
|
class BeltGCodeWriter : public GCodeWriter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BeltGCodeWriter() : GCodeWriter() {}
|
||||||
|
|
||||||
|
// Belt configuration (axis remap is inherited from GCodeWriter)
|
||||||
|
void set_belt_back_transform(const PrintConfig &config);
|
||||||
|
void set_machine_frame_transform(const PrintConfig &config);
|
||||||
|
Vec3d to_machine_coords(const Vec3d &pos) const;
|
||||||
|
|
||||||
|
// World-coordinates mode: incoming coordinates are treated as points
|
||||||
|
// relative to the physical belt surface (X across, Y along the belt,
|
||||||
|
// Z height above it) instead of slicing-frame coordinates — the
|
||||||
|
// slicer->world back-transform is skipped. Used by the PA line / PA
|
||||||
|
// pattern calibration generators, whose logical bed coordinates describe
|
||||||
|
// first-layer drawings on the build surface.
|
||||||
|
void set_world_coordinates(bool enable) { m_world_coordinates = enable; }
|
||||||
|
|
||||||
|
// First-layer plane: when set to a non-null active evaluator, travel
|
||||||
|
// speed selection consults the plane per-move and uses
|
||||||
|
// initial_layer_travel_speed for points within first_layer_height_mm
|
||||||
|
// of the plane (regardless of slicing layer index).
|
||||||
|
void set_first_layer_plane(const FirstLayerPlane *plane,
|
||||||
|
double first_layer_height_mm) {
|
||||||
|
m_first_layer_plane = plane;
|
||||||
|
m_first_layer_thickness_mm = first_layer_height_mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overridden movement methods
|
||||||
|
std::string travel_to_xy(const Vec2d &point, const std::string &comment = std::string()) override;
|
||||||
|
std::string travel_to_xyz(const Vec3d &point, const std::string &comment = std::string(), bool force_z = false) override;
|
||||||
|
std::string extrude_to_xy(const Vec2d &point, double dE, const std::string &comment = std::string(), bool force_no_extrusion = false) override;
|
||||||
|
std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment = std::string(), bool force_no_extrusion = false) override;
|
||||||
|
std::string lazy_lift(LiftType lift_type = LiftType::NormalLift, bool spiral_vase = false) override;
|
||||||
|
std::string eager_lift(const LiftType type) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::string _travel_to_z(double z, const std::string &comment) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
BeltBackTransform m_belt_back_transform;
|
||||||
|
MachineFrameTransform m_machine_frame_transform;
|
||||||
|
bool m_world_coordinates = false;
|
||||||
|
// Borrowed pointer; lifetime owned by GCode. null = inactive.
|
||||||
|
const FirstLayerPlane *m_first_layer_plane = nullptr;
|
||||||
|
double m_first_layer_thickness_mm = 0.;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
@@ -0,0 +1,346 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << "[BELT-DEBUG] purge grid align: snapped " << m_objects.size()
|
||||||
|
<< " objects onto ref grid offset=" << ref_offset
|
||||||
|
<< " (ref=" << ref->model_object()->name << ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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_restore_truncated_layers();
|
||||||
|
|
||||||
|
// 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.");
|
||||||
|
|
||||||
|
if (!m_wipe_tower_data.tool_ordering.has_wipe_tower())
|
||||||
|
// No toolchanges anywhere, nothing to purge.
|
||||||
|
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.
|
||||||
|
{
|
||||||
|
double last_tc_z = -1.;
|
||||||
|
unsigned int cur_ext = m_wipe_tower_data.tool_ordering.first_extruder();
|
||||||
|
for (const auto < : m_wipe_tower_data.tool_ordering.layer_tools())
|
||||||
|
for (const unsigned int e : lt.extruders)
|
||||||
|
if (e != cur_ext) { last_tc_z = lt.print_z; cur_ext = e; }
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Diagnostic: the prism only absorbs purge at toolchange layers whose
|
||||||
|
// print_z coincides with one of its own layers. Compare the prism's layer
|
||||||
|
// print_z range to the toolchange print_z range and count how many
|
||||||
|
// toolchange layers actually land on a prism layer. This distinguishes a
|
||||||
|
// range/grid-alignment failure (no coverage) from a capacity shortfall
|
||||||
|
// (covered but not enough cross-section).
|
||||||
|
PrintObject *prism_po = nullptr;
|
||||||
|
for (PrintObject *po : m_objects)
|
||||||
|
if (po->config().belt_purge_tower_object.value && !po->layers().empty()) { prism_po = po; break; }
|
||||||
|
const PrintObject *diag_prism = prism_po;
|
||||||
|
if (diag_prism != nullptr)
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "[BELT-DEBUG] purge prism layer range print_z=["
|
||||||
|
<< diag_prism->layers().front()->print_z << ", " << diag_prism->layers().back()->print_z
|
||||||
|
<< "] nlayers=" << diag_prism->layers().size();
|
||||||
|
int tc_layers = 0, tc_layers_covered = 0;
|
||||||
|
|
||||||
|
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;
|
||||||
|
bool layer_has_tc = false;
|
||||||
|
for (const unsigned int extruder_id : layer_tools.extruders) {
|
||||||
|
if (extruder_id == current_extruder_id)
|
||||||
|
continue;
|
||||||
|
if (!layer_has_tc) {
|
||||||
|
layer_has_tc = true;
|
||||||
|
++tc_layers;
|
||||||
|
if (diag_prism != nullptr && diag_prism->get_layer_at_printz(layer_tools.print_z, EPSILON) != nullptr)
|
||||||
|
++tc_layers_covered;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "[BELT-DEBUG] purge toolchange print_z=" << layer_tools.print_z
|
||||||
|
<< " filament " << current_extruder_id << "->" << extruder_id
|
||||||
|
<< " requested=" << volume_to_wipe
|
||||||
|
<< " absorbed=" << volume_to_wipe - leftover
|
||||||
|
<< " leftover=" << leftover;
|
||||||
|
layer_leftover += leftover;
|
||||||
|
current_extruder_id = extruder_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not destructively remove unclaimed fill entities here. psWipeTower
|
||||||
|
// can rerun without regenerating infill, and a later tool ordering may
|
||||||
|
// need entities that were unclaimed by the previous plan.
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "[BELT-DEBUG] purge coverage: " << tc_layers_covered << "/" << tc_layers
|
||||||
|
<< " toolchange layers land on a prism layer"
|
||||||
|
<< (tc_layers > 0 && tc_layers_covered == 0 ? " (RANGE/GRID MISALIGNMENT — prism absorbs nothing)" :
|
||||||
|
tc_layers_covered < tc_layers ? " (partial coverage)" : " (full coverage)");
|
||||||
|
|
||||||
|
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)));
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "[BELT-DEBUG] purge planning leftover total=" << total_leftover
|
||||||
|
<< " worst_layer=" << worst_layer_leftover << " at print_z=" << worst_layer_z;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
m_slicing_params.belt_floor_z_shift += delta;
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "[BELT-DEBUG] belt_shift_layer_grid"
|
||||||
|
<< " obj=" << this->model_object()->name
|
||||||
|
<< " delta=" << delta
|
||||||
|
<< " first_layer.print_z=" << (m_layers.empty() ? 0. : m_layers.front()->print_z);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << "[BELT-DEBUG] truncate purge prism above print_z=" << z
|
||||||
|
<< " kept=" << keep << " removed=" << removed
|
||||||
|
<< " new_top=" << (m_layers.empty() ? 0. : m_layers.back()->print_z);
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
#include "BeltSliceStrategy.hpp"
|
||||||
|
#include "Model.hpp"
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
#include <boost/log/trivial.hpp>
|
||||||
|
#ifdef SLIC3R_BELT_DIAGNOSTIC_LOG
|
||||||
|
#include <iomanip>
|
||||||
|
#include <sstream>
|
||||||
|
#include <thread>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
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.
|
||||||
|
#ifdef SLIC3R_BELT_DIAGNOSTIC_LOG
|
||||||
|
// Capture the incoming trafo for diagnostic logging.
|
||||||
|
// This is the slicer-frame transform AFTER remap + rotation but BEFORE z_shift.
|
||||||
|
const Transform3d trafo_pre_shift = trafo;
|
||||||
|
auto log_mat = [](const Matrix3d &m) {
|
||||||
|
std::ostringstream ss;
|
||||||
|
ss << std::fixed << std::setprecision(4);
|
||||||
|
ss << "[[" << m(0,0) << "," << m(0,1) << "," << m(0,2) << "],"
|
||||||
|
<< "[" << m(1,0) << "," << m(1,1) << "," << m(1,2) << "],"
|
||||||
|
<< "[" << m(2,0) << "," << m(2,1) << "," << m(2,2) << "]]";
|
||||||
|
return ss.str();
|
||||||
|
};
|
||||||
|
auto log_vec3 = [](const Vec3d &v) {
|
||||||
|
std::ostringstream ss;
|
||||||
|
ss << std::fixed << std::setprecision(4);
|
||||||
|
ss << "(" << v.x() << "," << v.y() << "," << v.z() << ")";
|
||||||
|
return ss.str();
|
||||||
|
};
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "[BELT-DEBUG] apply_preslice_transforms enter"
|
||||||
|
<< " has_rotation=" << has_rotation
|
||||||
|
<< " has_remap=" << has_remap
|
||||||
|
<< " trafo.linear=" << log_mat(trafo_pre_shift.linear())
|
||||||
|
<< " trafo.translation=" << log_vec3(trafo_pre_shift.translation())
|
||||||
|
<< " volumes=" << model_volumes.size();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
double min_z = std::numeric_limits<double>::max();
|
||||||
|
#ifdef SLIC3R_BELT_DIAGNOSTIC_LOG
|
||||||
|
int vol_idx = 0;
|
||||||
|
#endif
|
||||||
|
for (const ModelVolume *mv : model_volumes) {
|
||||||
|
#ifdef SLIC3R_BELT_DIAGNOSTIC_LOG
|
||||||
|
if (!mv->is_model_part()) { ++vol_idx; continue; }
|
||||||
|
#else
|
||||||
|
if (!mv->is_model_part()) continue;
|
||||||
|
#endif
|
||||||
|
Transform3d vol_trafo = trafo * mv->get_matrix();
|
||||||
|
const auto &its = mv->mesh().its;
|
||||||
|
#ifdef SLIC3R_BELT_DIAGNOSTIC_LOG
|
||||||
|
// Per-volume bbox in mesh-frame and post-trafo slicer-frame.
|
||||||
|
Vec3d mesh_min(std::numeric_limits<double>::max(), std::numeric_limits<double>::max(), std::numeric_limits<double>::max());
|
||||||
|
Vec3d mesh_max(std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest());
|
||||||
|
Vec3d slicer_min(std::numeric_limits<double>::max(), std::numeric_limits<double>::max(), std::numeric_limits<double>::max());
|
||||||
|
Vec3d slicer_max(std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest());
|
||||||
|
double vol_min_z = std::numeric_limits<double>::max();
|
||||||
|
#endif
|
||||||
|
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());
|
||||||
|
#ifdef SLIC3R_BELT_DIAGNOSTIC_LOG
|
||||||
|
mesh_min = mesh_min.cwiseMin(vm);
|
||||||
|
mesh_max = mesh_max.cwiseMax(vm);
|
||||||
|
slicer_min = slicer_min.cwiseMin(pt);
|
||||||
|
slicer_max = slicer_max.cwiseMax(pt);
|
||||||
|
vol_min_z = std::min(vol_min_z, pt.z());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#ifdef SLIC3R_BELT_DIAGNOSTIC_LOG
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "[BELT-DEBUG] vol[" << vol_idx
|
||||||
|
<< "] id=" << mv->id().id << " name='" << mv->name << "'"
|
||||||
|
<< " mesh_bbox_min=" << log_vec3(mesh_min) << " mesh_bbox_max=" << log_vec3(mesh_max)
|
||||||
|
<< " get_matrix.translation=" << log_vec3(mv->get_matrix().translation())
|
||||||
|
<< " slicer_bbox_min=" << log_vec3(slicer_min) << " slicer_bbox_max=" << log_vec3(slicer_max)
|
||||||
|
<< " vol_min_z=" << vol_min_z;
|
||||||
|
++vol_idx;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
const double z_shift_val = (min_z < 0. && min_z != std::numeric_limits<double>::max()) ? -min_z : 0.;
|
||||||
|
#ifdef SLIC3R_BELT_DIAGNOSTIC_LOG
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "[BELT-DEBUG] combined min_z=" << min_z
|
||||||
|
<< " z_shift_val=" << z_shift_val;
|
||||||
|
#endif
|
||||||
|
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) {
|
||||||
|
const double new_val = (min_z != std::numeric_limits<double>::max()) ? min_z : 0.;
|
||||||
|
#ifdef SLIC3R_BELT_DIAGNOSTIC_LOG
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "[BELT-DEBUG] write m_belt_min_z tid=" << std::this_thread::get_id()
|
||||||
|
<< " target=" << out_belt_min_z << " old=" << *out_belt_min_z << " new=" << new_val;
|
||||||
|
#endif
|
||||||
|
*out_belt_min_z = new_val;
|
||||||
|
}
|
||||||
|
#ifdef SLIC3R_BELT_DIAGNOSTIC_LOG
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "[BELT-DEBUG] apply_preslice_transforms exit"
|
||||||
|
<< " final_trafo.linear=" << log_mat(trafo.linear())
|
||||||
|
<< " final_trafo.translation=" << log_vec3(trafo.translation());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
-1
@@ -449,7 +449,9 @@ static ExPolygons outer_inner_brim_area(const Print& print,
|
|||||||
const bool use_brim_ears = object->config().brim_type == btPainted;
|
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 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_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);
|
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;
|
coordf_t brim_ears_max_angle = object->config().brim_ears_max_angle.value;
|
||||||
//ORCA: Select brim base slices from EFC-compensated outline when enabled.
|
//ORCA: Select brim base slices from EFC-compensated outline when enabled.
|
||||||
@@ -864,6 +866,17 @@ void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_
|
|||||||
std::vector<unsigned int>& printExtruders,
|
std::vector<unsigned int>& printExtruders,
|
||||||
std::map<ObjectInstanceID, ExPolygons>* objectBrimAreasByInstanceOut)
|
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;
|
std::map<ObjectInstanceID, ExPolygons> brimAreaMap;
|
||||||
Flow flow = print.brim_flow();
|
Flow flow = print.brim_flow();
|
||||||
ExPolygons islands_area_ex = outer_inner_brim_area(print,
|
ExPolygons islands_area_ex = outer_inner_brim_area(print,
|
||||||
|
|||||||
@@ -180,6 +180,31 @@ BuildVolume::BuildVolume(const std::vector<Vec2d> &printable_area, const double
|
|||||||
BOOST_LOG_TRIVIAL(debug) << "BuildVolume printable_area clasified as: " << this->type_name();
|
BOOST_LOG_TRIVIAL(debug) << "BuildVolume printable_area clasified as: " << this->type_name();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BuildVolume::set_belt_printer(bool enabled, double angle_deg, bool infinite_y)
|
||||||
|
{
|
||||||
|
m_is_belt_printer = enabled;
|
||||||
|
m_belt_angle = angle_deg;
|
||||||
|
m_belt_infinite_y = infinite_y;
|
||||||
|
|
||||||
|
// Restart from the unmodified bbox each call. Without this, toggling
|
||||||
|
// belt mode off (or switching infinite_y true→false) would leave the
|
||||||
|
// extents inflated and break collision / object_state checks.
|
||||||
|
BoundingBoxf bboxf = get_extents(m_bed_shape);
|
||||||
|
m_bboxf = BoundingBoxf3{ to_3d(bboxf.min, 0.), to_3d(bboxf.max, m_max_print_height) };
|
||||||
|
|
||||||
|
if (enabled) {
|
||||||
|
if (infinite_y) {
|
||||||
|
// Extend the Y bound to a very large value for infinite belt.
|
||||||
|
m_bboxf.max.y() = 100000.;
|
||||||
|
}
|
||||||
|
// Belt printer: the Z extent already equals printable_height (set above), which
|
||||||
|
// is the usable vertical clearance above the belt. The gantry's axis range is
|
||||||
|
// sized to reach height/cos(tilt), so no diagonal scaling is applied here — this
|
||||||
|
// keeps the live "outside build volume" highlight in agreement with Print::validate().
|
||||||
|
(void) angle_deg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
// Tests intersections of projected triangles, not just their vertices against a bounding box.
|
// Tests intersections of projected triangles, not just their vertices against a bounding box.
|
||||||
// This test also correctly evaluates collision of a non-convex object with the bounding box.
|
// This test also correctly evaluates collision of a non-convex object with the bounding box.
|
||||||
@@ -388,6 +413,11 @@ BuildVolume::ObjectState BuildVolume::object_state(const indexed_triangle_set& i
|
|||||||
build_volume.max.z() = std::numeric_limits<double>::max();
|
build_volume.max.z() = std::numeric_limits<double>::max();
|
||||||
if (ignore_bottom)
|
if (ignore_bottom)
|
||||||
build_volume.min.z() = -std::numeric_limits<double>::max();
|
build_volume.min.z() = -std::numeric_limits<double>::max();
|
||||||
|
// Belt printer: extend Y bounds for infinite Y.
|
||||||
|
if (m_is_belt_printer && m_belt_infinite_y) {
|
||||||
|
build_volume.min.y() = -std::numeric_limits<double>::max();
|
||||||
|
build_volume.max.y() = std::numeric_limits<double>::max();
|
||||||
|
}
|
||||||
BoundingBox3Base<Vec3f> build_volumef(build_volume.min.cast<float>(), build_volume.max.cast<float>());
|
BoundingBox3Base<Vec3f> build_volumef(build_volume.min.cast<float>(), build_volume.max.cast<float>());
|
||||||
// The following test correctly interprets intersection of a non-convex object with a rectangular build volume.
|
// The following test correctly interprets intersection of a non-convex object with a rectangular build volume.
|
||||||
//return rectangle_test(its, trafo, to_2d(build_volume.min), to_2d(build_volume.max), build_volume.max.z());
|
//return rectangle_test(its, trafo, to_2d(build_volume.min), to_2d(build_volume.max), build_volume.max.z());
|
||||||
|
|||||||
@@ -57,6 +57,10 @@ public:
|
|||||||
// Initialize from PrintConfig::printable_area and PrintConfig::printable_height
|
// Initialize from PrintConfig::printable_area and PrintConfig::printable_height
|
||||||
BuildVolume(const std::vector<Vec2d> &printable_area, const double printable_height, const std::vector<std::vector<Vec2d>> &extruder_areas, const std::vector<double>& extruder_printable_heights);
|
BuildVolume(const std::vector<Vec2d> &printable_area, const double printable_height, const std::vector<std::vector<Vec2d>> &extruder_areas, const std::vector<double>& extruder_printable_heights);
|
||||||
|
|
||||||
|
// Belt printer configuration.
|
||||||
|
void set_belt_printer(bool enabled, double angle_deg, bool infinite_y);
|
||||||
|
bool is_belt_printer() const { return m_is_belt_printer; }
|
||||||
|
|
||||||
// Source data, unscaled coordinates.
|
// Source data, unscaled coordinates.
|
||||||
const std::vector<Vec2d>& printable_area() const { return m_bed_shape; }
|
const std::vector<Vec2d>& printable_area() const { return m_bed_shape; }
|
||||||
double printable_height() const { return m_max_print_height; }
|
double printable_height() const { return m_max_print_height; }
|
||||||
@@ -80,7 +84,7 @@ public:
|
|||||||
indexed_triangle_set bounding_mesh(bool scale=true) const;
|
indexed_triangle_set bounding_mesh(bool scale=true) const;
|
||||||
|
|
||||||
// Center of the print bed, unscaled.
|
// 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.
|
// Convex hull of polygon(), scaled.
|
||||||
const Polygon& convex_hull() const { return m_convex_hull; }
|
const Polygon& convex_hull() const { return m_convex_hull; }
|
||||||
// Smallest enclosing circle of polygon(), scaled.
|
// Smallest enclosing circle of polygon(), scaled.
|
||||||
@@ -139,6 +143,10 @@ private:
|
|||||||
// Source definition of the print volume height (PrintConfig::printable_height)
|
// Source definition of the print volume height (PrintConfig::printable_height)
|
||||||
double m_max_print_height { 0.f };
|
double m_max_print_height { 0.f };
|
||||||
std::vector<double> m_extruder_printable_height;
|
std::vector<double> m_extruder_printable_height;
|
||||||
|
// Belt printer state.
|
||||||
|
bool m_is_belt_printer { false };
|
||||||
|
double m_belt_angle { 0. };
|
||||||
|
bool m_belt_infinite_y { false };
|
||||||
|
|
||||||
// Derived values.
|
// Derived values.
|
||||||
BuildVolume_Type m_type { BuildVolume_Type::Invalid };
|
BuildVolume_Type m_type { BuildVolume_Type::Invalid };
|
||||||
|
|||||||
@@ -80,6 +80,19 @@ set(lisbslic3r_sources
|
|||||||
BoundingBox.hpp
|
BoundingBox.hpp
|
||||||
BridgeDetector.cpp
|
BridgeDetector.cpp
|
||||||
BridgeDetector.hpp
|
BridgeDetector.hpp
|
||||||
|
BeltBrim.cpp
|
||||||
|
BeltBrim.hpp
|
||||||
|
BeltGCode.cpp
|
||||||
|
BeltGCode.hpp
|
||||||
|
BeltGCodeWriter.cpp
|
||||||
|
BeltGCodeWriter.hpp
|
||||||
|
BeltPurge.cpp
|
||||||
|
BeltSliceStrategy.cpp
|
||||||
|
BeltSliceStrategy.hpp
|
||||||
|
BeltTransform.cpp
|
||||||
|
BeltTransform.hpp
|
||||||
|
FirstLayerPlane.cpp
|
||||||
|
FirstLayerPlane.hpp
|
||||||
Brim.cpp
|
Brim.cpp
|
||||||
BrimEarsPoint.hpp
|
BrimEarsPoint.hpp
|
||||||
Brim.hpp
|
Brim.hpp
|
||||||
@@ -228,6 +241,10 @@ set(lisbslic3r_sources
|
|||||||
GCode/AdaptivePAProcessor.hpp
|
GCode/AdaptivePAProcessor.hpp
|
||||||
GCode/AvoidCrossingPerimeters.cpp
|
GCode/AvoidCrossingPerimeters.cpp
|
||||||
GCode/AvoidCrossingPerimeters.hpp
|
GCode/AvoidCrossingPerimeters.hpp
|
||||||
|
GCode/BeltBackTransform.cpp
|
||||||
|
GCode/BeltBackTransform.hpp
|
||||||
|
GCode/MachineFrameTransform.cpp
|
||||||
|
GCode/MachineFrameTransform.hpp
|
||||||
GCode/ConflictChecker.cpp
|
GCode/ConflictChecker.cpp
|
||||||
GCode/ConflictChecker.hpp
|
GCode/ConflictChecker.hpp
|
||||||
GCode/CoolingBuffer.cpp
|
GCode/CoolingBuffer.cpp
|
||||||
@@ -442,6 +459,8 @@ set(lisbslic3r_sources
|
|||||||
SlicingAdaptive.hpp
|
SlicingAdaptive.hpp
|
||||||
Slicing.cpp
|
Slicing.cpp
|
||||||
Slicing.hpp
|
Slicing.hpp
|
||||||
|
Support/BeltFloorContext.cpp
|
||||||
|
Support/BeltFloorContext.hpp
|
||||||
Support/SupportCommon.cpp
|
Support/SupportCommon.cpp
|
||||||
Support/SupportCommon.hpp
|
Support/SupportCommon.hpp
|
||||||
Support/SupportLayer.hpp
|
Support/SupportLayer.hpp
|
||||||
|
|||||||
@@ -396,6 +396,11 @@ inline void translate(ExPolygons &expolys, const Point &p) {
|
|||||||
expoly.translate(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)
|
inline void polygons_append(Polygons &dst, const ExPolygon &src)
|
||||||
{
|
{
|
||||||
dst.reserve(dst.size() + src.holes.size() + 1);
|
dst.reserve(dst.size() + src.holes.size() + 1);
|
||||||
|
|||||||
@@ -0,0 +1,225 @@
|
|||||||
|
#include "FirstLayerPlane.hpp"
|
||||||
|
#include "BeltTransform.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <climits>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Build the row of the gcode-axis-remap matrix R that produces machine_Z,
|
||||||
|
// AS A FUNCTION OF a slicing-frame point in the GCode generator's coordinate
|
||||||
|
// space. Without back-transform this is just R.row(2). With back-transform
|
||||||
|
// the writer applies F^-1 before R, so the effective row is (R * F^-1).row(2).
|
||||||
|
//
|
||||||
|
// Returns a pair (gradient, constant) such that:
|
||||||
|
// machine_Z(p_slicing) = gradient.dot(p_slicing) + constant
|
||||||
|
struct MachineZAffine {
|
||||||
|
Vec3d gradient = Vec3d::UnitZ();
|
||||||
|
double constant = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
MachineZAffine compute_machine_z_affine(const PrintConfig &config)
|
||||||
|
{
|
||||||
|
MachineZAffine out;
|
||||||
|
|
||||||
|
// R is the matrix form of GCodeWriter::apply_axis_remap. Each output axis
|
||||||
|
// i picks one slicing-frame component (with sign + optional Rev mode
|
||||||
|
// translation) based on m_remap_{x,y,z}. We only need row 2 (the z output)
|
||||||
|
// since machine_Z is what defines the first-layer plane.
|
||||||
|
int rz = int(config.gcode_remap_z.value);
|
||||||
|
int axis = rz % 3;
|
||||||
|
double sign;
|
||||||
|
double trans;
|
||||||
|
if (rz < int(RemapAxis::NegX)) { // 0..2 = PosX/Y/Z
|
||||||
|
sign = 1.0;
|
||||||
|
trans = 0.0;
|
||||||
|
} else if (rz < int(RemapAxis::RevX)) { // 3..5 = NegX/Y/Z
|
||||||
|
sign = -1.0;
|
||||||
|
trans = 0.0;
|
||||||
|
} else { // 6..8 = RevX/Y/Z
|
||||||
|
sign = -1.0;
|
||||||
|
BoundingBoxf bbox_bed(config.printable_area.values);
|
||||||
|
Vec3d vol_max(bbox_bed.max.x(),
|
||||||
|
bbox_bed.max.y(),
|
||||||
|
config.printable_height.value);
|
||||||
|
trans = vol_max[axis];
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3d r_row = Vec3d::Zero();
|
||||||
|
r_row[axis] = sign;
|
||||||
|
|
||||||
|
// Without back-transform, machine_Z(slicing) = r_row · slicing + trans.
|
||||||
|
out.gradient = r_row;
|
||||||
|
out.constant = trans;
|
||||||
|
|
||||||
|
if (config.gcode_back_transform.value && config.belt_printer.value) {
|
||||||
|
// BeltGCodeWriter applies F^-1 before R when back-transform is on.
|
||||||
|
// So machine_Z(slicing) = r_row · (F^-1 · slicing) + trans
|
||||||
|
// = (r_row^T · F^-1) · slicing + trans
|
||||||
|
// We need to compose r_row with F^-1 from the LEFT (treating r_row as
|
||||||
|
// a row vector). Eigen makes this easy: it's just F^-1.transpose() * r_row.
|
||||||
|
Transform3d forward = BeltTransformPipeline::build_forward_transform(config);
|
||||||
|
Transform3d inverse = forward.inverse();
|
||||||
|
// Note: forward.translation() is normally zero (per-print transforms
|
||||||
|
// don't add a translation; the per-object z_shift is added separately
|
||||||
|
// in PrintObjectSlice). We still incorporate inverse.translation() in
|
||||||
|
// case a Rev-mode preslice_remap puts a translation in F.
|
||||||
|
Vec3d composed_grad = inverse.linear().transpose() * r_row;
|
||||||
|
double composed_trans =
|
||||||
|
r_row.dot(inverse.translation()) + trans;
|
||||||
|
out.gradient = composed_grad;
|
||||||
|
out.constant = composed_trans;
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
FirstLayerPlane::FirstLayerPlane(const PrintConfig &config)
|
||||||
|
{
|
||||||
|
// -------- Resolve Auto -------------------------------------------------
|
||||||
|
FirstLayerPlaneMode mode = config.first_layer_plane.value;
|
||||||
|
if (mode == FirstLayerPlaneMode::Auto) {
|
||||||
|
bool belt_affine_active = config.belt_printer.value &&
|
||||||
|
config.belt_slice_rotation.value != BeltRotationAxis::None &&
|
||||||
|
std::abs(config.belt_slice_rotation_angle.value) > EPSILON;
|
||||||
|
mode = belt_affine_active ? FirstLayerPlaneMode::BeltAffine
|
||||||
|
: FirstLayerPlaneMode::XY;
|
||||||
|
}
|
||||||
|
m_mode = mode;
|
||||||
|
|
||||||
|
// -------- Band thickness ----------------------------------------------
|
||||||
|
// Note: layer_height lives in PrintObjectConfig, not PrintConfig, so we
|
||||||
|
// can't fall back to it from here. initial_layer_print_height is in
|
||||||
|
// PrintConfig and is the right default anyway (the legacy first-layer
|
||||||
|
// semantics used initial_layer_print_height, not the regular one).
|
||||||
|
double thickness = config.first_layer_plane_thickness.value;
|
||||||
|
if (thickness <= 0.0)
|
||||||
|
thickness = config.initial_layer_print_height.value;
|
||||||
|
if (thickness <= 0.0)
|
||||||
|
thickness = 0.2;
|
||||||
|
m_thickness_mm = thickness;
|
||||||
|
|
||||||
|
const double user_offset = config.first_layer_plane_offset.value;
|
||||||
|
|
||||||
|
// -------- Build the plane ---------------------------------------------
|
||||||
|
auto set_axis_aligned = [&](const Vec3d &n_unit, double offset_along_n) {
|
||||||
|
m_normal = n_unit;
|
||||||
|
m_offset = offset_along_n;
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case FirstLayerPlaneMode::XY:
|
||||||
|
// Legacy XY plane. Inactive: short-circuit to layer-index path.
|
||||||
|
set_axis_aligned(Vec3d::UnitZ(), user_offset);
|
||||||
|
m_active = false;
|
||||||
|
return;
|
||||||
|
|
||||||
|
case FirstLayerPlaneMode::YZ:
|
||||||
|
set_axis_aligned(Vec3d::UnitX(), user_offset);
|
||||||
|
m_active = true;
|
||||||
|
return;
|
||||||
|
|
||||||
|
case FirstLayerPlaneMode::XZ:
|
||||||
|
set_axis_aligned(Vec3d::UnitY(), user_offset);
|
||||||
|
m_active = true;
|
||||||
|
return;
|
||||||
|
|
||||||
|
case FirstLayerPlaneMode::BeltAffine: {
|
||||||
|
// Compute the slicing-frame plane that maps to machine_Z = user_offset
|
||||||
|
// under the gcode axis remap (and optional back-transform).
|
||||||
|
MachineZAffine mz = compute_machine_z_affine(config);
|
||||||
|
double cmag = mz.gradient.norm();
|
||||||
|
if (cmag < EPSILON) {
|
||||||
|
// Degenerate: slicing point doesn't affect machine_Z. Fall back.
|
||||||
|
set_axis_aligned(Vec3d::UnitZ(), user_offset);
|
||||||
|
m_active = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Plane equation: gradient · slicing = user_offset - constant
|
||||||
|
const double K = user_offset - mz.constant;
|
||||||
|
m_normal = mz.gradient / cmag;
|
||||||
|
m_offset = K / cmag;
|
||||||
|
m_active = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case FirstLayerPlaneMode::Auto:
|
||||||
|
// Should have been resolved above.
|
||||||
|
m_active = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
double FirstLayerPlane::distance_from_plane(const Vec3d &point_slicing_mm) const
|
||||||
|
{
|
||||||
|
return m_normal.dot(point_slicing_mm) - m_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FirstLayerPlane::is_first_layer(const Vec3d &point_slicing_mm,
|
||||||
|
double first_layer_height_mm) const
|
||||||
|
{
|
||||||
|
if (!m_active)
|
||||||
|
return false;
|
||||||
|
return distance_from_plane(point_slicing_mm) < first_layer_height_mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
int FirstLayerPlane::effective_layer_index(const Vec3d &point_slicing_mm) const
|
||||||
|
{
|
||||||
|
if (!m_active)
|
||||||
|
return INT_MAX / 2; // Effectively "way past first layer".
|
||||||
|
double d = distance_from_plane(point_slicing_mm);
|
||||||
|
if (d <= 0.0)
|
||||||
|
return 0;
|
||||||
|
return int(std::floor(d / m_thickness_mm));
|
||||||
|
}
|
||||||
|
|
||||||
|
int FirstLayerPlane::min_effective_index_for_xy_bbox(
|
||||||
|
const BoundingBoxf &xy_bbox_mm, double slicing_z_mm) const
|
||||||
|
{
|
||||||
|
if (!m_active)
|
||||||
|
return INT_MAX / 2;
|
||||||
|
// For the rectangular bbox in (x, y) at fixed z, the smallest value of
|
||||||
|
// (n.x*x + n.y*y + n.z*z - offset) is achieved at one of the four
|
||||||
|
// corners, with the smaller component picked when the corresponding
|
||||||
|
// normal coefficient is positive.
|
||||||
|
const double x_for_min = (m_normal.x() >= 0.0)
|
||||||
|
? xy_bbox_mm.min.x() : xy_bbox_mm.max.x();
|
||||||
|
const double y_for_min = (m_normal.y() >= 0.0)
|
||||||
|
? xy_bbox_mm.min.y() : xy_bbox_mm.max.y();
|
||||||
|
const double dmin = m_normal.x() * x_for_min
|
||||||
|
+ m_normal.y() * y_for_min
|
||||||
|
+ m_normal.z() * slicing_z_mm
|
||||||
|
- m_offset;
|
||||||
|
if (dmin <= 0.0)
|
||||||
|
return 0;
|
||||||
|
return int(std::floor(dmin / m_thickness_mm));
|
||||||
|
}
|
||||||
|
|
||||||
|
int FirstLayerPlane::min_effective_index_for_bbox3(
|
||||||
|
const BoundingBoxf3 &bbox_mm) const
|
||||||
|
{
|
||||||
|
if (!m_active)
|
||||||
|
return INT_MAX / 2;
|
||||||
|
const double x_for_min = (m_normal.x() >= 0.0)
|
||||||
|
? bbox_mm.min.x() : bbox_mm.max.x();
|
||||||
|
const double y_for_min = (m_normal.y() >= 0.0)
|
||||||
|
? bbox_mm.min.y() : bbox_mm.max.y();
|
||||||
|
const double z_for_min = (m_normal.z() >= 0.0)
|
||||||
|
? bbox_mm.min.z() : bbox_mm.max.z();
|
||||||
|
const double dmin = m_normal.x() * x_for_min
|
||||||
|
+ m_normal.y() * y_for_min
|
||||||
|
+ m_normal.z() * z_for_min
|
||||||
|
- m_offset;
|
||||||
|
if (dmin <= 0.0)
|
||||||
|
return 0;
|
||||||
|
return int(std::floor(dmin / m_thickness_mm));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
#ifndef slic3r_FirstLayerPlane_hpp_
|
||||||
|
#define slic3r_FirstLayerPlane_hpp_
|
||||||
|
|
||||||
|
#include "libslic3r.h"
|
||||||
|
#include "Point.hpp"
|
||||||
|
#include "BoundingBox.hpp"
|
||||||
|
#include "PrintConfig.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
// Decides which extrusions get "first layer" treatment (no fan, slow speed,
|
||||||
|
// initial-layer accel/jerk, deferred temperature drop) by reference to a
|
||||||
|
// configurable plane in slicing-frame coordinates rather than the slicing
|
||||||
|
// layer index.
|
||||||
|
//
|
||||||
|
// On a normal flat-bed printer the plane is XY at slicing_Z = 0 and the
|
||||||
|
// evaluator is INACTIVE — every call site short-circuits back to the legacy
|
||||||
|
// `Layer::id() == 0` test. On a belt printer with a Z-from-Y shear the
|
||||||
|
// belt surface (machine_Z = 0) maps to a plane in slicing-frame coordinates
|
||||||
|
// derived from the gcode axis remap, so layer-index-based detection no
|
||||||
|
// longer matches the physical first printed surface.
|
||||||
|
//
|
||||||
|
// Plane representation: unit normal `n` (slicing frame) and offset along
|
||||||
|
// the normal such that the plane equation is `n · p == offset`. Signed
|
||||||
|
// perpendicular distance is `d(p) = n · p - offset`. Positive distance
|
||||||
|
// means "away from the belt surface", negative means "below the plane".
|
||||||
|
class FirstLayerPlane
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit FirstLayerPlane(const PrintConfig &config);
|
||||||
|
|
||||||
|
// Inactive when the legacy XY layer-index path should be used. This
|
||||||
|
// covers all non-belt printers and any belt printer where the user
|
||||||
|
// explicitly picked XY mode.
|
||||||
|
bool is_active() const { return m_active; }
|
||||||
|
FirstLayerPlaneMode effective_mode() const{ return m_mode; }
|
||||||
|
double band_thickness_mm() const { return m_thickness_mm; }
|
||||||
|
const Vec3d & normal() const { return m_normal; }
|
||||||
|
double plane_offset() const { return m_offset; }
|
||||||
|
|
||||||
|
// Signed perpendicular distance from a slicing-frame point to the plane.
|
||||||
|
double distance_from_plane(const Vec3d &point_slicing_mm) const;
|
||||||
|
|
||||||
|
// True if perpendicular distance < first_layer_height_mm. When the
|
||||||
|
// evaluator is inactive this returns false (call sites should fall back
|
||||||
|
// to the legacy per-layer path before reaching this function).
|
||||||
|
bool is_first_layer(const Vec3d &point_slicing_mm,
|
||||||
|
double first_layer_height_mm) const;
|
||||||
|
|
||||||
|
// floor((distance - 0) / band_thickness), clamped to [0, +inf). Used
|
||||||
|
// for "first N layers" thresholds (fan, slow_down_layers). Returns 0
|
||||||
|
// for points within the band. Returns INT_MAX/2 when inactive.
|
||||||
|
int effective_layer_index(const Vec3d &point_slicing_mm) const;
|
||||||
|
|
||||||
|
// Min effective index over a 2D bbox at a fixed slicing_Z. Used for
|
||||||
|
// layer-level decisions (e.g. temperature transition gate) where we
|
||||||
|
// don't want to walk every extrusion in the layer. For axis-aligned
|
||||||
|
// planes this is exact; for tilted planes it's a tight lower bound
|
||||||
|
// (the plane projection of the bbox's extreme corner).
|
||||||
|
int min_effective_index_for_xy_bbox(const BoundingBoxf &xy_bbox_mm,
|
||||||
|
double slicing_z_mm) const;
|
||||||
|
|
||||||
|
// Same as above but the bbox spans a Z range too.
|
||||||
|
int min_effective_index_for_bbox3(const BoundingBoxf3 &bbox_mm) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool m_active = false;
|
||||||
|
FirstLayerPlaneMode m_mode = FirstLayerPlaneMode::XY;
|
||||||
|
Vec3d m_normal = Vec3d::UnitZ(); // unit, slicing frame
|
||||||
|
double m_offset = 0.0; // n·p == m_offset
|
||||||
|
double m_thickness_mm = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
#endif // slic3r_FirstLayerPlane_hpp_
|
||||||
+735
-252
File diff suppressed because it is too large
Load Diff
+96
-10
@@ -4,6 +4,8 @@
|
|||||||
#include "libslic3r.h"
|
#include "libslic3r.h"
|
||||||
#include "ExPolygon.hpp"
|
#include "ExPolygon.hpp"
|
||||||
#include "GCodeWriter.hpp"
|
#include "GCodeWriter.hpp"
|
||||||
|
#include "BeltGCodeWriter.hpp"
|
||||||
|
#include "FirstLayerPlane.hpp"
|
||||||
#include "Layer.hpp"
|
#include "Layer.hpp"
|
||||||
#include "Point.hpp"
|
#include "Point.hpp"
|
||||||
#include "PlaceholderParser.hpp"
|
#include "PlaceholderParser.hpp"
|
||||||
@@ -31,6 +33,7 @@
|
|||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <optional>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <cfloat>
|
#include <cfloat>
|
||||||
@@ -214,16 +217,18 @@ public:
|
|||||||
m_last_obj_copy(nullptr, Point(std::numeric_limits<coord_t>::max(), std::numeric_limits<coord_t>::max())),
|
m_last_obj_copy(nullptr, Point(std::numeric_limits<coord_t>::max(), std::numeric_limits<coord_t>::max())),
|
||||||
// BBS
|
// BBS
|
||||||
m_toolchange_count(0),
|
m_toolchange_count(0),
|
||||||
m_nominal_z(0.)
|
m_nominal_z(0.),
|
||||||
|
m_writer(std::make_unique<GCodeWriter>())
|
||||||
{}
|
{}
|
||||||
~GCode() = default;
|
virtual ~GCode() = default;
|
||||||
|
|
||||||
|
public:
|
||||||
// throws std::runtime_exception on error,
|
// throws std::runtime_exception on error,
|
||||||
// throws CanceledException through print->throw_if_canceled().
|
// throws CanceledException through print->throw_if_canceled().
|
||||||
void do_export(Print* print, const char* path, GCodeProcessorResult* result = nullptr, ThumbnailsGeneratorCallback thumbnail_cb = nullptr);
|
void do_export(Print* print, const char* path, GCodeProcessorResult* result = nullptr, ThumbnailsGeneratorCallback thumbnail_cb = nullptr);
|
||||||
void export_layer_filaments(GCodeProcessorResult* result);
|
void export_layer_filaments(GCodeProcessorResult* result);
|
||||||
//BBS: set offset for gcode writer
|
//BBS: set offset for gcode writer
|
||||||
void set_gcode_offset(double x, double y) { m_writer.set_xy_offset(x, y); m_processor.set_xy_offset(x, y);}
|
void set_gcode_offset(double x, double y) { m_writer->set_xy_offset(x, y); m_processor.set_xy_offset(x, y);}
|
||||||
|
|
||||||
// Exported for the helper classes (OozePrevention, Wipe) and for the Perl binding for unit tests.
|
// Exported for the helper classes (OozePrevention, Wipe) and for the Perl binding for unit tests.
|
||||||
const Vec2d& origin() const { return m_origin; }
|
const Vec2d& origin() const { return m_origin; }
|
||||||
@@ -237,8 +242,8 @@ public:
|
|||||||
Vec3d point_to_gcode_quantized(const Point3& point) const;
|
Vec3d point_to_gcode_quantized(const Point3& point) const;
|
||||||
const FullPrintConfig &config() const { return m_config; }
|
const FullPrintConfig &config() const { return m_config; }
|
||||||
const Layer* layer() const { return m_layer; }
|
const Layer* layer() const { return m_layer; }
|
||||||
GCodeWriter& writer() { return m_writer; }
|
GCodeWriter& writer() { return *m_writer; }
|
||||||
const GCodeWriter& writer() const { return m_writer; }
|
const GCodeWriter& writer() const { return *m_writer; }
|
||||||
PlaceholderParser& placeholder_parser() { return m_placeholder_parser_integration.parser; }
|
PlaceholderParser& placeholder_parser() { return m_placeholder_parser_integration.parser; }
|
||||||
const PlaceholderParser& placeholder_parser() const { return m_placeholder_parser_integration.parser; }
|
const PlaceholderParser& placeholder_parser() const { return m_placeholder_parser_integration.parser; }
|
||||||
// Process a template through the placeholder parser, collect error messages to be reported
|
// Process a template through the placeholder parser, collect error messages to be reported
|
||||||
@@ -261,7 +266,7 @@ public:
|
|||||||
bool needs_retraction(const Polyline& travel, ExtrusionRole role, LiftType& lift_type);
|
bool needs_retraction(const Polyline& travel, ExtrusionRole role, LiftType& lift_type);
|
||||||
std::string retract(bool toolchange = false, bool is_last_retraction = false, LiftType lift_type = LiftType::NormalLift, bool apply_instantly = false, ExtrusionRole role = erNone);
|
std::string retract(bool toolchange = false, bool is_last_retraction = false, LiftType lift_type = LiftType::NormalLift, bool apply_instantly = false, ExtrusionRole role = erNone);
|
||||||
// extra_retract forwards a PETG pre-extrusion over-extrusion; default 0 -> identical to the plain deretract.
|
// extra_retract forwards a PETG pre-extrusion over-extrusion; default 0 -> identical to the plain deretract.
|
||||||
std::string unretract(float extra_retract = 0.f) { return m_writer.unlift() + m_writer.unretract(extra_retract); }
|
std::string unretract(float extra_retract = 0.f) { return m_writer->unlift() + m_writer->unretract(extra_retract); }
|
||||||
std::string set_extruder(unsigned int extruder_id, double print_z, bool by_object=false, int toolchange_temp_override = -1, bool defer_temp_wait = false);
|
std::string set_extruder(unsigned int extruder_id, double print_z, bool by_object=false, int toolchange_temp_override = -1, bool defer_temp_wait = false);
|
||||||
bool is_BBL_Printer();
|
bool is_BBL_Printer();
|
||||||
WipeTowerType wipe_tower_type();
|
WipeTowerType wipe_tower_type();
|
||||||
@@ -286,6 +291,13 @@ public:
|
|||||||
const Layer* object_layer;
|
const Layer* object_layer;
|
||||||
const SupportLayer* support_layer;
|
const SupportLayer* support_layer;
|
||||||
const PrintObject* original_object; //BBS: used for shared object logic
|
const PrintObject* original_object; //BBS: used for shared object logic
|
||||||
|
// Belt printers only: an apron band that prints BELOW the object's first
|
||||||
|
// layer, so it has no object or support layer of its own. Deliberately
|
||||||
|
// not a Layer, so it cannot leak Layer::id() semantics into initial-layer
|
||||||
|
// temperature, spiral vase, cooling or interpolation logic. When this is
|
||||||
|
// the only thing set, layer() is null and process_layer() takes its
|
||||||
|
// dedicated brim-only branch.
|
||||||
|
const BeltBrimBand* belt_brim_band { nullptr };
|
||||||
const Layer* layer() const
|
const Layer* layer() const
|
||||||
{
|
{
|
||||||
if (object_layer != nullptr)
|
if (object_layer != nullptr)
|
||||||
@@ -315,11 +327,25 @@ public:
|
|||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A brim-only apron band contributes no object/support layer, and
|
||||||
|
// averaging zero terms would yield NaN. Never folded into the
|
||||||
|
// average, so the non-belt result is bit-identical.
|
||||||
|
if (count == 0 && belt_brim_band != nullptr)
|
||||||
|
return belt_brim_band->print_z;
|
||||||
|
|
||||||
return sum_z / count;
|
return sum_z / count;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
// Public accessor for the first-layer plane evaluator. Used by
|
||||||
|
// CoolingBuffer (which is constructed with a GCode reference and needs
|
||||||
|
// to read the plane for per-segment fan re-evaluation). All other
|
||||||
|
// first-layer-plane access points (on_first_layer overload, effective
|
||||||
|
// index helper) are in the protected section since they're called from
|
||||||
|
// GCode internals only.
|
||||||
|
const FirstLayerPlane *first_layer_plane() const { return m_first_layer_plane.get(); }
|
||||||
|
|
||||||
|
protected:
|
||||||
class GCodeOutputStream {
|
class GCodeOutputStream {
|
||||||
public:
|
public:
|
||||||
GCodeOutputStream(FILE *f, GCodeProcessor &processor) : f(f), m_processor(processor) {}
|
GCodeOutputStream(FILE *f, GCodeProcessor &processor) : f(f), m_processor(processor) {}
|
||||||
@@ -347,9 +373,17 @@ private:
|
|||||||
FILE *f = nullptr;
|
FILE *f = nullptr;
|
||||||
GCodeProcessor &m_processor;
|
GCodeProcessor &m_processor;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Virtual hooks for belt printer subclass (BeltGCode).
|
||||||
|
// No-ops in base GCode; overridden in BeltGCode.
|
||||||
|
virtual void init_belt_writer(Print &print, bool is_bbl_printers) {}
|
||||||
|
virtual void write_belt_header(GCodeOutputStream &file, const Print &print) {}
|
||||||
|
virtual void on_set_origin(const PrintObject *obj, const Point &inst_shift) {}
|
||||||
|
virtual bool should_disable_arc_fitting() const { return false; }
|
||||||
|
|
||||||
void _do_export(Print &print, GCodeOutputStream &file, ThumbnailsGeneratorCallback thumbnail_cb);
|
void _do_export(Print &print, GCodeOutputStream &file, ThumbnailsGeneratorCallback thumbnail_cb);
|
||||||
|
|
||||||
static std::vector<LayerToPrint> collect_layers_to_print(const PrintObject &object);
|
static std::vector<LayerToPrint> collect_layers_to_print(const PrintObject &object, bool skip_empty_first_layer = false);
|
||||||
static std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> collect_layers_to_print(const Print &print);
|
static std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> collect_layers_to_print(const Print &print);
|
||||||
|
|
||||||
std::string generate_skirt(const Print &print,
|
std::string generate_skirt(const Print &print,
|
||||||
@@ -369,7 +403,28 @@ private:
|
|||||||
std::string generate_object_brim(const Print &print,
|
std::string generate_object_brim(const Print &print,
|
||||||
const PrintObject &object,
|
const PrintObject &object,
|
||||||
size_t instance_id,
|
size_t instance_id,
|
||||||
bool first_layer);
|
bool first_layer,
|
||||||
|
const Layer *object_layer);
|
||||||
|
|
||||||
|
// Belt printers: emit one brim-only apron layer. These print below the
|
||||||
|
// object's first layer, so there is no object or support layer for the normal
|
||||||
|
// process_layer() machinery to work from. Kept to the minimum a layer needs -
|
||||||
|
// tool, Z move, extrusions - so that nothing here can perturb the
|
||||||
|
// Layer::id()-based logic the ordinary path relies on.
|
||||||
|
LayerResult process_belt_brim_layer(
|
||||||
|
const Print &print,
|
||||||
|
const std::vector<LayerToPrint> &layers,
|
||||||
|
const LayerTools &layer_tools,
|
||||||
|
const bool last_layer,
|
||||||
|
const size_t single_object_instance_idx);
|
||||||
|
|
||||||
|
// Emit the apron bands carried by these layers. Called from both the brim-only
|
||||||
|
// branch and the ordinary path, since a band's print_z can coincide with another
|
||||||
|
// object's layer on a multi-object belt.
|
||||||
|
std::string emit_belt_brim_bands(
|
||||||
|
const Print &print,
|
||||||
|
const std::vector<LayerToPrint> &layers,
|
||||||
|
const size_t single_object_instance_idx);
|
||||||
|
|
||||||
LayerResult process_layer(
|
LayerResult process_layer(
|
||||||
const Print &print,
|
const Print &print,
|
||||||
@@ -587,7 +642,7 @@ private:
|
|||||||
DynamicConfig m_calib_config;
|
DynamicConfig m_calib_config;
|
||||||
// scaled G-code resolution
|
// scaled G-code resolution
|
||||||
double m_scaled_resolution;
|
double m_scaled_resolution;
|
||||||
GCodeWriter m_writer;
|
std::unique_ptr<GCodeWriter> m_writer;
|
||||||
|
|
||||||
struct PlaceholderParserIntegration {
|
struct PlaceholderParserIntegration {
|
||||||
void reset();
|
void reset();
|
||||||
@@ -707,6 +762,11 @@ private:
|
|||||||
|
|
||||||
std::unique_ptr<CoolingBuffer> m_cooling_buffer;
|
std::unique_ptr<CoolingBuffer> m_cooling_buffer;
|
||||||
std::unique_ptr<SpiralVase> m_spiral_vase;
|
std::unique_ptr<SpiralVase> m_spiral_vase;
|
||||||
|
// First-layer plane evaluator. Constructed once per print from the
|
||||||
|
// PrintConfig. is_active() == false on non-belt printers and on belt
|
||||||
|
// printers without a Z-axis shear; in that case all per-path plane
|
||||||
|
// checks short-circuit to the legacy Layer::id() == 0 path.
|
||||||
|
std::unique_ptr<FirstLayerPlane> m_first_layer_plane;
|
||||||
|
|
||||||
std::unique_ptr<PressureEqualizer> m_pressure_equalizer;
|
std::unique_ptr<PressureEqualizer> m_pressure_equalizer;
|
||||||
|
|
||||||
@@ -760,6 +820,13 @@ private:
|
|||||||
// resolvers. Distinct from m_layer_index (an export progress counter starting at -1).
|
// resolvers. Distinct from m_layer_index (an export progress counter starting at -1).
|
||||||
size_t m_cur_layer_idx{0};
|
size_t m_cur_layer_idx{0};
|
||||||
|
|
||||||
|
// Belt brim apron layers only. They have no Layer, so the print_z that
|
||||||
|
// _extrude() needs for the first-layer-plane probe is published here instead.
|
||||||
|
// Scoped by BeltBrimZGuard in process_belt_brim_layer(), never left set.
|
||||||
|
std::optional<coordf_t> m_belt_brim_z;
|
||||||
|
// Counter standing in for Layer::id() on apron layers, which precede layer 0.
|
||||||
|
size_t m_belt_brim_layer_idx{0};
|
||||||
|
|
||||||
std::set<unsigned int> m_initial_layer_extruders;
|
std::set<unsigned int> m_initial_layer_extruders;
|
||||||
std::vector<std::vector<unsigned int>> m_sorted_layer_filaments;
|
std::vector<std::vector<unsigned int>> m_sorted_layer_filaments;
|
||||||
// BBS
|
// BBS
|
||||||
@@ -777,6 +844,25 @@ private:
|
|||||||
// On the first printing layer. This flag triggers first layer speeds.
|
// On the first printing layer. This flag triggers first layer speeds.
|
||||||
//BBS
|
//BBS
|
||||||
bool on_first_layer() const { return m_layer != nullptr && m_layer->id() == 0 && abs(m_layer->bottom_z()) < EPSILON; }
|
bool on_first_layer() const { return m_layer != nullptr && m_layer->id() == 0 && abs(m_layer->bottom_z()) < EPSILON; }
|
||||||
|
// Per-point first-layer test. When the FirstLayerPlane evaluator is
|
||||||
|
// active, the result depends on the supplied slicing-frame point;
|
||||||
|
// otherwise we delegate to the legacy per-layer test. This is the
|
||||||
|
// entry point used by per-path call sites in _extrude.
|
||||||
|
bool on_first_layer(const Vec3d &point_slicing_mm) const {
|
||||||
|
if (m_first_layer_plane && m_first_layer_plane->is_active())
|
||||||
|
return m_first_layer_plane->is_first_layer(
|
||||||
|
point_slicing_mm, m_config.initial_layer_print_height.value);
|
||||||
|
return on_first_layer();
|
||||||
|
}
|
||||||
|
// "Effective layer index" used to drive layer-count thresholds like
|
||||||
|
// slow_down_layers. When the evaluator is active this returns the
|
||||||
|
// perpendicular distance to the plane in band_thickness_mm units;
|
||||||
|
// otherwise it returns the legacy slicing layer index.
|
||||||
|
int effective_layer_index_for_point(const Vec3d &point_slicing_mm) const {
|
||||||
|
if (m_first_layer_plane && m_first_layer_plane->is_active())
|
||||||
|
return m_first_layer_plane->effective_layer_index(point_slicing_mm);
|
||||||
|
return on_first_layer() ? 0 : layer_id();
|
||||||
|
}
|
||||||
int layer_id() const {
|
int layer_id() const {
|
||||||
if (m_layer == nullptr)
|
if (m_layer == nullptr)
|
||||||
return -1;
|
return -1;
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
#include "BeltBackTransform.hpp"
|
||||||
|
#include "../BeltTransform.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
bool BeltBackTransform::init_from_config(const PrintConfig &config)
|
||||||
|
{
|
||||||
|
m_active = false;
|
||||||
|
m_inverse = Transform3d::Identity();
|
||||||
|
|
||||||
|
if (!config.belt_printer.value || !config.gcode_back_transform.value)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Require at least one active transform to proceed.
|
||||||
|
bool has_global_rotation = config.belt_slice_rotation_global.value
|
||||||
|
&& config.belt_slice_rotation.value != BeltRotationAxis::None;
|
||||||
|
bool has_preslice_global = config.belt_preslice_global.value
|
||||||
|
|| config.preslice_remap_global.value;
|
||||||
|
if (!has_global_rotation && !has_preslice_global
|
||||||
|
&& !BeltTransformPipeline::has_preslice_remap(config))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Build the forward pipeline (rotation * pre_remap) and store its inverse.
|
||||||
|
Transform3d forward = BeltTransformPipeline::build_forward_transform(config);
|
||||||
|
if (forward.isApprox(Transform3d::Identity()))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
m_inverse = forward.inverse();
|
||||||
|
m_active = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3d BeltBackTransform::apply(const Vec3d &pos) const
|
||||||
|
{
|
||||||
|
if (!m_active)
|
||||||
|
return pos;
|
||||||
|
return m_inverse * pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#ifndef slic3r_BeltBackTransform_hpp_
|
||||||
|
#define slic3r_BeltBackTransform_hpp_
|
||||||
|
|
||||||
|
#include "../libslic3r.h"
|
||||||
|
#include "../Point.hpp"
|
||||||
|
#include "../PrintConfig.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
// Reverses the pre-slice remap + shear + scale transforms that
|
||||||
|
// PrintObjectSlice.cpp applies to belt printer geometry, converting G-code
|
||||||
|
// coordinates from the sliced (remapped/sheared/scaled) frame back to the
|
||||||
|
// machine's real coordinate space.
|
||||||
|
//
|
||||||
|
// Initialized once from PrintConfig, then applied per-point in
|
||||||
|
// GCodeWriter::to_machine_coords() before axis remapping.
|
||||||
|
//
|
||||||
|
// Active when gcode_back_transform is true AND at least one of:
|
||||||
|
// - a shear axis has global mode enabled, or
|
||||||
|
// - a pre-slice axis remap is non-identity.
|
||||||
|
class BeltBackTransform
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BeltBackTransform() = default;
|
||||||
|
|
||||||
|
// Initialize from belt printer config. Rebuilds the same pre-slice remap,
|
||||||
|
// shear, and scale matrices as PrintObjectSlice.cpp and precomputes the
|
||||||
|
// affine inverse. Returns true if a non-identity back-transform was computed.
|
||||||
|
bool init_from_config(const PrintConfig &config);
|
||||||
|
|
||||||
|
// Apply the inverse transform to a point. Returns pos unchanged if
|
||||||
|
// no back-transform is active.
|
||||||
|
Vec3d apply(const Vec3d &pos) const;
|
||||||
|
|
||||||
|
// True if a non-identity back-transform is active.
|
||||||
|
bool is_active() const { return m_active; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool m_active = false;
|
||||||
|
Transform3d m_inverse = Transform3d::Identity();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
#endif // slic3r_BeltBackTransform_hpp_
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
#include "../GCode.hpp"
|
#include "../GCode.hpp"
|
||||||
|
#include "../FirstLayerPlane.hpp"
|
||||||
#include "CoolingBuffer.hpp"
|
#include "CoolingBuffer.hpp"
|
||||||
#include <boost/algorithm/string/predicate.hpp>
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
#include <boost/algorithm/string/replace.hpp>
|
#include <boost/algorithm/string/replace.hpp>
|
||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdlib>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <float.h>
|
#include <float.h>
|
||||||
|
#include <string_view>
|
||||||
#include <system_error>
|
#include <system_error>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
@@ -28,6 +32,12 @@ CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_config(gcodegen.config()), m_t
|
|||||||
m_num_extruders = std::max(ex.id() + 1, m_num_extruders);
|
m_num_extruders = std::max(ex.id() + 1, m_num_extruders);
|
||||||
m_extruder_ids.emplace_back(ex.id());
|
m_extruder_ids.emplace_back(ex.id());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Borrow the first-layer plane from the GCode generator. When inactive
|
||||||
|
// (non-belt printers and belt printers without Z shear), per-line fan
|
||||||
|
// re-evaluation is skipped and behavior is bit-identical to the legacy
|
||||||
|
// per-layer path.
|
||||||
|
m_first_layer_plane = gcodegen.first_layer_plane();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoolingBuffer::reset(const Vec3d &position)
|
void CoolingBuffer::reset(const Vec3d &position)
|
||||||
@@ -328,6 +338,13 @@ std::string CoolingBuffer::process_layer(std::string &&gcode, size_t layer_id, b
|
|||||||
std::vector<PerExtruderAdjustments> per_extruder_adjustments = this->parse_layer_gcode(m_gcode, m_current_pos);
|
std::vector<PerExtruderAdjustments> per_extruder_adjustments = this->parse_layer_gcode(m_gcode, m_current_pos);
|
||||||
float layer_time_stretched = this->calculate_layer_slowdown(per_extruder_adjustments);
|
float layer_time_stretched = this->calculate_layer_slowdown(per_extruder_adjustments);
|
||||||
out = this->apply_layer_cooldown(m_gcode, layer_id, layer_time_stretched, per_extruder_adjustments);
|
out = this->apply_layer_cooldown(m_gcode, layer_id, layer_time_stretched, per_extruder_adjustments);
|
||||||
|
// First-layer plane: per-segment fan re-evaluation post-pass. Walks
|
||||||
|
// the cooled-down gcode and inserts inline M106 commands at band
|
||||||
|
// crossings (where the path's perpendicular distance to the plane
|
||||||
|
// crosses close_fan_the_first_x_layers thresholds). No-op when
|
||||||
|
// the evaluator is inactive.
|
||||||
|
if (m_first_layer_plane && m_first_layer_plane->is_active())
|
||||||
|
out = this->apply_first_layer_plane_fan_eval(std::move(out), layer_id, layer_time_stretched);
|
||||||
m_gcode.clear();
|
m_gcode.clear();
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
@@ -1059,4 +1076,214 @@ std::string CoolingBuffer::apply_layer_cooldown(
|
|||||||
return new_gcode;
|
return new_gcode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pure helper: compute the main fan speed for a given effective layer index.
|
||||||
|
// Mirrors the inline logic in change_extruder_set_fan but is callable from
|
||||||
|
// per-line code in apply_first_layer_plane_fan_eval.
|
||||||
|
int CoolingBuffer::compute_main_fan_speed(int effective_layer_id, float layer_time,
|
||||||
|
unsigned int extruder_id) const
|
||||||
|
{
|
||||||
|
#define EXTRUDER_CFG(opt) m_config.opt.get_at(extruder_id)
|
||||||
|
float fan_min_speed = EXTRUDER_CFG(fan_min_speed);
|
||||||
|
float fan_max_speed = EXTRUDER_CFG(fan_max_speed);
|
||||||
|
bool reduce_fan_stop_start_freq = EXTRUDER_CFG(reduce_fan_stop_start_freq);
|
||||||
|
int close_fan_the_first_x_layers = EXTRUDER_CFG(close_fan_the_first_x_layers);
|
||||||
|
int full_fan_speed_layer = EXTRUDER_CFG(full_fan_speed_layer);
|
||||||
|
float slow_down_layer_time = float(EXTRUDER_CFG(slow_down_layer_time));
|
||||||
|
float fan_cooling_layer_time = float(EXTRUDER_CFG(fan_cooling_layer_time));
|
||||||
|
#undef EXTRUDER_CFG
|
||||||
|
|
||||||
|
if (close_fan_the_first_x_layers <= 0 && full_fan_speed_layer > 0)
|
||||||
|
close_fan_the_first_x_layers = 1;
|
||||||
|
|
||||||
|
float fan_speed_new = reduce_fan_stop_start_freq ? fan_min_speed : 0.f;
|
||||||
|
if (effective_layer_id >= close_fan_the_first_x_layers) {
|
||||||
|
if (layer_time < slow_down_layer_time) {
|
||||||
|
fan_speed_new = fan_max_speed;
|
||||||
|
} else if (layer_time < fan_cooling_layer_time) {
|
||||||
|
double t = (layer_time - slow_down_layer_time) /
|
||||||
|
(fan_cooling_layer_time - slow_down_layer_time);
|
||||||
|
fan_speed_new = float(int(floor(t * fan_min_speed +
|
||||||
|
(1. - t) * fan_max_speed) + 0.5));
|
||||||
|
}
|
||||||
|
if (effective_layer_id + 1 < full_fan_speed_layer) {
|
||||||
|
float factor = float(effective_layer_id + 1 - close_fan_the_first_x_layers)
|
||||||
|
/ float(full_fan_speed_layer - close_fan_the_first_x_layers);
|
||||||
|
fan_speed_new = float(std::clamp(int(fan_speed_new * factor + 0.5f), 0, 255));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fan_speed_new = 0.f;
|
||||||
|
}
|
||||||
|
return int(fan_speed_new);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post-pass: walk the cooled-down gcode line by line, track XYZ position,
|
||||||
|
// and insert M106 commands at first-layer-plane band crossings so the fan
|
||||||
|
// follows perpendicular distance to the plane rather than the slicing-layer
|
||||||
|
// index. Only invoked when the FirstLayerPlane evaluator is active.
|
||||||
|
//
|
||||||
|
// This implementation is intentionally minimal: it overrides only the MAIN
|
||||||
|
// fan (the one set by GCodeWriter::set_fan); overhang/internal-bridge/etc
|
||||||
|
// special fans remain at their layer-level values from apply_layer_cooldown.
|
||||||
|
// That keeps the per-line logic small while still giving the user precise
|
||||||
|
// fan control near the belt surface, which is the main quality concern.
|
||||||
|
std::string CoolingBuffer::apply_first_layer_plane_fan_eval(
|
||||||
|
std::string &&gcode_in, size_t /*layer_id*/, float layer_time)
|
||||||
|
{
|
||||||
|
if (!m_first_layer_plane || !m_first_layer_plane->is_active())
|
||||||
|
return std::move(gcode_in);
|
||||||
|
|
||||||
|
const std::string &gcode = gcode_in;
|
||||||
|
std::string out;
|
||||||
|
out.reserve(gcode.size() + 256);
|
||||||
|
|
||||||
|
// Match the PWM floor applied at every other set_fan call in this file so
|
||||||
|
// band-crossing M106 emissions start the fan reliably at low speeds.
|
||||||
|
const unsigned int part_cooling_fan_min_pwm = static_cast<unsigned int>(std::max(0, m_config.part_cooling_fan_min_pwm.value));
|
||||||
|
|
||||||
|
// Track position in slicing-frame mm. Seed from m_current_pos which the
|
||||||
|
// CoolingBuffer keeps up-to-date across layers.
|
||||||
|
Vec3d cur_pos_mm(m_current_pos[0], m_current_pos[1], m_current_pos[2]);
|
||||||
|
|
||||||
|
// Track current main fan speed by parsing M106 commands as we walk so
|
||||||
|
// we can restore it after a band exit.
|
||||||
|
int current_main_fan = m_fan_speed;
|
||||||
|
int pre_band_main_fan = current_main_fan;
|
||||||
|
// Implicit initial state: assume the layer started "out of the band"
|
||||||
|
// (i.e., the layer-level fan setting from apply_layer_cooldown is in
|
||||||
|
// effect). The first movement we encounter will reconcile this.
|
||||||
|
bool in_first_layer_band = false;
|
||||||
|
unsigned int active_extruder = m_current_extruder;
|
||||||
|
|
||||||
|
auto parse_xyz_into = [](const std::string_view &line_sv, Vec3d &p) {
|
||||||
|
if (line_sv.size() < 3) return false;
|
||||||
|
if (line_sv[0] != 'G') return false;
|
||||||
|
if (line_sv[1] != '0' && line_sv[1] != '1') return false;
|
||||||
|
if (line_sv[2] != ' ' && line_sv[2] != '\t') return false;
|
||||||
|
const char *c = line_sv.data() + 3;
|
||||||
|
const char *end = line_sv.data() + line_sv.size();
|
||||||
|
bool any = false;
|
||||||
|
while (c < end && *c != ';') {
|
||||||
|
while (c < end && (*c == ' ' || *c == '\t')) ++c;
|
||||||
|
if (c >= end || *c == ';' || *c == '\n' || *c == '\r') break;
|
||||||
|
char axis = *c;
|
||||||
|
++c;
|
||||||
|
if (axis == 'X' || axis == 'Y' || axis == 'Z') {
|
||||||
|
char *next;
|
||||||
|
double v = std::strtod(c, &next);
|
||||||
|
if (next != c) {
|
||||||
|
if (axis == 'X') p.x() = v;
|
||||||
|
else if (axis == 'Y') p.y() = v;
|
||||||
|
else p.z() = v;
|
||||||
|
c = next;
|
||||||
|
any = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Skip unrecognized word.
|
||||||
|
while (c < end && *c != ' ' && *c != '\t' && *c != ';' && *c != '\n')
|
||||||
|
++c;
|
||||||
|
}
|
||||||
|
return any;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto parse_m106 = [](const std::string_view &line_sv) -> int {
|
||||||
|
// Returns -1 if not an M106, otherwise the S value (0..255).
|
||||||
|
if (line_sv.size() < 4 || line_sv[0] != 'M') return -1;
|
||||||
|
if (!(line_sv[1] == '1' && line_sv[2] == '0' && line_sv[3] == '6'))
|
||||||
|
return -1;
|
||||||
|
// Find S<value>
|
||||||
|
size_t s_pos = line_sv.find('S');
|
||||||
|
if (s_pos == std::string_view::npos) return -1;
|
||||||
|
const char *c = line_sv.data() + s_pos + 1;
|
||||||
|
char *next;
|
||||||
|
long v = std::strtol(c, &next, 10);
|
||||||
|
if (next == c) return -1;
|
||||||
|
return int(std::clamp<long>(v, 0, 255));
|
||||||
|
};
|
||||||
|
|
||||||
|
auto parse_m107 = [](const std::string_view &line_sv) -> bool {
|
||||||
|
return line_sv.size() >= 4 && line_sv[0] == 'M' &&
|
||||||
|
line_sv[1] == '1' && line_sv[2] == '0' && line_sv[3] == '7';
|
||||||
|
};
|
||||||
|
|
||||||
|
auto parse_tool_change = [this](const std::string_view &line_sv) -> int {
|
||||||
|
// Returns the new extruder id, or -1 if not a toolchange.
|
||||||
|
if (line_sv.size() < m_toolchange_prefix.size() + 1) return -1;
|
||||||
|
if (line_sv.compare(0, m_toolchange_prefix.size(), m_toolchange_prefix) != 0)
|
||||||
|
return -1;
|
||||||
|
const char *c = line_sv.data() + m_toolchange_prefix.size();
|
||||||
|
char *next;
|
||||||
|
long v = std::strtol(c, &next, 10);
|
||||||
|
if (next == c) return -1;
|
||||||
|
return int(v);
|
||||||
|
};
|
||||||
|
|
||||||
|
const char *p = gcode.c_str();
|
||||||
|
const char *end = gcode.c_str() + gcode.size();
|
||||||
|
while (p < end) {
|
||||||
|
const char *line_end = p;
|
||||||
|
while (line_end < end && *line_end != '\n') ++line_end;
|
||||||
|
const char *next_line = line_end;
|
||||||
|
if (next_line < end) ++next_line; // include the '\n'
|
||||||
|
|
||||||
|
std::string_view line_sv(p, line_end - p);
|
||||||
|
|
||||||
|
// Track tool changes so the per-line fan eval uses the right extruder.
|
||||||
|
int new_tool = parse_tool_change(line_sv);
|
||||||
|
if (new_tool >= 0)
|
||||||
|
active_extruder = unsigned(new_tool);
|
||||||
|
|
||||||
|
// Track existing fan commands so we can restore the right value when
|
||||||
|
// exiting a band.
|
||||||
|
int m106_speed = parse_m106(line_sv);
|
||||||
|
if (m106_speed >= 0) {
|
||||||
|
current_main_fan = m106_speed;
|
||||||
|
if (!in_first_layer_band)
|
||||||
|
pre_band_main_fan = m106_speed;
|
||||||
|
} else if (parse_m107(line_sv)) {
|
||||||
|
current_main_fan = 0;
|
||||||
|
if (!in_first_layer_band)
|
||||||
|
pre_band_main_fan = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Movement line: parse XYZ, evaluate plane, possibly emit a fan
|
||||||
|
// change BEFORE this line.
|
||||||
|
bool moved = parse_xyz_into(line_sv, cur_pos_mm);
|
||||||
|
if (moved) {
|
||||||
|
const int eff_idx = m_first_layer_plane->effective_layer_index(cur_pos_mm);
|
||||||
|
const int close_n = m_config.close_fan_the_first_x_layers.get_at(active_extruder);
|
||||||
|
const bool now_in_band = eff_idx < std::max(close_n, 1);
|
||||||
|
if (now_in_band != in_first_layer_band) {
|
||||||
|
// Band crossing: emit a M106 with the appropriate speed.
|
||||||
|
int target_fan;
|
||||||
|
if (now_in_band) {
|
||||||
|
// Entering the first-layer band: fan off.
|
||||||
|
pre_band_main_fan = current_main_fan;
|
||||||
|
target_fan = compute_main_fan_speed(eff_idx, layer_time, active_extruder);
|
||||||
|
} else {
|
||||||
|
// Exiting the band: restore the layer's normal fan speed.
|
||||||
|
// Use compute_main_fan_speed with the effective index so
|
||||||
|
// the linear ramp factor (close_fan→full_fan_speed_layer)
|
||||||
|
// also follows distance from the plane.
|
||||||
|
target_fan = compute_main_fan_speed(eff_idx, layer_time, active_extruder);
|
||||||
|
if (target_fan == 0)
|
||||||
|
target_fan = pre_band_main_fan;
|
||||||
|
}
|
||||||
|
if (target_fan != current_main_fan) {
|
||||||
|
out += GCodeWriter::set_fan(m_config.gcode_flavor, target_fan, part_cooling_fan_min_pwm);
|
||||||
|
current_main_fan = target_fan;
|
||||||
|
m_fan_speed = target_fan;
|
||||||
|
m_current_fan_speed = target_fan;
|
||||||
|
}
|
||||||
|
in_first_layer_band = now_in_band;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.append(p, next_line - p);
|
||||||
|
p = next_line;
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ namespace Slic3r {
|
|||||||
|
|
||||||
class GCode;
|
class GCode;
|
||||||
class Layer;
|
class Layer;
|
||||||
|
class FirstLayerPlane;
|
||||||
struct PerExtruderAdjustments;
|
struct PerExtruderAdjustments;
|
||||||
|
|
||||||
// A standalone G-code filter, to control cooling of the print.
|
// A standalone G-code filter, to control cooling of the print.
|
||||||
@@ -18,7 +19,7 @@ struct PerExtruderAdjustments;
|
|||||||
//
|
//
|
||||||
// The simple it sounds, the actual implementation is significantly more complex.
|
// The simple it sounds, the actual implementation is significantly more complex.
|
||||||
// Namely, for a multi-extruder print, each material may require a different cooling logic.
|
// Namely, for a multi-extruder print, each material may require a different cooling logic.
|
||||||
// For example, some materials may not like to print too slowly, while with some materials
|
// For example, some materials may not like to print too slowly, while with some materials
|
||||||
// we may slow down significantly.
|
// we may slow down significantly.
|
||||||
//
|
//
|
||||||
class CoolingBuffer {
|
class CoolingBuffer {
|
||||||
@@ -36,6 +37,21 @@ private:
|
|||||||
// Returns the adjusted G-code.
|
// Returns the adjusted G-code.
|
||||||
std::string apply_layer_cooldown(const std::string &gcode, size_t layer_id, float layer_time, std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
|
std::string apply_layer_cooldown(const std::string &gcode, size_t layer_id, float layer_time, std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
|
||||||
|
|
||||||
|
// First-layer plane: per-line fan re-evaluation post-pass. Walks the
|
||||||
|
// post-cooldown gcode, tracks XYZ position, and inserts M106 commands at
|
||||||
|
// band-crossing transitions in slicing-frame coordinates. Only runs
|
||||||
|
// when m_first_layer_plane is active.
|
||||||
|
std::string apply_first_layer_plane_fan_eval(std::string &&gcode_in,
|
||||||
|
size_t layer_id,
|
||||||
|
float layer_time);
|
||||||
|
|
||||||
|
// Pure helper: compute the main fan speed for a given effective layer
|
||||||
|
// index (layer-id units, mapped through the plane evaluator) and the
|
||||||
|
// current extruder. Mirrors the inline logic in the change_extruder_set_fan
|
||||||
|
// lambda but is callable from per-line code.
|
||||||
|
int compute_main_fan_speed(int effective_layer_id, float layer_time,
|
||||||
|
unsigned int extruder_id) const;
|
||||||
|
|
||||||
// G-code snippet cached for the support layers preceding an object layer.
|
// G-code snippet cached for the support layers preceding an object layer.
|
||||||
std::string m_gcode;
|
std::string m_gcode;
|
||||||
// Internal data.
|
// Internal data.
|
||||||
@@ -58,6 +74,9 @@ private:
|
|||||||
unsigned int m_current_nozzle;
|
unsigned int m_current_nozzle;
|
||||||
//BBS: current fan speed
|
//BBS: current fan speed
|
||||||
int m_current_fan_speed;
|
int m_current_fan_speed;
|
||||||
|
// First-layer plane evaluator, borrowed from GCode. Null = inactive
|
||||||
|
// (legacy per-layer fan control).
|
||||||
|
const FirstLayerPlane *m_first_layer_plane = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2533,6 +2533,12 @@ void GCodeProcessorResult::reset() {
|
|||||||
long_retraction_when_cut = false;
|
long_retraction_when_cut = false;
|
||||||
timelapse_warning_code = 0;
|
timelapse_warning_code = 0;
|
||||||
printable_height = 0.0f;
|
printable_height = 0.0f;
|
||||||
|
machine_frame_transform_active = false;
|
||||||
|
belt_tilt_angle = 0.f;
|
||||||
|
belt_z_origin = 0.f;
|
||||||
|
preslice_remap_x = RemapAxis::PosX;
|
||||||
|
preslice_remap_y = RemapAxis::PosY;
|
||||||
|
preslice_remap_z = RemapAxis::PosZ;
|
||||||
settings_ids.reset();
|
settings_ids.reset();
|
||||||
filaments_count = 0;
|
filaments_count = 0;
|
||||||
backtrace_enabled = false;
|
backtrace_enabled = false;
|
||||||
@@ -2769,6 +2775,32 @@ bool GCodeProcessor::check_multi_extruder_gcode_valid(const int
|
|||||||
return ps;
|
return ps;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Belt-printer post-gcode shear/scale/post_remap is applied as the final
|
||||||
|
// step of BeltGCodeWriter::to_machine_coords, so MoveVertex.position is
|
||||||
|
// in the printer's machine frame. Undo it here so XY area and Z height
|
||||||
|
// checks operate in the build-volume frame that printable_area /
|
||||||
|
// printable_height are defined in. For non-belt printers
|
||||||
|
// (is_active() == false) apply_inverse is identity and behaviour is
|
||||||
|
// unchanged from before.
|
||||||
|
const bool machine_frame_active = m_machine_frame_transform.is_active();
|
||||||
|
auto compare_pos = [&](const GCodeProcessorResult::MoveVertex &move) -> Vec3d {
|
||||||
|
Vec3d pos = move.position.cast<double>();
|
||||||
|
if (!machine_frame_active)
|
||||||
|
return pos;
|
||||||
|
Vec3d extruder_off = Vec3d::Zero();
|
||||||
|
if (size_t(move.extruder_id) < m_extruder_offsets.size())
|
||||||
|
extruder_off = m_extruder_offsets[move.extruder_id].cast<double>();
|
||||||
|
// Strip plate + extruder offsets to recover the raw machine-frame
|
||||||
|
// coordinate that was emitted into the G-code (see store_move_vertex).
|
||||||
|
Vec3d machine(pos.x() - m_x_offset - extruder_off.x(),
|
||||||
|
pos.y() - m_y_offset - extruder_off.y(),
|
||||||
|
pos.z() - extruder_off.z() + m_z_offset);
|
||||||
|
Vec3d build = m_machine_frame_transform.apply_inverse(machine);
|
||||||
|
// Re-apply plate offset so the result matches plate_printable_poly,
|
||||||
|
// which is translated by plate_offset below.
|
||||||
|
return Vec3d(build.x() + m_x_offset, build.y() + m_y_offset, build.z());
|
||||||
|
};
|
||||||
|
|
||||||
struct GCodePosInfo
|
struct GCodePosInfo
|
||||||
{
|
{
|
||||||
Points pos;
|
Points pos;
|
||||||
@@ -2780,26 +2812,20 @@ bool GCodeProcessor::check_multi_extruder_gcode_valid(const int
|
|||||||
for (const GCodeProcessorResult::MoveVertex &move : m_result.moves) {
|
for (const GCodeProcessorResult::MoveVertex &move : m_result.moves) {
|
||||||
// sometimes, the start line extrude was outside the edge of plate a little, this is allowed, so do not include into the gcode_path_pos
|
// sometimes, the start line extrude was outside the edge of plate a little, this is allowed, so do not include into the gcode_path_pos
|
||||||
if (move.type == EMoveType::Extrude /* && move.extrusion_role != ExtrusionRole::erFlush || move.type == EMoveType::Travel*/) {
|
if (move.type == EMoveType::Extrude /* && move.extrusion_role != ExtrusionRole::erFlush || move.type == EMoveType::Travel*/) {
|
||||||
|
const Vec3d cp = compare_pos(move);
|
||||||
|
// For belt printers we read Z from the inverse-transformed position
|
||||||
|
// (post-origin-snap, pre-machine-frame). Otherwise keep the
|
||||||
|
// original print_z source (the slicer's layer-Z comment) so
|
||||||
|
// non-belt behaviour is bit-for-bit unchanged.
|
||||||
|
const float z_for_height = machine_frame_active ? float(cp.z()) : move.print_z;
|
||||||
if (move.extrusion_role == ExtrusionRole::erCustom) {
|
if (move.extrusion_role == ExtrusionRole::erCustom) {
|
||||||
/*if (move.is_arc_move_with_interpolation_points()) {
|
gcode_path_pos[move.object_label_id][int(move.extruder_id)].pos_custom.emplace_back(to_2d(cp));
|
||||||
for (int i = 0; i < move.interpolation_points.size(); i++) {
|
|
||||||
gcode_path_pos[move.object_label_id][int(move.extruder_id)].pos_custom.emplace_back(to_2d(move.interpolation_points[i].cast<double>()));
|
|
||||||
}
|
|
||||||
} else {*/
|
|
||||||
gcode_path_pos[move.object_label_id][int(move.extruder_id)].pos_custom.emplace_back(to_2d(move.position.cast<double>()));
|
|
||||||
//}
|
|
||||||
gcode_path_pos[move.object_label_id][int(move.extruder_id)].max_print_z_custom =
|
gcode_path_pos[move.object_label_id][int(move.extruder_id)].max_print_z_custom =
|
||||||
std::max(gcode_path_pos[move.object_label_id][int(move.extruder_id)].max_print_z_custom, move.print_z);
|
std::max(gcode_path_pos[move.object_label_id][int(move.extruder_id)].max_print_z_custom, z_for_height);
|
||||||
} else {
|
} else {
|
||||||
/*if (move.is_arc_move_with_interpolation_points()) {
|
gcode_path_pos[move.object_label_id][int(move.extruder_id)].pos.emplace_back(to_2d(cp));
|
||||||
for (int i = 0; i < move.interpolation_points.size(); i++) {
|
|
||||||
gcode_path_pos[move.object_label_id][int(move.extruder_id)].pos.emplace_back(to_2d(move.interpolation_points[i].cast<double>()));
|
|
||||||
}
|
|
||||||
} else {*/
|
|
||||||
gcode_path_pos[move.object_label_id][int(move.extruder_id)].pos.emplace_back(to_2d(move.position.cast<double>()));
|
|
||||||
//}
|
|
||||||
gcode_path_pos[move.object_label_id][int(move.extruder_id)].max_print_z = std::max(gcode_path_pos[move.object_label_id][int(move.extruder_id)].max_print_z,
|
gcode_path_pos[move.object_label_id][int(move.extruder_id)].max_print_z = std::max(gcode_path_pos[move.object_label_id][int(move.extruder_id)].max_print_z,
|
||||||
move.print_z);
|
z_for_height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3041,6 +3067,12 @@ void GCodeProcessor::apply_config(const PrintConfig& config)
|
|||||||
|
|
||||||
m_result.printable_height = config.printable_height;
|
m_result.printable_height = config.printable_height;
|
||||||
|
|
||||||
|
// Belt printer: cache the post-gcode machine-frame transform so the
|
||||||
|
// multi-extruder validator can undo it and compare against build-volume
|
||||||
|
// bounds rather than machine-frame positions.
|
||||||
|
m_machine_frame_transform.init_from_config(config);
|
||||||
|
m_result.machine_frame_transform_active = m_machine_frame_transform.is_active();
|
||||||
|
|
||||||
auto filament_maps = config.option<ConfigOptionInts>("filament_map");
|
auto filament_maps = config.option<ConfigOptionInts>("filament_map");
|
||||||
if (filament_maps != nullptr) {
|
if (filament_maps != nullptr) {
|
||||||
m_filament_maps = filament_maps->values;
|
m_filament_maps = filament_maps->values;
|
||||||
@@ -4159,6 +4191,44 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Belt printer: derive the physical tilt magnitude from the slicing-rotation
|
||||||
|
// angle header comment (used to enable the preview's belt view).
|
||||||
|
if (boost::starts_with(comment, " belt_slice_rotation_angle = ")) {
|
||||||
|
try {
|
||||||
|
m_result.belt_tilt_angle = std::abs(std::stof(std::string(comment.substr(29))));
|
||||||
|
} catch (...) {}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Belt printer: parse pre-slice axis remap from header comments.
|
||||||
|
{
|
||||||
|
auto trim = [](const std::string &s) -> std::string {
|
||||||
|
size_t start = s.find_first_not_of(" \t\r\n");
|
||||||
|
size_t end = s.find_last_not_of(" \t\r\n");
|
||||||
|
return (start == std::string::npos) ? "" : s.substr(start, end - start + 1);
|
||||||
|
};
|
||||||
|
// Pre-slice axis remap
|
||||||
|
auto parse_remap_axis = [](const std::string &s) -> RemapAxis {
|
||||||
|
if (s == "pos_x") return RemapAxis::PosX;
|
||||||
|
if (s == "pos_y") return RemapAxis::PosY;
|
||||||
|
if (s == "pos_z") return RemapAxis::PosZ;
|
||||||
|
if (s == "neg_x") return RemapAxis::NegX;
|
||||||
|
if (s == "neg_y") return RemapAxis::NegY;
|
||||||
|
if (s == "neg_z") return RemapAxis::NegZ;
|
||||||
|
if (s == "rev_x") return RemapAxis::RevX;
|
||||||
|
if (s == "rev_y") return RemapAxis::RevY;
|
||||||
|
if (s == "rev_z") return RemapAxis::RevZ;
|
||||||
|
return RemapAxis::PosX;
|
||||||
|
};
|
||||||
|
if (boost::starts_with(comment, " preslice_remap_x = ")) {
|
||||||
|
m_result.preslice_remap_x = parse_remap_axis(trim(std::string(comment.substr(25)))); return;
|
||||||
|
}
|
||||||
|
if (boost::starts_with(comment, " preslice_remap_y = ")) {
|
||||||
|
m_result.preslice_remap_y = parse_remap_axis(trim(std::string(comment.substr(25)))); return;
|
||||||
|
}
|
||||||
|
if (boost::starts_with(comment, " preslice_remap_z = ")) {
|
||||||
|
m_result.preslice_remap_z = parse_remap_axis(trim(std::string(comment.substr(25)))); return;
|
||||||
|
}
|
||||||
|
}
|
||||||
// wipe start tag
|
// wipe start tag
|
||||||
if (boost::starts_with(comment, reserved_tag(ETags::Wipe_Start))) {
|
if (boost::starts_with(comment, reserved_tag(ETags::Wipe_Start))) {
|
||||||
m_wiping = true;
|
m_wiping = true;
|
||||||
@@ -6055,6 +6125,13 @@ void GCodeProcessor::process_G92(const GCodeReader::GCodeLine& line)
|
|||||||
if (line.has_z()) {
|
if (line.has_z()) {
|
||||||
m_origin[Z] = m_end_position[Z] - line.z() * lengths_scale_factor;
|
m_origin[Z] = m_end_position[Z] - line.z() * lengths_scale_factor;
|
||||||
any_found = true;
|
any_found = true;
|
||||||
|
// Belt only: the start G-code's purge-blob advance + G92 Z0 resets leave a constant
|
||||||
|
// machine-Z origin offset here; the designed-view back-transform subtracts it so
|
||||||
|
// toolpaths map to the model's belt coordinate (gcode Z). Gated on belt_tilt_angle
|
||||||
|
// (set from the belt header, parsed before the body) so non-belt G-code processing
|
||||||
|
// is byte-identical — no unconditional work on the shared path.
|
||||||
|
if (m_result.belt_tilt_angle != 0.f)
|
||||||
|
m_result.belt_z_origin = m_origin[Z];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (line.has_e()) {
|
if (line.has_e()) {
|
||||||
@@ -7033,6 +7110,22 @@ void GCodeProcessor::store_move_vertex(EMoveType type, EMovePathType path_type,
|
|||||||
m_result.print_statistics.total_travel_distance += m_travel_dist;
|
m_result.print_statistics.total_travel_distance += m_travel_dist;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// During the start G-code "prepare" stage the toolhead Z is not yet a real
|
||||||
|
// print height on a normal printer, so it is pinned to the first-layer height
|
||||||
|
// to keep the preview tidy. Belt printers are the exception: there the Z is
|
||||||
|
// written explicitly by BeltGCodeWriter and the designed-view back-transform
|
||||||
|
// couples machine Z into the rendered model Y (the belt tilt mixes the height
|
||||||
|
// and belt-feed axes). Overriding Z therefore back-transforms the last
|
||||||
|
// prepare-stage move (the unretract before the first extrusion) to model
|
||||||
|
// Y ~= 0, and the libvgcode path builder then draws a phantom extrusion
|
||||||
|
// segment from Y ~= 0 to the first real toolpath. Keep the real Z for belt
|
||||||
|
// printers so prepare-stage moves map correctly. Gated on belt_tilt_angle (set
|
||||||
|
// from the G-code header before the body is processed) so non-belt processing
|
||||||
|
// is byte-identical.
|
||||||
|
const float store_z = (m_processing_start_custom_gcode && m_result.belt_tilt_angle == 0.f)
|
||||||
|
? m_first_layer_height
|
||||||
|
: m_end_position[Z] - m_z_offset;
|
||||||
|
|
||||||
m_result.moves.push_back({
|
m_result.moves.push_back({
|
||||||
m_last_line_id,
|
m_last_line_id,
|
||||||
type,
|
type,
|
||||||
@@ -7040,7 +7133,7 @@ void GCodeProcessor::store_move_vertex(EMoveType type, EMovePathType path_type,
|
|||||||
static_cast<unsigned char>(filament_id),
|
static_cast<unsigned char>(filament_id),
|
||||||
m_cp_color.current,
|
m_cp_color.current,
|
||||||
//BBS: add plate's offset to the rendering vertices
|
//BBS: add plate's offset to the rendering vertices
|
||||||
Vec3f(m_end_position[X] + m_x_offset, m_end_position[Y] + m_y_offset, m_processing_start_custom_gcode ? m_first_layer_height : m_end_position[Z]- m_z_offset) + m_extruder_offsets[filament_id],
|
Vec3f(m_end_position[X] + m_x_offset, m_end_position[Y] + m_y_offset, store_z) + m_extruder_offsets[filament_id],
|
||||||
static_cast<float>(m_end_position[E] - m_start_position[E]),
|
static_cast<float>(m_end_position[E] - m_start_position[E]),
|
||||||
m_feedrate,
|
m_feedrate,
|
||||||
0.0f, // actual feedrate
|
0.0f, // actual feedrate
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "libslic3r/PrintConfig.hpp"
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
#include "libslic3r/CustomGCode.hpp"
|
#include "libslic3r/CustomGCode.hpp"
|
||||||
#include "libslic3r/MultiNozzleUtils.hpp"
|
#include "libslic3r/MultiNozzleUtils.hpp"
|
||||||
|
#include "libslic3r/GCode/MachineFrameTransform.hpp"
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <array>
|
#include <array>
|
||||||
@@ -276,6 +277,22 @@ class Print;
|
|||||||
bool support_traditional_timelapse{true};
|
bool support_traditional_timelapse{true};
|
||||||
float printable_height;
|
float printable_height;
|
||||||
float z_offset;
|
float z_offset;
|
||||||
|
// Belt printer: physical tilt magnitude (deg) parsed from the slicing-rotation
|
||||||
|
// header comment; used to enable the preview's belt view.
|
||||||
|
float belt_tilt_angle{ 0.f };
|
||||||
|
// Belt printer: machine-Z origin offset (mm) left in m_origin[Z] by the start
|
||||||
|
// G-code (purge-blob belt advance + G92 Z0 resets). Move positions are stored
|
||||||
|
// as gcode_Z + this offset, so the designed-view back-transform must subtract it
|
||||||
|
// to recover the model's belt coordinate.
|
||||||
|
float belt_z_origin{ 0.f };
|
||||||
|
// Belt printer: post-gcode shear/scale/post_remap is configured and
|
||||||
|
// non-identity. When set, the layer Z values in `moves` are in the
|
||||||
|
// machine frame and should not be compared against `printable_height`
|
||||||
|
// (which lives in the build-volume frame).
|
||||||
|
bool machine_frame_transform_active{ false };
|
||||||
|
RemapAxis preslice_remap_x{ RemapAxis::PosX };
|
||||||
|
RemapAxis preslice_remap_y{ RemapAxis::PosY };
|
||||||
|
RemapAxis preslice_remap_z{ RemapAxis::PosZ };
|
||||||
SettingsIds settings_ids;
|
SettingsIds settings_ids;
|
||||||
size_t filaments_count;
|
size_t filaments_count;
|
||||||
bool backtrace_enabled;
|
bool backtrace_enabled;
|
||||||
@@ -367,6 +384,12 @@ class Print;
|
|||||||
// Keep the SKIPPABLE per-type time on a copied result.
|
// Keep the SKIPPABLE per-type time on a copied result.
|
||||||
skippable_part_time = other.skippable_part_time;
|
skippable_part_time = other.skippable_part_time;
|
||||||
initial_layer_time = other.initial_layer_time;
|
initial_layer_time = other.initial_layer_time;
|
||||||
|
belt_tilt_angle = other.belt_tilt_angle;
|
||||||
|
belt_z_origin = other.belt_z_origin;
|
||||||
|
machine_frame_transform_active = other.machine_frame_transform_active;
|
||||||
|
preslice_remap_x = other.preslice_remap_x;
|
||||||
|
preslice_remap_y = other.preslice_remap_y;
|
||||||
|
preslice_remap_z = other.preslice_remap_z;
|
||||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||||
time = other.time;
|
time = other.time;
|
||||||
#endif
|
#endif
|
||||||
@@ -1136,6 +1159,12 @@ class Print;
|
|||||||
double m_x_offset{ 0 };
|
double m_x_offset{ 0 };
|
||||||
double m_y_offset{ 0 };
|
double m_y_offset{ 0 };
|
||||||
|
|
||||||
|
// Belt-printer post-gcode shear/scale/post_remap. Used by
|
||||||
|
// check_multi_extruder_gcode_valid to undo the machine-frame
|
||||||
|
// transform on move positions so bounds checks operate in the
|
||||||
|
// pre-machine-frame (build-volume) frame.
|
||||||
|
MachineFrameTransform m_machine_frame_transform;
|
||||||
|
|
||||||
unsigned int m_line_id;
|
unsigned int m_line_id;
|
||||||
unsigned int m_last_line_id;
|
unsigned int m_last_line_id;
|
||||||
float m_feedrate; // mm/s
|
float m_feedrate; // mm/s
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
#include "MachineFrameTransform.hpp"
|
||||||
|
#include "../Geometry.hpp"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
bool MachineFrameTransform::init_from_config(const PrintConfig &config)
|
||||||
|
{
|
||||||
|
m_active = false;
|
||||||
|
m_transform = Transform3d::Identity();
|
||||||
|
m_transform_inverse = Transform3d::Identity();
|
||||||
|
|
||||||
|
if (!config.belt_printer.value)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// The machine-frame transform is derived from the single belt tilt (axis +
|
||||||
|
// angle) that also drives the pre-slice mesh rotation. Expert decouple lets
|
||||||
|
// the machine-frame angle differ from the slicing rotation; otherwise both
|
||||||
|
// use belt_slice_rotation_angle.
|
||||||
|
const BeltRotationAxis axis = config.belt_slice_rotation.value;
|
||||||
|
if (axis == BeltRotationAxis::None || axis == BeltRotationAxis::Z)
|
||||||
|
return false; // Z is an in-plane spin: no machine-frame tilt.
|
||||||
|
|
||||||
|
const double angle_deg = config.belt_frame_tilt_decouple.value
|
||||||
|
? config.belt_frame_tilt_angle.value
|
||||||
|
: config.belt_slice_rotation_angle.value;
|
||||||
|
if (std::abs(angle_deg) <= EPSILON)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const double angle_rad = Geometry::deg2rad(angle_deg);
|
||||||
|
const double sin_a = std::sin(angle_rad);
|
||||||
|
if (std::abs(sin_a) <= EPSILON)
|
||||||
|
return false;
|
||||||
|
const double cot_a = std::cos(angle_rad) / sin_a;
|
||||||
|
const double inv_sin = 1.0 / std::abs(sin_a);
|
||||||
|
|
||||||
|
// This stage runs after the conventional belt axis swap. For an X-axis
|
||||||
|
// slicing rotation, remapped Y is model height and remapped Z is travel
|
||||||
|
// along the belt. Convert those Cartesian coordinates to machine axes with
|
||||||
|
// the established belt-printer convention:
|
||||||
|
// machine gantry = model height / sin(a)
|
||||||
|
// machine belt = model belt + model height * cot(a)
|
||||||
|
// The Y-rotation case is the same mapping on X/Z, with the rotation sign.
|
||||||
|
// At 45 degrees tan/cot and sin/cos are equal, which previously hid the
|
||||||
|
// incorrect complementary-angle formulas used by this unified transform.
|
||||||
|
Matrix3d shear = Matrix3d::Identity();
|
||||||
|
Matrix3d scale = Matrix3d::Identity();
|
||||||
|
if (axis == BeltRotationAxis::X) {
|
||||||
|
shear(2, 1) = cot_a; // Z from Y
|
||||||
|
scale(1, 1) = inv_sin; // Y
|
||||||
|
} else { // BeltRotationAxis::Y
|
||||||
|
shear(2, 0) = -cot_a; // Z from X
|
||||||
|
scale(0, 0) = inv_sin; // X
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply shear first, then scale (the historical default ShearThenScale order:
|
||||||
|
// result = scale * shear * p). For the canonical 45°/X belt this maps
|
||||||
|
// (x,y,z) -> (x, y/sin, y + z), matching the previous per-axis config.
|
||||||
|
Transform3d combined = Transform3d::Identity();
|
||||||
|
combined.linear() = scale * shear;
|
||||||
|
|
||||||
|
if (combined.isApprox(Transform3d::Identity()))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
m_transform = combined;
|
||||||
|
m_transform_inverse = combined.inverse();
|
||||||
|
m_active = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3d MachineFrameTransform::apply(const Vec3d &pos) const
|
||||||
|
{
|
||||||
|
if (!m_active)
|
||||||
|
return pos;
|
||||||
|
return m_transform * pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3d MachineFrameTransform::apply_inverse(const Vec3d &pos) const
|
||||||
|
{
|
||||||
|
if (!m_active)
|
||||||
|
return pos;
|
||||||
|
return m_transform_inverse * pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
#ifndef slic3r_MachineFrameTransform_hpp_
|
||||||
|
#define slic3r_MachineFrameTransform_hpp_
|
||||||
|
|
||||||
|
#include "../libslic3r.h"
|
||||||
|
#include "../Point.hpp"
|
||||||
|
#include "../PrintConfig.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
// Post-stage machine-frame transform for belt printers.
|
||||||
|
//
|
||||||
|
// Applied in BeltGCodeWriter::to_machine_coords AFTER the back-transform and
|
||||||
|
// the gcode_remap_* axis remap. Maps Cartesian (axis-permuted) G-code
|
||||||
|
// coordinates into the printer's physical machine frame.
|
||||||
|
//
|
||||||
|
// Derived entirely from the single belt tilt (belt_slice_rotation axis +
|
||||||
|
// belt_slice_rotation_angle): a shear coupling the height axis to the belt-feed
|
||||||
|
// axis (factor cot a) plus a 1/sin a scale on the gantry-height axis. The expert
|
||||||
|
// belt_frame_tilt_decouple flag lets the machine-frame angle differ from the
|
||||||
|
// pre-slice rotation angle via belt_frame_tilt_angle.
|
||||||
|
class MachineFrameTransform
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MachineFrameTransform() = default;
|
||||||
|
|
||||||
|
// Initialize from belt printer config. Returns true if a non-identity
|
||||||
|
// transform was computed. Inactive when belt_printer is disabled or
|
||||||
|
// both shear and scale are identity.
|
||||||
|
bool init_from_config(const PrintConfig &config);
|
||||||
|
|
||||||
|
// Apply the transform to a point. Returns pos unchanged if not active.
|
||||||
|
Vec3d apply(const Vec3d &pos) const;
|
||||||
|
|
||||||
|
// Apply the inverse transform. Returns pos unchanged if not active.
|
||||||
|
// Used by validators that need to compare emitted machine-frame
|
||||||
|
// coordinates against build-volume bounds.
|
||||||
|
Vec3d apply_inverse(const Vec3d &pos) const;
|
||||||
|
|
||||||
|
bool is_active() const { return m_active; }
|
||||||
|
|
||||||
|
// The composed shear*scale transform (identity when inactive). Exposed so the
|
||||||
|
// G-code viewer can build the machine->model back-transform for the upright
|
||||||
|
// ("designed") belt preview.
|
||||||
|
const Transform3d& transform() const { return m_transform; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool m_active = false;
|
||||||
|
Transform3d m_transform = Transform3d::Identity();
|
||||||
|
Transform3d m_transform_inverse = Transform3d::Identity();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
#endif // slic3r_MachineFrameTransform_hpp_
|
||||||
@@ -395,6 +395,10 @@ bool ToolOrdering::insert_wipe_tower_extruder()
|
|||||||
{
|
{
|
||||||
if (!m_print_config_ptr || !m_print_config_ptr->enable_prime_tower)
|
if (!m_print_config_ptr || !m_print_config_ptr->enable_prime_tower)
|
||||||
return false;
|
return false;
|
||||||
|
// Belt mode has no classic wipe tower; the dedicated wipe tower filament
|
||||||
|
// must not inject extra toolchanges into the purge prism planning.
|
||||||
|
if (m_print_config_ptr->belt_printer)
|
||||||
|
return false;
|
||||||
if (m_print_config_ptr->wipe_tower_filament == 0)
|
if (m_print_config_ptr->wipe_tower_filament == 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -492,6 +496,11 @@ ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extrude
|
|||||||
zs.emplace_back(layer->print_z);
|
zs.emplace_back(layer->print_z);
|
||||||
for (auto layer : object.support_layers())
|
for (auto layer : object.support_layers())
|
||||||
zs.emplace_back(layer->print_z);
|
zs.emplace_back(layer->print_z);
|
||||||
|
// Belt brim apron bands sit below the object's first layer and have no
|
||||||
|
// layer of their own, but tools_for_layer() asserts an exact Z match, so
|
||||||
|
// their print_z must be part of the ordering.
|
||||||
|
for (const BeltBrimBand &band : object.belt_brim_prologue())
|
||||||
|
zs.emplace_back(band.print_z);
|
||||||
this->initialize_layers(zs);
|
this->initialize_layers(zs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -536,6 +545,10 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool
|
|||||||
zs.emplace_back(layer->print_z);
|
zs.emplace_back(layer->print_z);
|
||||||
for (auto layer : object->support_layers())
|
for (auto layer : object->support_layers())
|
||||||
zs.emplace_back(layer->print_z);
|
zs.emplace_back(layer->print_z);
|
||||||
|
// See the single-object ctor: belt brim apron bands need their own
|
||||||
|
// ordering entries or tools_for_layer() will assert.
|
||||||
|
for (const BeltBrimBand &band : object->belt_brim_prologue())
|
||||||
|
zs.emplace_back(band.print_z);
|
||||||
|
|
||||||
max_layer_height = std::max(max_layer_height, object->config().layer_height.value);
|
max_layer_height = std::max(max_layer_height, object->config().layer_height.value);
|
||||||
}
|
}
|
||||||
@@ -970,6 +983,44 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Belt brim apron bands own their layers outright: they print below the
|
||||||
|
// object's first layer, so no object or support layer claims an extruder there
|
||||||
|
// and process_layer() would bail out at "Nothing to extrude". Claim the
|
||||||
|
// object's outer wall filament, in the same raw 1-based domain the loops above
|
||||||
|
// push. Deliberately not layer_tools.has_object, which drives skirt marking
|
||||||
|
// and wiping overrides.
|
||||||
|
if (! object.belt_brim_prologue().empty()) {
|
||||||
|
// 1-based, same domain the object/support pushes above use; reindexed to 0-based
|
||||||
|
// with the rest of the list later.
|
||||||
|
const unsigned int brim_filament = object.belt_brim_filament();
|
||||||
|
for (const BeltBrimBand &band : object.belt_brim_prologue()) {
|
||||||
|
if (band.fills.empty())
|
||||||
|
continue;
|
||||||
|
LayerTools &layer_tools = this->tools_for_layer(band.print_z);
|
||||||
|
layer_tools.extruders.push_back(brim_filament);
|
||||||
|
layer_tools.has_belt_brim = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Coincident brim bands (belt_brim_by_layer) print ON an object layer rather than
|
||||||
|
// below it, but that layer can produce no InstanceVisit in process_layer - a
|
||||||
|
// zero-extrusion lead-in slice with no coinciding support - and the band would then
|
||||||
|
// be silently dropped. Register the brim filament on every layer that carries a
|
||||||
|
// coincident band, in the same 1-based domain as the prologue push above, so a brim
|
||||||
|
// pass always exists there.
|
||||||
|
if (object.has_belt_brim()) {
|
||||||
|
const unsigned int brim_filament = object.belt_brim_filament();
|
||||||
|
const auto &by_layer = object.belt_brim_by_layer();
|
||||||
|
const size_t n = std::min(by_layer.size(), object.layers().size());
|
||||||
|
for (size_t i = 0; i < n; ++ i) {
|
||||||
|
if (by_layer[i].empty())
|
||||||
|
continue;
|
||||||
|
LayerTools &layer_tools = this->tools_for_layer(object.layers()[i]->print_z);
|
||||||
|
layer_tools.extruders.push_back(brim_filament);
|
||||||
|
layer_tools.has_belt_brim = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (auto& layer : m_layer_tools) {
|
for (auto& layer : m_layer_tools) {
|
||||||
// Sort and remove duplicates
|
// Sort and remove duplicates
|
||||||
sort_remove_duplicates(layer.extruders);
|
sort_remove_duplicates(layer.extruders);
|
||||||
@@ -1012,12 +1063,28 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_
|
|||||||
}
|
}
|
||||||
|
|
||||||
//FIXME this is a hack to get the ball rolling.
|
//FIXME this is a hack to get the ball rolling.
|
||||||
|
// The `print_z < object_bottom_z` clause reads "below the object" as "raft
|
||||||
|
// gap". On a belt printer that is wrong: the brim apron legitimately prints
|
||||||
|
// below the object's first layer, and treating those layers as raft would put a
|
||||||
|
// wipe tower at negative Z. Belt brim and the prime tower are mutually
|
||||||
|
// exclusive (rejected in Print::validate()), so simply drop the clause there.
|
||||||
|
//
|
||||||
|
// Gate on config.belt_printer, NOT on has_belt_brim: every layer below the
|
||||||
|
// object bottom on a belt printer is legitimately a sub-object stream - brim
|
||||||
|
// apron, belt support printed below Z0, or the object's own lead-in - and none of
|
||||||
|
// them is ever raft, because Print::validate() rejects raft_layers>0 on a belt
|
||||||
|
// printer outright. Narrowing this to has_belt_brim would reclassify
|
||||||
|
// belt-support-below-floor layers as raft on brim-less belt prints and reintroduce
|
||||||
|
// the negative-Z wipe tower, so the broad belt_printer gate is correct.
|
||||||
|
const bool belt_no_raft_gap = config.belt_printer.value;
|
||||||
for (LayerTools < : m_layer_tools)
|
for (LayerTools < : m_layer_tools)
|
||||||
lt.has_wipe_tower |= ((lt.has_object || lt.has_support) && (config.timelapse_type == TimelapseType::tlSmooth || lt.wipe_tower_partitions > 0))
|
lt.has_wipe_tower |= ((lt.has_object || lt.has_support) && (config.timelapse_type == TimelapseType::tlSmooth || lt.wipe_tower_partitions > 0))
|
||||||
|| lt.print_z < object_bottom_z + EPSILON;
|
|| (! belt_no_raft_gap && lt.print_z < object_bottom_z + EPSILON);
|
||||||
|
|
||||||
// Test for a raft, insert additional wipe tower layer to fill in the raft separation gap.
|
// Test for a raft, insert additional wipe tower layer to fill in the raft separation gap.
|
||||||
for (size_t i = 0; i + 1 < m_layer_tools.size(); ++ i) {
|
// Skipped on belt printers for the same reason as the clause above: layers
|
||||||
|
// below the object are brim apron, not raft.
|
||||||
|
for (size_t i = 0; ! belt_no_raft_gap && i + 1 < m_layer_tools.size(); ++ i) {
|
||||||
const LayerTools < = m_layer_tools[i];
|
const LayerTools < = m_layer_tools[i];
|
||||||
const LayerTools <_next = m_layer_tools[i + 1];
|
const LayerTools <_next = m_layer_tools[i + 1];
|
||||||
if (lt.print_z < object_bottom_z + EPSILON && lt_next.print_z >= object_bottom_z + EPSILON) {
|
if (lt.print_z < object_bottom_z + EPSILON && lt_next.print_z >= object_bottom_z + EPSILON) {
|
||||||
|
|||||||
@@ -75,6 +75,12 @@ public:
|
|||||||
void set_layer_tools_ptr(const LayerTools* lt) { m_layer_tools = lt; }
|
void set_layer_tools_ptr(const LayerTools* lt) { m_layer_tools = lt; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// Returns true if entity is not printed with its usual extruder for a given copy.
|
||||||
|
bool is_entity_overridden(const ExtrusionEntity* entity, const PrintObject *object, size_t copy_id) const {
|
||||||
|
auto it = entity_map.find(std::make_tuple(entity, object));
|
||||||
|
return it != entity_map.end() && copy_id < it->second.size() && it->second[copy_id] != -1;
|
||||||
|
}
|
||||||
|
|
||||||
int first_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const;
|
int first_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const;
|
||||||
int last_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const;
|
int last_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const;
|
||||||
|
|
||||||
@@ -84,12 +90,6 @@ private:
|
|||||||
void set_support_extruder_override(const PrintObject* object, size_t copy_id, int extruder, size_t num_of_copies);
|
void set_support_extruder_override(const PrintObject* object, size_t copy_id, int extruder, size_t num_of_copies);
|
||||||
void set_support_interface_extruder_override(const PrintObject* object, size_t copy_id, int extruder, size_t num_of_copies);
|
void set_support_interface_extruder_override(const PrintObject* object, size_t copy_id, int extruder, size_t num_of_copies);
|
||||||
|
|
||||||
// Returns true in case that entity is not printed with its usual extruder for a given copy:
|
|
||||||
bool is_entity_overridden(const ExtrusionEntity* entity, const PrintObject *object, size_t copy_id) const {
|
|
||||||
auto it = entity_map.find(std::make_tuple(entity, object));
|
|
||||||
return it == entity_map.end() ? false : it->second[copy_id] != -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::map<std::tuple<const ExtrusionEntity*, const PrintObject *>, ExtruderPerCopy> entity_map; // to keep track of who prints what
|
std::map<std::tuple<const ExtrusionEntity*, const PrintObject *>, ExtruderPerCopy> entity_map; // to keep track of who prints what
|
||||||
// BBS
|
// BBS
|
||||||
std::map<const PrintObject*, int> support_map;
|
std::map<const PrintObject*, int> support_map;
|
||||||
@@ -165,6 +165,10 @@ public:
|
|||||||
// Should a skirt be printed at this layer?
|
// Should a skirt be printed at this layer?
|
||||||
// Layers are marked for infinite skirt aka draft shield. Not all the layers have to be printed.
|
// Layers are marked for infinite skirt aka draft shield. Not all the layers have to be printed.
|
||||||
bool has_skirt = false;
|
bool has_skirt = false;
|
||||||
|
// Belt printers: is this one of the brim-only apron layers below the object's
|
||||||
|
// first layer? Kept separate from has_object so skirt marking and wiping
|
||||||
|
// overrides are unaffected.
|
||||||
|
bool has_belt_brim = false;
|
||||||
// Will there be anything extruded on this layer for the wipe tower?
|
// Will there be anything extruded on this layer for the wipe tower?
|
||||||
// Due to the support layers possibly interleaving the object layers,
|
// Due to the support layers possibly interleaving the object layers,
|
||||||
// wipe tower will be disabled for some support only layers.
|
// wipe tower will be disabled for some support only layers.
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "GCodeWriter.hpp"
|
#include "GCodeWriter.hpp"
|
||||||
#include "CustomGCode.hpp"
|
#include "CustomGCode.hpp"
|
||||||
|
#include "Geometry.hpp"
|
||||||
#include "I18N.hpp"
|
#include "I18N.hpp"
|
||||||
#include "PrintConfig.hpp"
|
#include "PrintConfig.hpp"
|
||||||
#include "ClipperUtils.hpp"
|
#include "ClipperUtils.hpp"
|
||||||
@@ -23,6 +24,36 @@ namespace Slic3r {
|
|||||||
|
|
||||||
bool GCodeWriter::full_gcode_comment = true;
|
bool GCodeWriter::full_gcode_comment = true;
|
||||||
|
|
||||||
|
void GCodeWriter::set_axis_remap(int rx, int ry, int rz)
|
||||||
|
{
|
||||||
|
m_remap_x = rx;
|
||||||
|
m_remap_y = ry;
|
||||||
|
m_remap_z = rz;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GCodeWriter::set_build_volume_max(const Vec3d &max)
|
||||||
|
{
|
||||||
|
m_build_vol_max = max;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GCodeWriter::has_axis_remap() const
|
||||||
|
{
|
||||||
|
return m_remap_x != 0 || m_remap_y != 1 || m_remap_z != 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3d GCodeWriter::apply_axis_remap(const Vec3d &pos) const
|
||||||
|
{
|
||||||
|
if (!has_axis_remap())
|
||||||
|
return pos;
|
||||||
|
auto remap = [this, &pos](int r) -> double {
|
||||||
|
int axis = r % 3;
|
||||||
|
if (r < 3) return pos[axis];
|
||||||
|
if (r < 6) return -pos[axis];
|
||||||
|
return m_build_vol_max[axis] - pos[axis];
|
||||||
|
};
|
||||||
|
return { remap(m_remap_x), remap(m_remap_y), remap(m_remap_z) };
|
||||||
|
}
|
||||||
|
|
||||||
bool GCodeWriter::supports_separate_travel_acceleration(GCodeFlavor flavor)
|
bool GCodeWriter::supports_separate_travel_acceleration(GCodeFlavor flavor)
|
||||||
{
|
{
|
||||||
return (flavor == gcfRepetier || flavor == gcfMarlinFirmware || flavor == gcfRepRapFirmware);
|
return (flavor == gcfRepetier || flavor == gcfMarlinFirmware || flavor == gcfRepRapFirmware);
|
||||||
@@ -757,7 +788,13 @@ std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &com
|
|||||||
Vec2d point_on_plate = { point(0) - m_x_offset, point(1) - m_y_offset };
|
Vec2d point_on_plate = { point(0) - m_x_offset, point(1) - m_y_offset };
|
||||||
|
|
||||||
GCodeG1Formatter w;
|
GCodeG1Formatter w;
|
||||||
w.emit_xy(point_on_plate);
|
if (has_axis_remap()) {
|
||||||
|
// Axis remap may couple XY with Z; emit full XYZ in machine coordinates.
|
||||||
|
Vec3d machine = apply_axis_remap(Vec3d(point_on_plate.x(), point_on_plate.y(), m_pos.z()));
|
||||||
|
w.emit_xyz(machine);
|
||||||
|
} else {
|
||||||
|
w.emit_xy(point_on_plate);
|
||||||
|
}
|
||||||
auto speed = m_is_first_layer
|
auto speed = m_is_first_layer
|
||||||
? this->config.get_abs_value_at("initial_layer_travel_speed", m_cached_extruder_idx) : this->config.travel_speed.get_at(m_cached_extruder_idx);
|
? this->config.get_abs_value_at("initial_layer_travel_speed", m_cached_extruder_idx) : this->config.travel_speed.get_at(m_cached_extruder_idx);
|
||||||
w.emit_f(speed * 60.0);
|
w.emit_f(speed * 60.0);
|
||||||
@@ -797,8 +834,9 @@ std::string GCodeWriter::lazy_lift(LiftType lift_type, bool spiral_vase)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// BBS: immediately execute an undelayed lift move with a spiral lift pattern
|
// BBS: immediately execute an undelayed lift move with a spiral lift pattern
|
||||||
// designed specifically for subsequent gcode injection (e.g. timelapse)
|
// designed specifically for subsequent gcode injection (e.g. timelapse)
|
||||||
std::string GCodeWriter::eager_lift(const LiftType type) {
|
std::string GCodeWriter::eager_lift(const LiftType type) {
|
||||||
|
const LiftType effective_type = type;
|
||||||
std::string lift_move;
|
std::string lift_move;
|
||||||
double target_lift = 0;
|
double target_lift = 0;
|
||||||
{
|
{
|
||||||
@@ -812,7 +850,7 @@ std::string GCodeWriter::eager_lift(const LiftType type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// BBS: spiral lift only safe with known position
|
// BBS: spiral lift only safe with known position
|
||||||
if (type == LiftType::SpiralLift && this->is_current_position_clear()) {
|
if (effective_type == LiftType::SpiralLift && this->is_current_position_clear()) {
|
||||||
double radius = target_lift / (2 * PI * atan(filament()->travel_slope()));
|
double radius = target_lift / (2 * PI * atan(filament()->travel_slope()));
|
||||||
// static spiral alignment when no move in x,y plane.
|
// static spiral alignment when no move in x,y plane.
|
||||||
// spiral centra is a radius distance to the right (y=0)
|
// spiral centra is a radius distance to the right (y=0)
|
||||||
@@ -899,7 +937,10 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co
|
|||||||
Vec2d temp = delta_no_z.normalized() * delta(2) / tan(this->filament()->travel_slope());
|
Vec2d temp = delta_no_z.normalized() * delta(2) / tan(this->filament()->travel_slope());
|
||||||
Vec3d slope_top_point = Vec3d(temp(0), temp(1), delta(2)) + source;
|
Vec3d slope_top_point = Vec3d(temp(0), temp(1), delta(2)) + source;
|
||||||
GCodeG1Formatter w0;
|
GCodeG1Formatter w0;
|
||||||
w0.emit_xyz(slope_top_point);
|
// A slope lift is a straight (linear) diagonal move, so remapping its
|
||||||
|
// endpoint is exact. Route the destination through apply_axis_remap()
|
||||||
|
// when a remap is active (no-op at identity).
|
||||||
|
w0.emit_xyz(has_axis_remap() ? apply_axis_remap(slope_top_point) : slope_top_point);
|
||||||
w0.emit_f(travel_speed * 60.0);
|
w0.emit_f(travel_speed * 60.0);
|
||||||
//BBS
|
//BBS
|
||||||
w0.emit_comment(GCodeWriter::full_gcode_comment, comment);
|
w0.emit_comment(GCodeWriter::full_gcode_comment, comment);
|
||||||
@@ -913,7 +954,14 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co
|
|||||||
std::string xy_z_move;
|
std::string xy_z_move;
|
||||||
{
|
{
|
||||||
GCodeG1Formatter w0;
|
GCodeG1Formatter w0;
|
||||||
if (this->is_current_position_clear()) {
|
if (has_axis_remap()) {
|
||||||
|
// Remap may couple XY with Z; emit full XYZ in machine coordinates.
|
||||||
|
w0.emit_xyz(apply_axis_remap(target));
|
||||||
|
w0.emit_f(travel_speed * 60.0);
|
||||||
|
w0.emit_comment(GCodeWriter::full_gcode_comment, comment);
|
||||||
|
xy_z_move = w0.string();
|
||||||
|
}
|
||||||
|
else if (this->is_current_position_clear()) {
|
||||||
w0.emit_xyz(target);
|
w0.emit_xyz(target);
|
||||||
w0.emit_f(travel_speed * 60.0);
|
w0.emit_f(travel_speed * 60.0);
|
||||||
w0.emit_comment(GCodeWriter::full_gcode_comment, comment);
|
w0.emit_comment(GCodeWriter::full_gcode_comment, comment);
|
||||||
@@ -951,7 +999,13 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co
|
|||||||
Vec3d point_on_plate = { dest_point(0) - m_x_offset, dest_point(1) - m_y_offset, dest_point(2) };
|
Vec3d point_on_plate = { dest_point(0) - m_x_offset, dest_point(1) - m_y_offset, dest_point(2) };
|
||||||
std::string out_string;
|
std::string out_string;
|
||||||
GCodeG1Formatter w;
|
GCodeG1Formatter w;
|
||||||
if (!this->is_current_position_clear())
|
if (has_axis_remap()) {
|
||||||
|
// Remap may couple XY with Z; emit full XYZ in machine coordinates.
|
||||||
|
w.emit_xyz(apply_axis_remap(point_on_plate));
|
||||||
|
w.emit_f(this->config.travel_speed.get_at(m_cached_extruder_idx) * 60.0);
|
||||||
|
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
|
||||||
|
out_string = w.string();
|
||||||
|
} else if (!this->is_current_position_clear())
|
||||||
{
|
{
|
||||||
//force to move xy first then z after filament change
|
//force to move xy first then z after filament change
|
||||||
w.emit_xy(Vec2d(point_on_plate.x(), point_on_plate.y()));
|
w.emit_xy(Vec2d(point_on_plate.x(), point_on_plate.y()));
|
||||||
@@ -1001,7 +1055,13 @@ std::string GCodeWriter::_travel_to_z(double z, const std::string &comment)
|
|||||||
}
|
}
|
||||||
|
|
||||||
GCodeG1Formatter w;
|
GCodeG1Formatter w;
|
||||||
w.emit_z(z);
|
if (has_axis_remap()) {
|
||||||
|
// Remap may couple Z with other axes; emit full XYZ.
|
||||||
|
Vec3d machine = apply_axis_remap(Vec3d(m_pos.x() - m_x_offset, m_pos.y() - m_y_offset, z));
|
||||||
|
w.emit_xyz(machine);
|
||||||
|
} else {
|
||||||
|
w.emit_z(z);
|
||||||
|
}
|
||||||
w.emit_f(speed * 60.0);
|
w.emit_f(speed * 60.0);
|
||||||
//BBS
|
//BBS
|
||||||
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
|
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
|
||||||
@@ -1010,6 +1070,14 @@ std::string GCodeWriter::_travel_to_z(double z, const std::string &comment)
|
|||||||
|
|
||||||
std::string GCodeWriter::_spiral_travel_to_z(double z, const Vec2d &ij_offset, const std::string &comment)
|
std::string GCodeWriter::_spiral_travel_to_z(double z, const Vec2d &ij_offset, const std::string &comment)
|
||||||
{
|
{
|
||||||
|
// A circular XY arc / spiral lift cannot be correctly axis-remapped by
|
||||||
|
// transforming only its endpoint: the arc plane (G17/XY) and the I-J center
|
||||||
|
// would change under the remap. When an axis remap is active, fall back to a
|
||||||
|
// plain linear lift instead of emitting a possibly-wrong spiral/arc. This
|
||||||
|
// single guard covers every spiral call site (lazy/eager lift and travel_to_xyz).
|
||||||
|
if (has_axis_remap())
|
||||||
|
return _travel_to_z(z, comment);
|
||||||
|
|
||||||
std::string output;
|
std::string output;
|
||||||
double speed = this->config.travel_speed_z.get_at(m_cached_extruder_idx);
|
double speed = this->config.travel_speed_z.get_at(m_cached_extruder_idx);
|
||||||
|
|
||||||
@@ -1109,7 +1177,12 @@ std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std:
|
|||||||
Vec2d point_on_plate = { point(0) - m_x_offset, point(1) - m_y_offset };
|
Vec2d point_on_plate = { point(0) - m_x_offset, point(1) - m_y_offset };
|
||||||
|
|
||||||
GCodeG1Formatter w;
|
GCodeG1Formatter w;
|
||||||
w.emit_xy(point_on_plate);
|
if (has_axis_remap()) {
|
||||||
|
Vec3d machine = apply_axis_remap(Vec3d(point_on_plate.x(), point_on_plate.y(), m_pos.z()));
|
||||||
|
w.emit_xyz(machine);
|
||||||
|
} else {
|
||||||
|
w.emit_xy(point_on_plate);
|
||||||
|
}
|
||||||
if (!force_no_extrusion)
|
if (!force_no_extrusion)
|
||||||
w.emit_e(filament()->E());
|
w.emit_e(filament()->E());
|
||||||
//BBS
|
//BBS
|
||||||
@@ -1155,10 +1228,18 @@ std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std
|
|||||||
Vec3d point_on_plate = { point(0) - m_x_offset, point(1) - m_y_offset, point(2) };
|
Vec3d point_on_plate = { point(0) - m_x_offset, point(1) - m_y_offset, point(2) };
|
||||||
|
|
||||||
GCodeG1Formatter w;
|
GCodeG1Formatter w;
|
||||||
if (z_changed)
|
if (has_axis_remap()) {
|
||||||
|
// z_changed was computed from the ORIGINAL slicing Z, but an axis remap can
|
||||||
|
// make machine-Z depend on slicing X/Y. An X/Y-only move (slicing-Z
|
||||||
|
// unchanged) would then drop the required machine-Z word, so always emit
|
||||||
|
// full XYZ whenever a remap is active.
|
||||||
|
point_on_plate = apply_axis_remap(point_on_plate);
|
||||||
w.emit_xyz(point_on_plate);
|
w.emit_xyz(point_on_plate);
|
||||||
else
|
} else if (z_changed) {
|
||||||
|
w.emit_xyz(point_on_plate);
|
||||||
|
} else {
|
||||||
w.emit_xy(Vec2d(point_on_plate.x(), point_on_plate.y()));
|
w.emit_xy(Vec2d(point_on_plate.x(), point_on_plate.y()));
|
||||||
|
}
|
||||||
if (!force_no_extrusion)
|
if (!force_no_extrusion)
|
||||||
w.emit_e(filament()->E());
|
w.emit_e(filament()->E());
|
||||||
//BBS
|
//BBS
|
||||||
|
|||||||
@@ -9,11 +9,11 @@
|
|||||||
#include "Polygon.hpp"
|
#include "Polygon.hpp"
|
||||||
#include "PrintConfig.hpp"
|
#include "PrintConfig.hpp"
|
||||||
#include "GCode/CoolingBuffer.hpp"
|
#include "GCode/CoolingBuffer.hpp"
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
class GCodeWriter {
|
class GCodeWriter {
|
||||||
public:
|
public:
|
||||||
|
virtual ~GCodeWriter() = default;
|
||||||
GCodeConfig config;
|
GCodeConfig config;
|
||||||
bool multiple_extruders;
|
bool multiple_extruders;
|
||||||
|
|
||||||
@@ -78,23 +78,23 @@ public:
|
|||||||
std::string set_speed(double F, const std::string &comment = std::string(), const std::string &cooling_marker = std::string());
|
std::string set_speed(double F, const std::string &comment = std::string(), const std::string &cooling_marker = std::string());
|
||||||
// SoftFever NOTE: the returned speed is mm/minute
|
// SoftFever NOTE: the returned speed is mm/minute
|
||||||
double get_current_speed() const { return m_current_speed;}
|
double get_current_speed() const { return m_current_speed;}
|
||||||
std::string travel_to_xy(const Vec2d &point, const std::string &comment = std::string());
|
virtual std::string travel_to_xy(const Vec2d &point, const std::string &comment = std::string());
|
||||||
std::string travel_to_xyz(const Vec3d &point, const std::string &comment = std::string(), bool force_z = false);
|
virtual std::string travel_to_xyz(const Vec3d &point, const std::string &comment = std::string(), bool force_z = false);
|
||||||
std::string travel_to_z(double z, const std::string &comment = std::string(), bool force = false);
|
std::string travel_to_z(double z, const std::string &comment = std::string(), bool force = false);
|
||||||
bool will_move_z(double z) const;
|
bool will_move_z(double z) const;
|
||||||
std::string extrude_to_xy(const Vec2d &point, double dE, const std::string &comment = std::string(), bool force_no_extrusion = false);
|
virtual std::string extrude_to_xy(const Vec2d &point, double dE, const std::string &comment = std::string(), bool force_no_extrusion = false);
|
||||||
//BBS: generate G2 or G3 extrude which moves by arc
|
//BBS: generate G2 or G3 extrude which moves by arc
|
||||||
std::string extrude_arc_to_xy(const Vec2d &point, const Vec2d ¢er_offset, double dE, const bool is_ccw, const std::string &comment = std::string(), bool force_no_extrusion = false);
|
std::string extrude_arc_to_xy(const Vec2d &point, const Vec2d ¢er_offset, double dE, const bool is_ccw, const std::string &comment = std::string(), bool force_no_extrusion = false);
|
||||||
std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment = std::string(), bool force_no_extrusion = false);
|
virtual std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment = std::string(), bool force_no_extrusion = false);
|
||||||
std::string retract(bool before_wipe = false, double retract_length = 0);
|
std::string retract(bool before_wipe = false, double retract_length = 0);
|
||||||
std::string retract_for_toolchange(bool before_wipe = false, double retract_length = 0);
|
std::string retract_for_toolchange(bool before_wipe = false, double retract_length = 0);
|
||||||
// extra_retract adds a small over-extrusion to the deretract move (PETG pre-extrusion).
|
// extra_retract adds a small over-extrusion to the deretract move (PETG pre-extrusion).
|
||||||
// Default 0 -> byte-identical to the plain deretract.
|
// Default 0 -> byte-identical to the plain deretract.
|
||||||
std::string unretract(float extra_retract = 0.f);
|
std::string unretract(float extra_retract = 0.f);
|
||||||
// do lift instantly
|
// do lift instantly
|
||||||
std::string eager_lift(const LiftType type);
|
virtual std::string eager_lift(const LiftType type);
|
||||||
// record a lift request, do realy lift in next travel
|
// record a lift request, do realy lift in next travel
|
||||||
std::string lazy_lift(LiftType lift_type = LiftType::NormalLift, bool spiral_vase = false);
|
virtual std::string lazy_lift(LiftType lift_type = LiftType::NormalLift, bool spiral_vase = false);
|
||||||
std::string unlift();
|
std::string unlift();
|
||||||
const Vec3d& get_position() const { return m_pos; }
|
const Vec3d& get_position() const { return m_pos; }
|
||||||
Vec3d& get_position() { return m_pos; }
|
Vec3d& get_position() { return m_pos; }
|
||||||
@@ -136,16 +136,48 @@ public:
|
|||||||
void invalidate_acceleration() { m_last_acceleration = 0; m_last_travel_acceleration = 0; }
|
void invalidate_acceleration() { m_last_acceleration = 0; m_last_travel_acceleration = 0; }
|
||||||
void invalidate_jerk() { m_last_jerk = 0; }
|
void invalidate_jerk() { m_last_jerk = 0; }
|
||||||
|
|
||||||
|
// Axis remap: permute/negate/reverse axes in G-code output.
|
||||||
|
// Works standalone (without belt mode) for printers with non-standard axis conventions.
|
||||||
|
void set_axis_remap(int rx, int ry, int rz);
|
||||||
|
void set_build_volume_max(const Vec3d &max);
|
||||||
|
bool has_axis_remap() const;
|
||||||
|
|
||||||
// Returns whether this flavor supports separate print and travel acceleration.
|
// Returns whether this flavor supports separate print and travel acceleration.
|
||||||
static bool supports_separate_travel_acceleration(GCodeFlavor flavor);
|
static bool supports_separate_travel_acceleration(GCodeFlavor flavor);
|
||||||
private:
|
protected:
|
||||||
|
// Position/lift/offset state — accessible to subclasses (e.g. BeltGCodeWriter)
|
||||||
|
Vec3d m_pos = Vec3d::Zero();
|
||||||
|
double m_x_offset{ 0 };
|
||||||
|
double m_y_offset{ 0 };
|
||||||
|
double m_lifted;
|
||||||
|
double m_to_lift;
|
||||||
|
LiftType m_to_lift_type;
|
||||||
|
bool m_is_first_layer = true;
|
||||||
|
bool m_is_current_pos_clear = false;
|
||||||
|
double m_current_speed;
|
||||||
|
|
||||||
|
virtual std::string _travel_to_z(double z, const std::string &comment);
|
||||||
|
|
||||||
|
// Axis remap state — accessible to subclasses.
|
||||||
|
int m_remap_x = 0; // RemapAxis: 0=+X, 1=+Y, 2=+Z, 3=-X, etc.
|
||||||
|
int m_remap_y = 1;
|
||||||
|
int m_remap_z = 2;
|
||||||
|
Vec3d m_build_vol_max = Vec3d::Zero();
|
||||||
|
|
||||||
|
// Apply axis remap to a point. Returns pos unchanged if remap is identity.
|
||||||
|
Vec3d apply_axis_remap(const Vec3d &pos) const;
|
||||||
|
|
||||||
|
// Motion uses the global/base process variant until a filament becomes active.
|
||||||
|
// Protected so BeltGCodeWriter indexes the per-extruder speed options (travel_speed,
|
||||||
|
// travel_speed_z, initial_layer_travel_speed) exactly as the base writer does.
|
||||||
|
size_t m_cached_extruder_idx;
|
||||||
|
|
||||||
|
private:
|
||||||
// Extruders are sorted by their ID, so that binary search is possible.
|
// Extruders are sorted by their ID, so that binary search is possible.
|
||||||
std::vector<Extruder> m_filament_extruders;
|
std::vector<Extruder> m_filament_extruders;
|
||||||
bool m_single_extruder_multi_material;
|
bool m_single_extruder_multi_material;
|
||||||
std::vector<Extruder*> m_curr_filament_extruder;
|
std::vector<Extruder*> m_curr_filament_extruder;
|
||||||
int m_curr_extruder_id;
|
int m_curr_extruder_id;
|
||||||
// Motion uses the global/base process variant until a filament becomes active.
|
|
||||||
size_t m_cached_extruder_idx;
|
|
||||||
unsigned int m_last_acceleration;
|
unsigned int m_last_acceleration;
|
||||||
unsigned int m_last_travel_acceleration;
|
unsigned int m_last_travel_acceleration;
|
||||||
std::vector<unsigned int> m_max_travel_acceleration;
|
std::vector<unsigned int> m_max_travel_acceleration;
|
||||||
@@ -167,19 +199,6 @@ public:
|
|||||||
//BBS
|
//BBS
|
||||||
int m_last_bed_temperature;
|
int m_last_bed_temperature;
|
||||||
bool m_last_bed_temperature_reached;
|
bool m_last_bed_temperature_reached;
|
||||||
double m_lifted;
|
|
||||||
|
|
||||||
// BBS
|
|
||||||
double m_to_lift;
|
|
||||||
LiftType m_to_lift_type;
|
|
||||||
Vec3d m_pos = Vec3d::Zero();
|
|
||||||
//BBS: this flag is used to indicate whether the m_pos is real.
|
|
||||||
//A example that of the first move, the m_pos is zero, but the real position of extruder doesn't
|
|
||||||
//Pos must be clear after the first xyz travel move
|
|
||||||
bool m_is_current_pos_clear = false;
|
|
||||||
//BBS: x, y offset for gcode generated
|
|
||||||
double m_x_offset{ 0 };
|
|
||||||
double m_y_offset{ 0 };
|
|
||||||
|
|
||||||
// Orca: slicing resolution in mm
|
// Orca: slicing resolution in mm
|
||||||
double m_resolution = 0.01;
|
double m_resolution = 0.01;
|
||||||
@@ -191,21 +210,18 @@ public:
|
|||||||
// non-rectangular beds such as delta/circular printers.
|
// non-rectangular beds such as delta/circular printers.
|
||||||
Polygon m_bed_printable_area;
|
Polygon m_bed_printable_area;
|
||||||
std::vector<Polygon> m_extruder_printable_areas;
|
std::vector<Polygon> m_extruder_printable_areas;
|
||||||
|
|
||||||
std::string m_gcode_label_objects_start;
|
std::string m_gcode_label_objects_start;
|
||||||
std::string m_gcode_label_objects_end;
|
std::string m_gcode_label_objects_end;
|
||||||
|
|
||||||
//SoftFever
|
//SoftFever
|
||||||
bool m_is_bbl_printers = false;
|
bool m_is_bbl_printers = false;
|
||||||
double m_current_speed;
|
|
||||||
bool m_is_first_layer = true;
|
|
||||||
|
|
||||||
enum class Acceleration {
|
enum class Acceleration {
|
||||||
Travel,
|
Travel,
|
||||||
Print
|
Print
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string _travel_to_z(double z, const std::string &comment);
|
|
||||||
std::string _spiral_travel_to_z(double z, const Vec2d &ij_offset, const std::string &comment);
|
std::string _spiral_travel_to_z(double z, const Vec2d &ij_offset, const std::string &comment);
|
||||||
// Orca: printable area of the active extruder (per-extruder when configured, otherwise the bed). Null when unknown.
|
// Orca: printable area of the active extruder (per-extruder when configured, otherwise the bed). Null when unknown.
|
||||||
const Polygon *active_printable_area() const;
|
const Polygon *active_printable_area() const;
|
||||||
|
|||||||
@@ -1124,7 +1124,7 @@ static std::vector<std::string> s_Preset_print_options{
|
|||||||
"top_surface_speed", "support_speed", "support_object_xy_distance", "support_object_first_layer_gap", "support_interface_speed",
|
"top_surface_speed", "support_speed", "support_object_xy_distance", "support_object_first_layer_gap", "support_interface_speed",
|
||||||
"bridge_speed", "internal_bridge_speed", "gap_infill_speed", "travel_speed", "travel_speed_z", "initial_layer_speed",
|
"bridge_speed", "internal_bridge_speed", "gap_infill_speed", "travel_speed", "travel_speed_z", "initial_layer_speed",
|
||||||
"outer_wall_acceleration", "initial_layer_acceleration", "top_surface_acceleration", "default_acceleration", "skirt_type", "skirt_loops", "skirt_speed","min_skirt_length", "skirt_distance", "skirt_start_angle", "skirt_height","single_loop_draft_shield", "draft_shield",
|
"outer_wall_acceleration", "initial_layer_acceleration", "top_surface_acceleration", "default_acceleration", "skirt_type", "skirt_loops", "skirt_speed","min_skirt_length", "skirt_distance", "skirt_start_angle", "skirt_height","single_loop_draft_shield", "draft_shield",
|
||||||
"brim_width", "brim_object_gap", "brim_flow_ratio", "brim_use_efc_outline", "combine_brims", "brim_type", "brim_ears_max_angle", "brim_ears_detection_length", "brim_ears_outer_only", "enable_support", "support_type", "support_threshold_angle", "support_threshold_overlap","enforce_support_layers",
|
"brim_width", "leading_brim_length", "extra_brim_width", "brim_object_gap", "brim_flow_ratio", "brim_use_efc_outline", "combine_brims", "brim_type", "brim_ears_max_angle", "brim_ears_detection_length", "brim_ears_outer_only", "enable_support", "support_type", "support_threshold_angle", "support_threshold_overlap","enforce_support_layers",
|
||||||
"raft_layers", "raft_first_layer_density", "raft_first_layer_expansion", "raft_contact_distance", "raft_expansion",
|
"raft_layers", "raft_first_layer_density", "raft_first_layer_expansion", "raft_contact_distance", "raft_expansion",
|
||||||
"support_base_pattern", "support_base_pattern_spacing", "support_expansion", "support_style",
|
"support_base_pattern", "support_base_pattern_spacing", "support_expansion", "support_style",
|
||||||
// BBS
|
// BBS
|
||||||
@@ -1194,6 +1194,8 @@ static std::vector<std::string> s_Preset_print_options{
|
|||||||
"prime_volume",
|
"prime_volume",
|
||||||
"prime_tower_infill_gap",
|
"prime_tower_infill_gap",
|
||||||
"prime_tower_flat_ironing",
|
"prime_tower_flat_ironing",
|
||||||
|
"belt_purge_tower_width",
|
||||||
|
"belt_purge_tower_object",
|
||||||
"enable_tower_interface_features",
|
"enable_tower_interface_features",
|
||||||
"enable_tower_interface_cooldown_during_tower",
|
"enable_tower_interface_cooldown_during_tower",
|
||||||
"wipe_tower_no_sparse_layers",
|
"wipe_tower_no_sparse_layers",
|
||||||
@@ -1439,8 +1441,17 @@ static std::vector<std::string> s_Preset_machine_limits_options {
|
|||||||
|
|
||||||
static std::vector<std::string> s_Preset_printer_options {
|
static std::vector<std::string> s_Preset_printer_options {
|
||||||
"printer_technology",
|
"printer_technology",
|
||||||
"printable_area", "extruder_printable_area", "support_parallel_printheads", "parallel_printheads_count", "parallel_printheads_bed_exclude_areas", "bed_exclude_area","bed_custom_texture", "bed_custom_model", "gcode_flavor",
|
"printable_area", "extruder_printable_area", "support_parallel_printheads", "parallel_printheads_count", "parallel_printheads_bed_exclude_areas", "bed_exclude_area","bed_custom_texture", "bed_custom_model", "build_plate_tilt_x", "build_plate_tilt_y", "belt_printer", "belt_printer_infinite_y",
|
||||||
"gcode_skip_config_block", "fan_kickstart", "part_cooling_fan_min_pwm", "fan_speedup_time", "fan_speedup_overhangs",
|
"belt_slice_rotation", "belt_slice_rotation_angle", "belt_slice_rotation_global",
|
||||||
|
"preslice_remap_x", "preslice_remap_y", "preslice_remap_z", "preslice_remap_global",
|
||||||
|
"gcode_remap_x", "gcode_remap_y", "gcode_remap_z", "gcode_back_transform",
|
||||||
|
"belt_frame_tilt_decouple", "belt_frame_tilt_angle",
|
||||||
|
"belt_preslice_global",
|
||||||
|
"first_layer_plane", "first_layer_plane_offset", "first_layer_plane_thickness",
|
||||||
|
"belt_support_floor_offset", "belt_support_floor_mode", "belt_support_z_offset_mode",
|
||||||
|
"enable_belt_purge_tower",
|
||||||
|
"gcode_flavor", "gcode_skip_config_block",
|
||||||
|
"fan_kickstart", "part_cooling_fan_min_pwm", "fan_speedup_time", "fan_speedup_overhangs",
|
||||||
"single_extruder_multi_material", "manual_filament_change", "file_start_gcode", "machine_start_gcode", "machine_end_gcode", "before_layer_change_gcode", "printing_by_object_gcode", "layer_change_gcode", "time_lapse_gcode", "wrapping_detection_gcode", "change_filament_gcode", "change_extrusion_role_gcode",
|
"single_extruder_multi_material", "manual_filament_change", "file_start_gcode", "machine_start_gcode", "machine_end_gcode", "before_layer_change_gcode", "printing_by_object_gcode", "layer_change_gcode", "time_lapse_gcode", "wrapping_detection_gcode", "change_filament_gcode", "change_extrusion_role_gcode",
|
||||||
"printer_model", "printer_variant", "printer_extruder_id", "printer_extruder_variant", "extruder_variant_list", "default_nozzle_volume_type",
|
"printer_model", "printer_variant", "printer_extruder_id", "printer_extruder_variant", "extruder_variant_list", "default_nozzle_volume_type",
|
||||||
"printable_height", "extruder_printable_height", "extruder_clearance_radius", "extruder_clearance_height_to_lid", "extruder_clearance_height_to_rod",
|
"printable_height", "extruder_printable_height", "extruder_clearance_radius", "extruder_clearance_height_to_lid", "extruder_clearance_height_to_rod",
|
||||||
|
|||||||
+308
-50
@@ -18,6 +18,9 @@
|
|||||||
#include "Thread.hpp"
|
#include "Thread.hpp"
|
||||||
#include "Time.hpp"
|
#include "Time.hpp"
|
||||||
#include "GCode.hpp"
|
#include "GCode.hpp"
|
||||||
|
#include "BeltGCode.hpp"
|
||||||
|
#include "BeltTransform.hpp"
|
||||||
|
#include "GCode/MachineFrameTransform.hpp"
|
||||||
#include "GCode/WipeTower.hpp"
|
#include "GCode/WipeTower.hpp"
|
||||||
#include "GCode/WipeTower2.hpp"
|
#include "GCode/WipeTower2.hpp"
|
||||||
#include "GCode/WipeTowerEstimate.hpp"
|
#include "GCode/WipeTowerEstimate.hpp"
|
||||||
@@ -26,6 +29,7 @@
|
|||||||
#include "MaterialType.hpp"
|
#include "MaterialType.hpp"
|
||||||
#include "Model.hpp"
|
#include "Model.hpp"
|
||||||
#include "format.hpp"
|
#include "format.hpp"
|
||||||
|
#include "LocalesUtils.hpp"
|
||||||
#include <float.h>
|
#include <float.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -111,6 +115,12 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
|||||||
// Cache the plenty of parameters, which influence the G-code generator only,
|
// Cache the plenty of parameters, which influence the G-code generator only,
|
||||||
// or they are only notes not influencing the generated G-code.
|
// or they are only notes not influencing the generated G-code.
|
||||||
static std::unordered_set<std::string> steps_gcode = {
|
static std::unordered_set<std::string> steps_gcode = {
|
||||||
|
// Belt printer G-code axis remap (only affects G-code output, not slicing).
|
||||||
|
"gcode_remap_x",
|
||||||
|
"gcode_remap_y",
|
||||||
|
"gcode_remap_z",
|
||||||
|
// Machine-frame transform (derived from belt tilt; only affects G-code output).
|
||||||
|
"belt_frame_tilt_decouple", "belt_frame_tilt_angle",
|
||||||
//BBS
|
//BBS
|
||||||
"additional_cooling_fan_speed",
|
"additional_cooling_fan_speed",
|
||||||
"reduce_crossing_wall",
|
"reduce_crossing_wall",
|
||||||
@@ -310,8 +320,26 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
|||||||
// Spiral Vase forces different kind of slicing than the normal model:
|
// Spiral Vase forces different kind of slicing than the normal model:
|
||||||
// In Spiral Vase mode, holes are closed and only the largest area contour is kept at each layer.
|
// In Spiral Vase mode, holes are closed and only the largest area contour is kept at each layer.
|
||||||
// Therefore toggling the Spiral Vase on / off requires complete reslicing.
|
// Therefore toggling the Spiral Vase on / off requires complete reslicing.
|
||||||
|| opt_key == "spiral_mode") {
|
|| opt_key == "spiral_mode"
|
||||||
|
// Build plate tilt changes slicing plane orientation.
|
||||||
|
|| opt_key == "build_plate_tilt_x"
|
||||||
|
|| opt_key == "build_plate_tilt_y"
|
||||||
|
// Belt printer transform options change the mesh geometry before slicing.
|
||||||
|
|| opt_key == "belt_printer"
|
||||||
|
|| opt_key == "belt_slice_rotation"
|
||||||
|
|| opt_key == "belt_slice_rotation_angle"
|
||||||
|
|| opt_key == "belt_slice_rotation_global"
|
||||||
|
|| opt_key == "belt_preslice_global"
|
||||||
|
|| opt_key == "preslice_remap_global"
|
||||||
|
|| opt_key == "preslice_remap_x"
|
||||||
|
|| opt_key == "preslice_remap_y"
|
||||||
|
|| opt_key == "preslice_remap_z") {
|
||||||
osteps.emplace_back(posSlice);
|
osteps.emplace_back(posSlice);
|
||||||
|
} else if (
|
||||||
|
opt_key == "belt_support_floor_offset"
|
||||||
|
|| opt_key == "belt_support_floor_mode"
|
||||||
|
|| opt_key == "belt_support_z_offset_mode") {
|
||||||
|
osteps.emplace_back(posSupportMaterial);
|
||||||
} else if (
|
} else if (
|
||||||
opt_key == "print_sequence"
|
opt_key == "print_sequence"
|
||||||
|| opt_key == "filament_type"
|
|| opt_key == "filament_type"
|
||||||
@@ -346,6 +374,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
|||||||
|| opt_key == "hot_plate_temp"
|
|| opt_key == "hot_plate_temp"
|
||||||
|| opt_key == "textured_plate_temp"
|
|| opt_key == "textured_plate_temp"
|
||||||
|| opt_key == "enable_prime_tower"
|
|| opt_key == "enable_prime_tower"
|
||||||
|
|| opt_key == "enable_belt_purge_tower"
|
||||||
|| opt_key == "enable_wrapping_detection"
|
|| opt_key == "enable_wrapping_detection"
|
||||||
|| opt_key == "prime_tower_enable_framework"
|
|| opt_key == "prime_tower_enable_framework"
|
||||||
|| opt_key == "prime_tower_width"
|
|| opt_key == "prime_tower_width"
|
||||||
@@ -377,6 +406,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
|||||||
|| opt_key == "prime_volume"
|
|| opt_key == "prime_volume"
|
||||||
|| opt_key == "flush_into_infill"
|
|| opt_key == "flush_into_infill"
|
||||||
|| opt_key == "flush_into_support"
|
|| opt_key == "flush_into_support"
|
||||||
|
|| opt_key == "belt_purge_tower_width"
|
||||||
|| opt_key == "initial_layer_infill_speed"
|
|| opt_key == "initial_layer_infill_speed"
|
||||||
|| opt_key == "travel_speed"
|
|| opt_key == "travel_speed"
|
||||||
|| opt_key == "travel_speed_z"
|
|| opt_key == "travel_speed_z"
|
||||||
@@ -608,6 +638,9 @@ std::vector<ObjectID> Print::print_object_ids() const
|
|||||||
|
|
||||||
bool Print::has_infinite_skirt() const
|
bool Print::has_infinite_skirt() const
|
||||||
{
|
{
|
||||||
|
// Belt printer: no skirt support.
|
||||||
|
if (m_config.belt_printer.value)
|
||||||
|
return false;
|
||||||
// Orca: unclear why (m_config.ooze_prevention && this->extruders().size() > 1) logic is here, removed.
|
// Orca: unclear why (m_config.ooze_prevention && this->extruders().size() > 1) logic is here, removed.
|
||||||
// return (m_config.draft_shield == dsEnabled && m_config.skirt_loops > 0) || (m_config.ooze_prevention && this->extruders().size() > 1);
|
// return (m_config.draft_shield == dsEnabled && m_config.skirt_loops > 0) || (m_config.ooze_prevention && this->extruders().size() > 1);
|
||||||
|
|
||||||
@@ -616,6 +649,9 @@ bool Print::has_infinite_skirt() const
|
|||||||
|
|
||||||
bool Print::has_skirt() const
|
bool Print::has_skirt() const
|
||||||
{
|
{
|
||||||
|
// Belt printer: no skirt support.
|
||||||
|
if (m_config.belt_printer.value)
|
||||||
|
return false;
|
||||||
return (m_config.skirt_height > 0);
|
return (m_config.skirt_height > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -624,6 +660,24 @@ bool Print::has_brim() const
|
|||||||
return std::any_of(m_objects.begin(), m_objects.end(), [](PrintObject *object) { return object->has_brim(); });
|
return std::any_of(m_objects.begin(), m_objects.end(), [](PrintObject *object) { return object->has_brim(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Print::has_tilted_belt() const
|
||||||
|
{
|
||||||
|
if (! m_config.belt_printer.value)
|
||||||
|
return false;
|
||||||
|
// A Z rotation leaves the belt floor flat (BeltTransform forces shear = 0) and no
|
||||||
|
// rotation at all means the machine is geometrically a flat bed.
|
||||||
|
const BeltRotationAxis axis = m_config.belt_slice_rotation.value;
|
||||||
|
if (axis != BeltRotationAxis::X && axis != BeltRotationAxis::Y)
|
||||||
|
return false;
|
||||||
|
const double tilt = std::abs(m_config.belt_slice_rotation_angle.value);
|
||||||
|
return tilt >= BELT_BRIM_MIN_TILT_DEG && tilt <= BELT_BRIM_MAX_TILT_DEG;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Print::has_belt_brim() const
|
||||||
|
{
|
||||||
|
return std::any_of(m_objects.begin(), m_objects.end(), [](PrintObject *object) { return object->has_belt_brim(); });
|
||||||
|
}
|
||||||
|
|
||||||
//BBS
|
//BBS
|
||||||
std::vector<size_t> Print::layers_sorted_for_object(float start, float end, std::vector<LayerPtrs> &layers_of_objects, std::vector<BoundingBox> &boundingBox_for_objects, VecOfPoints &objects_instances_shift)
|
std::vector<size_t> Print::layers_sorted_for_object(float start, float end, std::vector<LayerPtrs> &layers_of_objects, std::vector<BoundingBox> &boundingBox_for_objects, VecOfPoints &objects_instances_shift)
|
||||||
{
|
{
|
||||||
@@ -1353,6 +1407,84 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
|
|||||||
if (extruders.empty())
|
if (extruders.empty())
|
||||||
return { L("No extrusions under current settings.") };
|
return { L("No extrusions under current settings.") };
|
||||||
|
|
||||||
|
// Belt printer validation: incompatible features.
|
||||||
|
if (m_config.belt_printer.value) {
|
||||||
|
for (const PrintObject *object : m_objects) {
|
||||||
|
if (object->config().raft_layers > 0)
|
||||||
|
return { L("Raft is not compatible with belt printer mode.") };
|
||||||
|
}
|
||||||
|
if (m_config.draft_shield != dsDisabled)
|
||||||
|
return { L("Draft shield is not compatible with belt printer mode.") };
|
||||||
|
|
||||||
|
// Belt brim spans many layers and owns the layers below the object, which
|
||||||
|
// neither the prime tower nor spiral vase can share.
|
||||||
|
if (this->has_belt_brim()) {
|
||||||
|
if (m_config.enable_prime_tower.value)
|
||||||
|
return { L("Brim is not compatible with the prime tower on a belt printer. "
|
||||||
|
"Disable one of them.") };
|
||||||
|
if (m_config.spiral_mode.value)
|
||||||
|
return { L("Brim is not compatible with spiral vase mode on a belt printer. "
|
||||||
|
"Disable one of them.") };
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const PrintObject *object : m_objects) {
|
||||||
|
const PrintObjectConfig &ocfg = object->config();
|
||||||
|
// Mirror PrintObject::has_belt_brim(): an inner-only brim needs a positive
|
||||||
|
// brim_width (leading/extra widen only the outer ring), so keep this
|
||||||
|
// predicate in step or the belt-brim warnings below would fire for a brim
|
||||||
|
// that has_belt_brim() rejects.
|
||||||
|
const bool wants_brim = ocfg.brim_type != btNoBrim
|
||||||
|
&& (ocfg.brim_type == btInnerOnly
|
||||||
|
? ocfg.brim_width.value > 0.
|
||||||
|
: (ocfg.brim_width.value > 0. || ocfg.leading_brim_length.value > 0.
|
||||||
|
|| ocfg.extra_brim_width.value > 0.));
|
||||||
|
if (! wants_brim)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (! this->has_tilted_belt()) {
|
||||||
|
if (std::abs(m_config.belt_slice_rotation_angle.value) > BELT_BRIM_MAX_TILT_DEG)
|
||||||
|
warn(L("The belt is too steep for a brim, so no brim will be generated."),
|
||||||
|
"brim_width", object->model_object());
|
||||||
|
else
|
||||||
|
warn(L("A brim is only generated when the belt is tilted. Set a belt tilt angle, "
|
||||||
|
"or remove the brim setting."),
|
||||||
|
"brim_type", object->model_object());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ocfg.brim_type == btAutoBrim || ocfg.brim_type == btEar || ocfg.brim_type == btPainted)
|
||||||
|
warn(L("Belt printers support outer and inner brim only. Auto, Mouse ear and Painted "
|
||||||
|
"brim are printed as Outer brim only, using Brim width."),
|
||||||
|
"brim_type", object->model_object());
|
||||||
|
|
||||||
|
if (ocfg.leading_brim_length.value > 0. && ocfg.brim_object_gap.value > 0.)
|
||||||
|
warn(L("Brim-object gap separates the leading brim from the object's leading edge, "
|
||||||
|
"which is the edge it is meant to anchor. Set the gap to 0 when using leading "
|
||||||
|
"brim length."),
|
||||||
|
"brim_object_gap", object->model_object());
|
||||||
|
|
||||||
|
// Unconditional: this suppresses the WHOLE belt brim, not just the apron, so a
|
||||||
|
// user asking for any brim at all needs to be told they are getting none.
|
||||||
|
if (! object->belt_brim_instances_compatible())
|
||||||
|
warn(L("This object's copies are spaced along the belt, so they would each need "
|
||||||
|
"their own brim and none is generated. Print them as separate objects, or "
|
||||||
|
"arrange the copies side by side across the belt."),
|
||||||
|
"brim_type", object->model_object());
|
||||||
|
}
|
||||||
|
if (this->has_belt_brim() && m_objects.size() > 1)
|
||||||
|
warn(L("Leading brim length extends ahead of each object along the belt, and Arrange does "
|
||||||
|
"not reserve that space. Leave room between objects."),
|
||||||
|
"leading_brim_length");
|
||||||
|
} else {
|
||||||
|
// "Leading edge only" describes where a part meets a moving belt, so it has no
|
||||||
|
// meaning on a fixed bed. Brim.cpp prints it as an ordinary outer brim rather
|
||||||
|
// than silently producing nothing; say so.
|
||||||
|
for (const PrintObject *object : m_objects)
|
||||||
|
if (object->config().brim_type == btLeadingEdgeOnly)
|
||||||
|
warn(L("\"Leading edge only\" brim applies to belt printers. On this printer it is "
|
||||||
|
"printed as an ordinary outer brim."),
|
||||||
|
"brim_type", object->model_object());
|
||||||
|
}
|
||||||
|
|
||||||
// Orca: a gradient mixed filament only renders its gradient with "Mixed color sublayer" on;
|
// Orca: a gradient mixed filament only renders its gradient with "Mixed color sublayer" on;
|
||||||
// without it ToolOrdering::resolve_mixed_filaments prints one whole component per layer and
|
// without it ToolOrdering::resolve_mixed_filaments prints one whole component per layer and
|
||||||
// the gradient is dropped silently. extruders() already covers painting, height ranges,
|
// the gradient is dropped silently. extruders() already covers painting, height ranges,
|
||||||
@@ -1408,6 +1540,25 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
|
|||||||
add_warning(layer_warning);
|
add_warning(layer_warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_config.belt_printer.value && m_config.enable_belt_purge_tower.value
|
||||||
|
&& m_config.print_sequence == PrintSequence::ByObject
|
||||||
|
&& extruders.size() > 1) {
|
||||||
|
StringObjectException warningtemp;
|
||||||
|
warningtemp.string = L("The belt purge tower is not generated in \"By object\" print sequence; "
|
||||||
|
"filament changes will not be purged.");
|
||||||
|
warningtemp.opt_key = "enable_belt_purge_tower";
|
||||||
|
warningtemp.is_warning = true;
|
||||||
|
add_warning(warningtemp);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_config.belt_printer.value && m_config.enable_belt_purge_tower.value) {
|
||||||
|
const size_t prism_count = std::count_if(m_objects.begin(), m_objects.end(), [](const PrintObject *object) {
|
||||||
|
return object->config().belt_purge_tower_object.value;
|
||||||
|
});
|
||||||
|
if (prism_count > 1)
|
||||||
|
return {L("The project contains multiple managed belt purge towers. Reload the plate or toggle the belt purge tower off and on to regenerate it.")};
|
||||||
|
}
|
||||||
|
|
||||||
if (m_config.enable_prime_tower) {
|
if (m_config.enable_prime_tower) {
|
||||||
for (const PrintObject* object : m_objects) {
|
for (const PrintObject* object : m_objects) {
|
||||||
if (object->config().precise_z_height.value) {
|
if (object->config().precise_z_height.value) {
|
||||||
@@ -1461,35 +1612,65 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
|
|||||||
return profile;
|
return profile;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Checks that the print does not exceed the max print height
|
// Checks that the print does not exceed the max print height.
|
||||||
for (size_t print_object_idx = 0; print_object_idx < m_objects.size(); ++ print_object_idx) {
|
// For belt printers the slicing-frame Z spans the sheared X-length and
|
||||||
|
// is not comparable to printable_height (which is gantry clearance in the
|
||||||
|
// build-volume frame). Compare against the model's pre-shear Z instead,
|
||||||
|
// mirroring the bbox computed in PrintObject::update_slicing_parameters.
|
||||||
|
// When the post-gcode MachineFrameTransform is active the printer's
|
||||||
|
// physical Z mapping is non-trivial — skip the check entirely.
|
||||||
|
const bool belt_printer = this->config().belt_printer.value;
|
||||||
|
bool skip_max_height_check = false;
|
||||||
|
if (belt_printer) {
|
||||||
|
MachineFrameTransform machine_frame;
|
||||||
|
machine_frame.init_from_config(this->config());
|
||||||
|
skip_max_height_check = machine_frame.is_active();
|
||||||
|
}
|
||||||
|
const double shrinkage_compensation_z = this->shrinkage_compensation().z();
|
||||||
|
for (size_t print_object_idx = 0; !skip_max_height_check && print_object_idx < m_objects.size(); ++ print_object_idx) {
|
||||||
const PrintObject &print_object = *m_objects[print_object_idx];
|
const PrintObject &print_object = *m_objects[print_object_idx];
|
||||||
//FIXME It is quite expensive to generate object layers just to get the print height!
|
|
||||||
if (auto layers = generate_object_layers(print_object.slicing_parameters(), layer_height_profile(print_object_idx), print_object.config().precise_z_height.value);
|
|
||||||
!layers.empty()) {
|
|
||||||
|
|
||||||
Vec3d test =this->shrinkage_compensation();
|
double effective_max_z = 0;
|
||||||
const double shrinkage_compensation_z = this->shrinkage_compensation().z();
|
bool last_layer_below_max = false;
|
||||||
|
bool have_height = false;
|
||||||
if (shrinkage_compensation_z != 1. && layers.back() > (this->config().printable_height / shrinkage_compensation_z + EPSILON)) {
|
|
||||||
// The object exceeds the maximum build volume height because of shrinkage compensation.
|
if (belt_printer) {
|
||||||
return StringObjectException{
|
double raw_z = print_object.model_object()->max_z();
|
||||||
Slic3r::format(_u8L("While the object %1% itself fits the build volume, it exceeds the maximum build volume height because of material shrinkage compensation."), print_object.model_object()->name),
|
if (BeltTransformPipeline::has_preslice_remap(this->config()))
|
||||||
print_object.model_object(),
|
raw_z = BeltTransformPipeline::remap_bbox(*print_object.model_object(), this->config()).size().z();
|
||||||
""
|
effective_max_z = raw_z;
|
||||||
};
|
have_height = raw_z > 0;
|
||||||
} else if (layers.back() > this->config().printable_height + EPSILON) {
|
} else {
|
||||||
// Test whether the last slicing plane is below or above the print volume.
|
//FIXME It is quite expensive to generate object layers just to get the print height!
|
||||||
return StringObjectException{
|
auto layers = generate_object_layers(print_object.slicing_parameters(), layer_height_profile(print_object_idx), print_object.config().precise_z_height.value);
|
||||||
0.5 * (layers[layers.size() - 2] + layers.back()) > this->config().printable_height + EPSILON ?
|
if (!layers.empty()) {
|
||||||
Slic3r::format(_u8L("The object %1% exceeds the maximum build volume height."), print_object.model_object()->name) :
|
effective_max_z = layers.back();
|
||||||
Slic3r::format(_u8L("While the object %1% itself fits the build volume, its last layer exceeds the maximum build volume height."), print_object.model_object()->name) +
|
last_layer_below_max = layers.size() >= 2 &&
|
||||||
" " + _u8L("You might want to reduce the size of your model or change current print settings and retry."),
|
0.5 * (layers[layers.size() - 2] + layers.back()) <= this->config().printable_height + EPSILON;
|
||||||
print_object.model_object(),
|
have_height = true;
|
||||||
""
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!have_height)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (shrinkage_compensation_z != 1. && effective_max_z > (this->config().printable_height / shrinkage_compensation_z + EPSILON)) {
|
||||||
|
// The object exceeds the maximum build volume height because of shrinkage compensation.
|
||||||
|
return StringObjectException{
|
||||||
|
Slic3r::format(_u8L("While the object %1% itself fits the build volume, it exceeds the maximum build volume height because of material shrinkage compensation."), print_object.model_object()->name),
|
||||||
|
print_object.model_object(),
|
||||||
|
""
|
||||||
|
};
|
||||||
|
} else if (effective_max_z > this->config().printable_height + EPSILON) {
|
||||||
|
return StringObjectException{
|
||||||
|
last_layer_below_max ?
|
||||||
|
Slic3r::format(_u8L("While the object %1% itself fits the build volume, its last layer exceeds the maximum build volume height."), print_object.model_object()->name) +
|
||||||
|
" " + _u8L("You might want to reduce the size of your model or change current print settings and retry.") :
|
||||||
|
Slic3r::format(_u8L("The object %1% exceeds the maximum build volume height."), print_object.model_object()->name),
|
||||||
|
print_object.model_object(),
|
||||||
|
""
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some of the objects has variable layer height applied by painting or by a table.
|
// Some of the objects has variable layer height applied by painting or by a table.
|
||||||
@@ -1509,12 +1690,12 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
|
|||||||
return {_u8L("Variable layer height is not supported with Organic supports.") };
|
return {_u8L("Variable layer height is not supported with Organic supports.") };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->has_wipe_tower() && ! m_objects.empty()) {
|
if ((this->has_wipe_tower() || this->has_belt_purge_tower()) && ! m_objects.empty()) {
|
||||||
// Orca: wipe_tower_filament (issue #10971) is inserted into the tool order after
|
// Orca: wipe_tower_filament (issue #10971) is inserted into the tool order after
|
||||||
// resolve_mixed_filaments has expanded every mixed (virtual) slot, so a mixed slot here
|
// resolve_mixed_filaments has expanded every mixed (virtual) slot, so a mixed slot here
|
||||||
// would reach the G-code as a tool change to a slot no nozzle carries. The GUI hides
|
// would reach the G-code as a tool change to a slot no nozzle carries. The GUI hides
|
||||||
// mixed slots from the option; this guards loaded projects and the CLI.
|
// mixed slots from the option; this guards loaded projects and the CLI.
|
||||||
if (m_config.wipe_tower_filament > 0) {
|
if (this->has_wipe_tower() && m_config.wipe_tower_filament > 0) {
|
||||||
const auto &is_mixed = m_config.filament_is_mixed.values;
|
const auto &is_mixed = m_config.filament_is_mixed.values;
|
||||||
const size_t wipe_idx = size_t(m_config.wipe_tower_filament - 1);
|
const size_t wipe_idx = size_t(m_config.wipe_tower_filament - 1);
|
||||||
if (wipe_idx < is_mixed.size() && is_mixed[wipe_idx])
|
if (wipe_idx < is_mixed.size() && is_mixed[wipe_idx])
|
||||||
@@ -1536,12 +1717,17 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! m_config.use_relative_e_distances)
|
// The following two constraints come from the classic wipe tower G-code
|
||||||
return { L("The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1).") };
|
// generator; purging into the belt purge prism uses normal object
|
||||||
|
// extrusions and does not need them.
|
||||||
|
if (this->has_wipe_tower()) {
|
||||||
|
if (! m_config.use_relative_e_distances)
|
||||||
|
return { L("The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1).") };
|
||||||
|
|
||||||
|
if (m_config.ooze_prevention && m_config.single_extruder_multi_material)
|
||||||
|
return {L("Ooze prevention is only supported with the wipe tower when 'single_extruder_multi_material' is off.")};
|
||||||
|
}
|
||||||
|
|
||||||
if (m_config.ooze_prevention && m_config.single_extruder_multi_material)
|
|
||||||
return {L("Ooze prevention is only supported with the wipe tower when 'single_extruder_multi_material' is off.")};
|
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
if (m_config.gcode_flavor != gcfRepRapSprinter && m_config.gcode_flavor != gcfRepRapFirmware &&
|
if (m_config.gcode_flavor != gcfRepRapSprinter && m_config.gcode_flavor != gcfRepRapFirmware &&
|
||||||
m_config.gcode_flavor != gcfRepetier && m_config.gcode_flavor != gcfMarlinLegacy && m_config.gcode_flavor != gcfMarlinFirmware)
|
m_config.gcode_flavor != gcfRepetier && m_config.gcode_flavor != gcfMarlinLegacy && m_config.gcode_flavor != gcfMarlinFirmware)
|
||||||
@@ -2261,8 +2447,28 @@ BoundingBox PrintObject::get_first_layer_bbox(float& a, float& layer_height, std
|
|||||||
a += area(slice);
|
a += area(slice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (has_brim())
|
// Guard on `defined`: make_brim() can return before assigning this (it does on
|
||||||
|
// belt printers, where has_brim() is still true but the plate brim is skipped),
|
||||||
|
// and overwriting a valid bbox with an undefined one corrupted the first-layer
|
||||||
|
// centre and the GUI's first-layer area readout.
|
||||||
|
if (has_brim() && firstLayerObjectBrimBoundingBox.defined)
|
||||||
bbox = firstLayerObjectBrimBoundingBox;
|
bbox = firstLayerObjectBrimBoundingBox;
|
||||||
|
// Belt brim: the apron reaches ahead of the object along the belt.
|
||||||
|
if (has_belt_brim()) {
|
||||||
|
const Point shift = instances().empty() ? Point(0, 0) : instances()[0].shift_without_plate_offset();
|
||||||
|
for (const ExPolygons &areas : m_belt_brim_areas_by_layer)
|
||||||
|
for (const ExPolygon &ex : areas) {
|
||||||
|
BoundingBox bb = get_extents(ex.contour);
|
||||||
|
bb.translate(shift.x(), shift.y());
|
||||||
|
bbox.merge(bb);
|
||||||
|
}
|
||||||
|
for (const BeltBrimBand &band : m_belt_brim_prologue)
|
||||||
|
for (const ExPolygon &ex : band.areas) {
|
||||||
|
BoundingBox bb = get_extents(ex.contour);
|
||||||
|
bb.translate(shift.x(), shift.y());
|
||||||
|
bbox.merge(bb);
|
||||||
|
}
|
||||||
|
}
|
||||||
return bbox;
|
return bbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2361,15 +2567,24 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
|||||||
int object_count = m_objects.size();
|
int object_count = m_objects.size();
|
||||||
std::set<PrintObject*> need_slicing_objects;
|
std::set<PrintObject*> need_slicing_objects;
|
||||||
std::set<PrintObject*> re_slicing_objects;
|
std::set<PrintObject*> re_slicing_objects;
|
||||||
|
// Belt global modes couple each object's bed position into its layer Z values,
|
||||||
|
// so sharing layers between "identical" objects is wrong.
|
||||||
|
bool belt_no_share = m_config.belt_printer.value &&
|
||||||
|
((m_config.belt_slice_rotation_global.value
|
||||||
|
&& m_config.belt_slice_rotation.value != BeltRotationAxis::None)
|
||||||
|
|| m_config.preslice_remap_global.value
|
||||||
|
|| m_config.belt_preslice_global.value);
|
||||||
if (!use_cache) {
|
if (!use_cache) {
|
||||||
for (int index = 0; index < object_count; index++)
|
for (int index = 0; index < object_count; index++)
|
||||||
{
|
{
|
||||||
PrintObject *obj = m_objects[index];
|
PrintObject *obj = m_objects[index];
|
||||||
for (PrintObject *slicing_obj : need_slicing_objects)
|
if (!belt_no_share) {
|
||||||
{
|
for (PrintObject *slicing_obj : need_slicing_objects)
|
||||||
if (is_print_object_the_same(obj, slicing_obj)) {
|
{
|
||||||
obj->set_shared_object(slicing_obj);
|
if (is_print_object_the_same(obj, slicing_obj)) {
|
||||||
break;
|
obj->set_shared_object(slicing_obj);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!obj->get_shared_object())
|
if (!obj->get_shared_object())
|
||||||
@@ -2388,12 +2603,14 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
|||||||
PrintObject *obj = m_objects[index];
|
PrintObject *obj = m_objects[index];
|
||||||
bool found_shared = false;
|
bool found_shared = false;
|
||||||
if (need_slicing_objects.find(obj) == need_slicing_objects.end()) {
|
if (need_slicing_objects.find(obj) == need_slicing_objects.end()) {
|
||||||
for (PrintObject *slicing_obj : need_slicing_objects)
|
if (!belt_no_share) {
|
||||||
{
|
for (PrintObject *slicing_obj : need_slicing_objects)
|
||||||
if (is_print_object_the_same(obj, slicing_obj)) {
|
{
|
||||||
obj->set_shared_object(slicing_obj);
|
if (is_print_object_the_same(obj, slicing_obj)) {
|
||||||
found_shared = true;
|
obj->set_shared_object(slicing_obj);
|
||||||
break;
|
found_shared = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!found_shared) {
|
if (!found_shared) {
|
||||||
@@ -2601,7 +2818,10 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
|||||||
|
|
||||||
m_wipe_tower_data.clear();
|
m_wipe_tower_data.clear();
|
||||||
m_tool_ordering.clear();
|
m_tool_ordering.clear();
|
||||||
if (this->has_wipe_tower()) {
|
if (this->has_belt_purge_tower() && this->config().print_sequence != PrintSequence::ByObject) {
|
||||||
|
this->_plan_belt_purge();
|
||||||
|
}
|
||||||
|
else if (this->has_wipe_tower()) {
|
||||||
this->_make_wipe_tower();
|
this->_make_wipe_tower();
|
||||||
}
|
}
|
||||||
else if (this->config().print_sequence != PrintSequence::ByObject) {
|
else if (this->config().print_sequence != PrintSequence::ByObject) {
|
||||||
@@ -2849,6 +3069,26 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Belt brim: bound the first-layer convex hull by the lowest apron band, so
|
||||||
|
// bed levelling and the initial purge line account for brim that reaches
|
||||||
|
// ahead of every object.
|
||||||
|
if (this->has_belt_brim()) {
|
||||||
|
for (PrintObject *object : m_objects) {
|
||||||
|
if (! object->has_belt_brim() || object->belt_brim_prologue().empty())
|
||||||
|
continue;
|
||||||
|
const BeltBrimBand &lowest = object->belt_brim_prologue().front();
|
||||||
|
for (const PrintInstance &instance : object->instances())
|
||||||
|
for (const ExPolygon &ex : lowest.areas) {
|
||||||
|
Polygon poly = ex.contour;
|
||||||
|
poly.translate(instance.shift);
|
||||||
|
append(m_first_layer_convex_hull.points, std::move(poly.points));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unchanged for belt printers: _make_skirt() already returns early for them, and
|
||||||
|
// the belt brim does not populate m_brimMapByInstance, which is what the
|
||||||
|
// skirt/brim grouping reads.
|
||||||
if (has_skirt() || has_infinite_skirt() || has_brim()) {
|
if (has_skirt() || has_infinite_skirt() || has_brim()) {
|
||||||
// Generate skirt/brim groups after brim so per-object and draft-shield footprints
|
// Generate skirt/brim groups after brim so per-object and draft-shield footprints
|
||||||
// include brims when grouping and offsetting skirt loops.
|
// include brims when grouping and offsetting skirt loops.
|
||||||
@@ -2943,12 +3183,17 @@ std::string Print::export_gcode(const std::string& path_template, GCodeProcessor
|
|||||||
this->set_status(80, message);
|
this->set_status(80, message);
|
||||||
|
|
||||||
// The following line may die for multiple reasons.
|
// The following line may die for multiple reasons.
|
||||||
GCode gcode;
|
// Factory: use BeltGCode for belt printers, plain GCode otherwise.
|
||||||
|
std::unique_ptr<GCode> gcode;
|
||||||
|
if (m_config.belt_printer.value)
|
||||||
|
gcode = std::make_unique<BeltGCode>();
|
||||||
|
else
|
||||||
|
gcode = std::make_unique<GCode>();
|
||||||
//BBS: compute plate offset for gcode-generator
|
//BBS: compute plate offset for gcode-generator
|
||||||
const Vec3d origin = this->get_plate_origin();
|
const Vec3d origin = this->get_plate_origin();
|
||||||
gcode.set_gcode_offset(origin(0), origin(1));
|
gcode->set_gcode_offset(origin(0), origin(1));
|
||||||
gcode.do_export(this, path.c_str(), result, thumbnail_cb);
|
gcode->do_export(this, path.c_str(), result, thumbnail_cb);
|
||||||
gcode.export_layer_filaments(result);
|
gcode->export_layer_filaments(result);
|
||||||
//BBS
|
//BBS
|
||||||
if (result != nullptr) {
|
if (result != nullptr) {
|
||||||
result->conflict_result = m_conflict_result;
|
result->conflict_result = m_conflict_result;
|
||||||
@@ -2963,6 +3208,10 @@ std::string Print::export_gcode(const std::string& path_template, GCodeProcessor
|
|||||||
|
|
||||||
void Print::_make_skirt()
|
void Print::_make_skirt()
|
||||||
{
|
{
|
||||||
|
// Belt printer: skirt is not compatible.
|
||||||
|
if (m_config.belt_printer.value)
|
||||||
|
return;
|
||||||
|
|
||||||
const bool generate_skirt = this->has_skirt() || this->has_infinite_skirt();
|
const bool generate_skirt = this->has_skirt() || this->has_infinite_skirt();
|
||||||
|
|
||||||
// First off we need to decide how tall the skirt must be.
|
// First off we need to decide how tall the skirt must be.
|
||||||
@@ -4003,6 +4252,13 @@ int Print::get_config_index(int filament_id, int layer_id, const std::vector<std
|
|||||||
// Wipe tower support.
|
// Wipe tower support.
|
||||||
bool Print::has_wipe_tower() const
|
bool Print::has_wipe_tower() const
|
||||||
{
|
{
|
||||||
|
// Belt printers never get the classic wipe tower: its G-code is generated
|
||||||
|
// directly in machine XY coordinates and bypasses the belt rotation
|
||||||
|
// transform. Purging is routed into the belt purge prism instead
|
||||||
|
// (see has_belt_purge_tower() / _plan_belt_purge()).
|
||||||
|
if (m_config.belt_printer.value)
|
||||||
|
return false;
|
||||||
|
|
||||||
if (m_config.enable_prime_tower.value == true) {
|
if (m_config.enable_prime_tower.value == true) {
|
||||||
if (m_config.enable_wrapping_detection.value && m_config.wrapping_exclude_area.values.size() > 2)
|
if (m_config.enable_wrapping_detection.value && m_config.wrapping_exclude_area.values.size() > 2)
|
||||||
return true;
|
return true;
|
||||||
@@ -4015,6 +4271,7 @@ bool Print::has_wipe_tower() const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const WipeTowerData &Print::wipe_tower_data(size_t filaments_cnt) const
|
const WipeTowerData &Print::wipe_tower_data(size_t filaments_cnt) const
|
||||||
{
|
{
|
||||||
// Until the tower is generated, size it with the estimate the GUI/CLI placement uses, so
|
// Until the tower is generated, size it with the estimate the GUI/CLI placement uses, so
|
||||||
@@ -4044,6 +4301,7 @@ bool Print::enable_timelapse_print() const
|
|||||||
return m_config.timelapse_type.value == TimelapseType::tlSmooth;
|
return m_config.timelapse_type.value == TimelapseType::tlSmooth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Print::_make_wipe_tower()
|
void Print::_make_wipe_tower()
|
||||||
{
|
{
|
||||||
m_wipe_tower_data.clear();
|
m_wipe_tower_data.clear();
|
||||||
|
|||||||
+89
-1
@@ -17,6 +17,8 @@
|
|||||||
#include "GCode/ThumbnailData.hpp"
|
#include "GCode/ThumbnailData.hpp"
|
||||||
#include "GCode/GCodeProcessor.hpp"
|
#include "GCode/GCodeProcessor.hpp"
|
||||||
#include "MultiMaterialSegmentation.hpp"
|
#include "MultiMaterialSegmentation.hpp"
|
||||||
|
#include "BeltBrim.hpp"
|
||||||
|
#include "BeltTransform.hpp"
|
||||||
#include "ObjectID.hpp"
|
#include "ObjectID.hpp"
|
||||||
#include "libslic3r.h"
|
#include "libslic3r.h"
|
||||||
|
|
||||||
@@ -205,6 +207,13 @@ class ConstSupportLayerPtrsAdaptor : public ConstVectorOfPtrsAdaptor<SupportLaye
|
|||||||
ConstSupportLayerPtrsAdaptor(const SupportLayerPtrs *data) : ConstVectorOfPtrsAdaptor<SupportLayer>(data) {}
|
ConstSupportLayerPtrsAdaptor(const SupportLayerPtrs *data) : ConstVectorOfPtrsAdaptor<SupportLayer>(data) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Returns the model's raw bounding box with pre-slice axis remap applied.
|
||||||
|
// When no remap is active, returns the unmodified raw_bounding_box().
|
||||||
|
inline BoundingBoxf3 belt_remapped_bbox(const ModelObject &model_object, const PrintConfig &config)
|
||||||
|
{
|
||||||
|
return BeltTransformPipeline::remap_bbox(model_object, config);
|
||||||
|
}
|
||||||
|
|
||||||
// Single instance of a PrintObject.
|
// Single instance of a PrintObject.
|
||||||
// As multiple PrintObjects may be generated for a single ModelObject (their instances differ in rotation around Z),
|
// As multiple PrintObjects may be generated for a single ModelObject (their instances differ in rotation around Z),
|
||||||
// ModelObject's instancess will be distributed among these multiple PrintObjects.
|
// ModelObject's instancess will be distributed among these multiple PrintObjects.
|
||||||
@@ -389,6 +398,30 @@ public:
|
|||||||
&& ! this->has_raft();
|
&& ! this->has_raft();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Belt brim. A tilted belt needs its brim laid onto the belt plane over many
|
||||||
|
// layers instead of as one flat first-layer ring, so it is generated by
|
||||||
|
// BeltBrim.cpp and stored per layer here. Deliberately separate from
|
||||||
|
// has_brim(): that predicate feeds PrintRegion extruder collection, support
|
||||||
|
// trimming and the spiral vase probe, and widening it would perturb belt
|
||||||
|
// support output.
|
||||||
|
bool has_belt_brim() const;
|
||||||
|
// Brim filament for this object's belt brim, returned in the 1-based domain of
|
||||||
|
// PrintRegion::outer_wall_filament_id and the raw values pushed into
|
||||||
|
// LayerTools::extruders in ToolOrdering::collect_extruders (ToolOrdering reindexes
|
||||||
|
// the whole list to 0-based afterwards). Lowest positive outer_wall_filament_id
|
||||||
|
// over the printing regions, 1 if none is explicitly set.
|
||||||
|
unsigned int belt_brim_filament() const;
|
||||||
|
// False when this object's instances sit at different points ALONG the belt, which
|
||||||
|
// would need a separate set of bands each. Public so validate() can explain it.
|
||||||
|
bool belt_brim_instances_compatible() const;
|
||||||
|
const std::vector<ExtrusionEntityCollection>& belt_brim_by_layer() const { return m_belt_brim_by_layer; }
|
||||||
|
const std::vector<ExPolygons>& belt_brim_areas_by_layer() const { return m_belt_brim_areas_by_layer; }
|
||||||
|
const std::vector<BeltBrimBand>& belt_brim_prologue() const { return m_belt_brim_prologue; }
|
||||||
|
void clear_belt_brim();
|
||||||
|
void set_belt_brim(std::vector<ExtrusionEntityCollection> &&by_layer,
|
||||||
|
std::vector<ExPolygons> &&areas,
|
||||||
|
std::vector<BeltBrimBand> &&prologue);
|
||||||
|
|
||||||
// BBS
|
// BBS
|
||||||
const ExtrusionEntityCollection& object_skirt() const {
|
const ExtrusionEntityCollection& object_skirt() const {
|
||||||
return m_skirt;
|
return m_skirt;
|
||||||
@@ -538,6 +571,17 @@ private:
|
|||||||
std::vector<std::set<int>> detect_extruder_geometric_unprintables() const;
|
std::vector<std::set<int>> detect_extruder_geometric_unprintables() const;
|
||||||
|
|
||||||
void slice_volumes();
|
void slice_volumes();
|
||||||
|
// Belt mode: shift the sliced layer grid (layer print_z and the belt floor
|
||||||
|
// clipping shift) by delta, used by Print::_align_belt_purge_layers() to
|
||||||
|
// snap the purge prism's layers onto the printed objects' layer grid.
|
||||||
|
void belt_shift_layer_grid(double delta);
|
||||||
|
// Belt mode: remove layers above z (cancel the purge prism past the last
|
||||||
|
// toolchange). Returns how many layers were dropped.
|
||||||
|
size_t belt_truncate_layers_above(coordf_t z);
|
||||||
|
// Restore layers removed by belt_truncate_layers_above() before replanning.
|
||||||
|
// Wipe-tower-only invalidations do not necessarily reslice the object, so
|
||||||
|
// truncation must be reversible when later toolchanges move upward.
|
||||||
|
void belt_restore_truncated_layers();
|
||||||
//BBS
|
//BBS
|
||||||
ExPolygons _shrink_contour_holes(double contour_delta, double hole_delta, const ExPolygons& polys) const;
|
ExPolygons _shrink_contour_holes(double contour_delta, double hole_delta, const ExPolygons& polys) const;
|
||||||
// BBS
|
// BBS
|
||||||
@@ -580,7 +624,14 @@ private:
|
|||||||
|
|
||||||
SlicingParameters m_slicing_params;
|
SlicingParameters m_slicing_params;
|
||||||
LayerPtrs m_layers;
|
LayerPtrs m_layers;
|
||||||
|
LayerPtrs m_belt_truncated_layers;
|
||||||
SupportLayerPtrs m_support_layers;
|
SupportLayerPtrs m_support_layers;
|
||||||
|
// Belt brim, generated in posSupportMaterial by BeltBrim.cpp. Object-local
|
||||||
|
// slicing frame, one entry per object layer plus a prologue of brim-only
|
||||||
|
// bands that print below the object's first layer.
|
||||||
|
std::vector<ExtrusionEntityCollection> m_belt_brim_by_layer;
|
||||||
|
std::vector<ExPolygons> m_belt_brim_areas_by_layer;
|
||||||
|
std::vector<BeltBrimBand> m_belt_brim_prologue;
|
||||||
// BBS
|
// BBS
|
||||||
std::shared_ptr<TreeSupportData> m_tree_support_preview_cache;
|
std::shared_ptr<TreeSupportData> m_tree_support_preview_cache;
|
||||||
|
|
||||||
@@ -599,7 +650,27 @@ private:
|
|||||||
|
|
||||||
PrintObject* m_shared_object{ nullptr };
|
PrintObject* m_shared_object{ nullptr };
|
||||||
|
|
||||||
|
// Belt printer: global Z offset applied to this object's layers for shear positioning.
|
||||||
|
double m_belt_global_z_offset { 0.0 };
|
||||||
|
// Belt printer: min_z of mesh after belt shear (before Z-shift), for z_offset calc.
|
||||||
|
double m_belt_min_z { 0.0 };
|
||||||
|
// Belt printer: XY correction from global pre-slice mode, applied to G-code origin.
|
||||||
|
Vec2d m_belt_global_xy_correction { Vec2d::Zero() };
|
||||||
|
// Belt printer: exact belt_floor_z_shift computed during posSlice from a
|
||||||
|
// vertex-level scan of the post-transform mesh. Cached separately from
|
||||||
|
// m_slicing_params so that rebuilding m_slicing_params on a non-belt-affecting
|
||||||
|
// invalidation (e.g. support config change) doesn't replace the exact value
|
||||||
|
// with the bbox approximation seeded by create_from_config(). Cleared when
|
||||||
|
// posSlice is invalidated.
|
||||||
|
double m_belt_floor_z_shift_cached { 0.0 };
|
||||||
|
bool m_belt_floor_z_shift_cache_valid { false };
|
||||||
|
public:
|
||||||
|
double belt_global_z_offset() const { return m_belt_global_z_offset; }
|
||||||
|
double belt_min_z() const { return m_belt_min_z; }
|
||||||
|
Vec2d belt_global_xy_correction() const { return m_belt_global_xy_correction; }
|
||||||
|
private:
|
||||||
|
|
||||||
|
|
||||||
// SoftFever
|
// SoftFever
|
||||||
//
|
//
|
||||||
// object id
|
// object id
|
||||||
@@ -960,6 +1031,15 @@ public:
|
|||||||
bool has_infinite_skirt() const;
|
bool has_infinite_skirt() const;
|
||||||
bool has_skirt() const;
|
bool has_skirt() const;
|
||||||
bool has_brim() const;
|
bool has_brim() const;
|
||||||
|
// True when the belt is tilted enough that the BELT plane, not the bed plane, is
|
||||||
|
// the adhesion surface. The flat plate brim is geometrically meaningless then
|
||||||
|
// and must not run, whatever the per-object brim settings say - in particular
|
||||||
|
// brim_type "Auto", which has_brim() reports as enabled even at width 0.
|
||||||
|
bool has_tilted_belt() const;
|
||||||
|
// True when at least one object actually gets a tilted-belt brim generated.
|
||||||
|
// Implies has_tilted_belt(), but additionally requires the object's own brim
|
||||||
|
// settings to ask for one.
|
||||||
|
bool has_belt_brim() const;
|
||||||
//BBS
|
//BBS
|
||||||
bool has_auto_brim() const {
|
bool has_auto_brim() const {
|
||||||
return std::any_of(m_objects.begin(), m_objects.end(), [](PrintObject* object) { return object->config().brim_type == btAutoBrim; });
|
return std::any_of(m_objects.begin(), m_objects.end(), [](PrintObject* object) { return object->config().brim_type == btAutoBrim; });
|
||||||
@@ -1034,6 +1114,8 @@ public:
|
|||||||
|
|
||||||
// Wipe tower support.
|
// Wipe tower support.
|
||||||
bool has_wipe_tower() const;
|
bool has_wipe_tower() const;
|
||||||
|
// Belt purge prism (belt-mode replacement for the wipe tower).
|
||||||
|
bool has_belt_purge_tower() const;
|
||||||
const WipeTowerData& wipe_tower_data(size_t filaments_cnt = 0) const;
|
const WipeTowerData& wipe_tower_data(size_t filaments_cnt = 0) const;
|
||||||
const ToolOrdering& tool_ordering() const { return m_tool_ordering; }
|
const ToolOrdering& tool_ordering() const { return m_tool_ordering; }
|
||||||
|
|
||||||
@@ -1283,6 +1365,12 @@ private:
|
|||||||
|
|
||||||
void _make_skirt();
|
void _make_skirt();
|
||||||
void _make_wipe_tower();
|
void _make_wipe_tower();
|
||||||
|
// Belt mode: route filament-change purging into the belt purge prism
|
||||||
|
// (flush-into-objects) without generating a classic wipe tower.
|
||||||
|
void _plan_belt_purge();
|
||||||
|
// Belt mode: shift the purge prism's layer grid to match the printed
|
||||||
|
// objects' grid so toolchange layers find prism layers to purge into.
|
||||||
|
void _align_belt_purge_layers();
|
||||||
void finalize_first_layer_convex_hull();
|
void finalize_first_layer_convex_hull();
|
||||||
void update_filament_self_index_cache();
|
void update_filament_self_index_cache();
|
||||||
// Deduplicates, per filament, the (extruder type x volume type) variants the grouping
|
// Deduplicates, per filament, the (extruder type x volume type) variants the grouping
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "ClipperUtils.hpp"
|
#include "ClipperUtils.hpp"
|
||||||
#include "Model.hpp"
|
#include "Model.hpp"
|
||||||
#include "Print.hpp"
|
#include "Print.hpp"
|
||||||
|
#include "BeltTransform.hpp"
|
||||||
#include "FilamentMixer.hpp"
|
#include "FilamentMixer.hpp"
|
||||||
|
|
||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
@@ -136,22 +137,29 @@ struct PrintObjectTrafoAndInstances
|
|||||||
|
|
||||||
// Generate a list of trafos and XY offsets for instances of a ModelObject
|
// Generate a list of trafos and XY offsets for instances of a ModelObject
|
||||||
// Orca: Updated to include XYZ filament shrinkage compensation
|
// Orca: Updated to include XYZ filament shrinkage compensation
|
||||||
static std::vector<PrintObjectTrafoAndInstances> print_objects_from_model_object(const ModelObject &model_object, const Vec3d &shrinkage_compensation)
|
static std::vector<PrintObjectTrafoAndInstances> print_objects_from_model_object(const ModelObject &model_object, const Vec3d &shrinkage_compensation, bool force_separate_instances = false)
|
||||||
{
|
{
|
||||||
std::set<PrintObjectTrafoAndInstances> trafos;
|
std::set<PrintObjectTrafoAndInstances> trafos;
|
||||||
PrintObjectTrafoAndInstances trafo;
|
PrintObjectTrafoAndInstances trafo;
|
||||||
//BBS: add useful logs for debug
|
//BBS: add useful logs for debug
|
||||||
int index = 0;
|
int index = 0;
|
||||||
|
int unique_counter = 0;
|
||||||
for (ModelInstance *model_instance : model_object.instances) {
|
for (ModelInstance *model_instance : model_object.instances) {
|
||||||
if (model_instance->is_printable()) {
|
if (model_instance->is_printable()) {
|
||||||
// Orca: Updated with XYZ filament shrinkage compensation
|
// Orca: Updated with XYZ filament shrinkage compensation
|
||||||
Geometry::Transformation model_instance_transformation = model_instance->get_transformation();
|
Geometry::Transformation model_instance_transformation = model_instance->get_transformation();
|
||||||
trafo.trafo = model_instance_transformation.get_matrix_with_applied_shrinkage_compensation(shrinkage_compensation);
|
trafo.trafo = model_instance_transformation.get_matrix_with_applied_shrinkage_compensation(shrinkage_compensation);
|
||||||
|
|
||||||
auto shift = Point::new_scale(trafo.trafo.data()[12], trafo.trafo.data()[13]);
|
auto shift = Point::new_scale(trafo.trafo.data()[12], trafo.trafo.data()[13]);
|
||||||
// Reset the XY axes of the transformation.
|
// Reset the XY axes of the transformation.
|
||||||
trafo.trafo.data()[12] = 0;
|
trafo.trafo.data()[12] = 0;
|
||||||
trafo.trafo.data()[13] = 0;
|
trafo.trafo.data()[13] = 0;
|
||||||
|
// Belt printer global mode: prevent instance grouping so each
|
||||||
|
// copy gets its own PrintObject with independent layer Z values.
|
||||||
|
// Add a tiny unique perturbation to the existing Z (don't replace
|
||||||
|
// it — the Z translation from ensure_on_bed must be preserved).
|
||||||
|
if (force_separate_instances)
|
||||||
|
trafo.trafo.data()[14] += 1e-10 * (++unique_counter);
|
||||||
// Search or insert a trafo.
|
// Search or insert a trafo.
|
||||||
auto it = trafos.emplace(trafo).first;
|
auto it = trafos.emplace(trafo).first;
|
||||||
const_cast<PrintObjectTrafoAndInstances&>(*it).instances.emplace_back(PrintInstance{ nullptr, model_instance, shift });
|
const_cast<PrintObjectTrafoAndInstances&>(*it).instances.emplace_back(PrintInstance{ nullptr, model_instance, shift });
|
||||||
@@ -1748,11 +1756,20 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
|||||||
PrintObjectPtrs print_objects_new;
|
PrintObjectPtrs print_objects_new;
|
||||||
print_objects_new.reserve(std::max(m_objects.size(), m_model.objects.size()));
|
print_objects_new.reserve(std::max(m_objects.size(), m_model.objects.size()));
|
||||||
bool new_objects = false;
|
bool new_objects = false;
|
||||||
|
bool belt_instances_shifted = false;
|
||||||
// Walk over all new model objects and check, whether there are matching PrintObjects.
|
// Walk over all new model objects and check, whether there are matching PrintObjects.
|
||||||
for (ModelObject *model_object : m_model.objects) {
|
for (ModelObject *model_object : m_model.objects) {
|
||||||
ModelObjectStatus &model_object_status = const_cast<ModelObjectStatus&>(model_object_status_db.reuse(*model_object));
|
ModelObjectStatus &model_object_status = const_cast<ModelObjectStatus&>(model_object_status_db.reuse(*model_object));
|
||||||
// Orca: Updated for XYZ filament shrink compensation
|
// Orca: Updated for XYZ filament shrink compensation
|
||||||
model_object_status.print_instances = print_objects_from_model_object(*model_object, this->shrinkage_compensation());
|
// Belt global mode: force each instance into its own PrintObject
|
||||||
|
// so each gets independent layer Z values.
|
||||||
|
bool belt_force_separate = m_config.belt_printer.value && (
|
||||||
|
(m_config.belt_slice_rotation_global.value
|
||||||
|
&& m_config.belt_slice_rotation.value != BeltRotationAxis::None
|
||||||
|
&& std::abs(m_config.belt_slice_rotation_angle.value) > EPSILON)
|
||||||
|
|| m_config.belt_preslice_global.value
|
||||||
|
|| (m_config.preslice_remap_global.value && BeltTransformPipeline::has_preslice_remap(m_config)));
|
||||||
|
model_object_status.print_instances = print_objects_from_model_object(*model_object, this->shrinkage_compensation(), belt_force_separate);
|
||||||
std::vector<const PrintObjectStatus*> old;
|
std::vector<const PrintObjectStatus*> old;
|
||||||
old.reserve(print_object_status_db.count(*model_object));
|
old.reserve(print_object_status_db.count(*model_object));
|
||||||
for (const PrintObjectStatus &print_object_status : print_object_status_db.get_range(*model_object))
|
for (const PrintObjectStatus &print_object_status : print_object_status_db.get_range(*model_object))
|
||||||
@@ -1800,6 +1817,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
|||||||
if (status != PrintBase::APPLY_STATUS_UNCHANGED) {
|
if (status != PrintBase::APPLY_STATUS_UNCHANGED) {
|
||||||
size_t extruder_num = new_full_config.option<ConfigOptionFloats>("nozzle_diameter")->size();
|
size_t extruder_num = new_full_config.option<ConfigOptionFloats>("nozzle_diameter")->size();
|
||||||
update_apply_status(status == PrintBase::APPLY_STATUS_INVALIDATED);
|
update_apply_status(status == PrintBase::APPLY_STATUS_INVALIDATED);
|
||||||
|
belt_instances_shifted = true;
|
||||||
}
|
}
|
||||||
print_objects_new.emplace_back((*it_old)->print_object);
|
print_objects_new.emplace_back((*it_old)->print_object);
|
||||||
const_cast<PrintObjectStatus*>(*it_old)->status = PrintObjectStatus::Reused;
|
const_cast<PrintObjectStatus*>(*it_old)->status = PrintObjectStatus::Reused;
|
||||||
@@ -1835,6 +1853,20 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
|||||||
update_apply_status(object->invalidate_step(posSlice));
|
update_apply_status(object->invalidate_step(posSlice));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Belt printer global mode: when any object's instances shifted,
|
||||||
|
// recompute m_belt_global_z_offset for ALL objects (it depends on
|
||||||
|
// min_shift across all objects, so one move affects everyone).
|
||||||
|
if (belt_instances_shifted
|
||||||
|
&& m_config.belt_printer.value
|
||||||
|
&& ((m_config.belt_slice_rotation_global.value
|
||||||
|
&& m_config.belt_slice_rotation.value != BeltRotationAxis::None
|
||||||
|
&& std::abs(m_config.belt_slice_rotation_angle.value) > EPSILON)
|
||||||
|
|| m_config.belt_preslice_global.value
|
||||||
|
|| (m_config.preslice_remap_global.value && BeltTransformPipeline::has_preslice_remap(m_config)))) {
|
||||||
|
for (PrintObject *object : m_objects)
|
||||||
|
update_apply_status(object->invalidate_step(posSlice));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//BBS: check the config again
|
//BBS: check the config again
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
#include "PrintConfig.hpp"
|
#include "PrintConfig.hpp"
|
||||||
#include "PrintConfigConstants.hpp"
|
#include "PrintConfigConstants.hpp"
|
||||||
|
#include "BeltTransform.hpp"
|
||||||
#include "ClipperUtils.hpp"
|
#include "ClipperUtils.hpp"
|
||||||
#include "Config.hpp"
|
#include "Config.hpp"
|
||||||
|
#include "Geometry.hpp"
|
||||||
#include "FilamentMixer.hpp"
|
#include "FilamentMixer.hpp"
|
||||||
#include "MaterialType.hpp"
|
#include "MaterialType.hpp"
|
||||||
#include "I18N.hpp"
|
#include "I18N.hpp"
|
||||||
@@ -347,6 +349,54 @@ static t_config_enum_values s_keys_map_SlicingMode {
|
|||||||
};
|
};
|
||||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SlicingMode)
|
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SlicingMode)
|
||||||
|
|
||||||
|
static t_config_enum_values s_keys_map_BeltRotationAxis {
|
||||||
|
{ "none", int(BeltRotationAxis::None) },
|
||||||
|
{ "x", int(BeltRotationAxis::X) },
|
||||||
|
{ "y", int(BeltRotationAxis::Y) },
|
||||||
|
{ "z", int(BeltRotationAxis::Z) },
|
||||||
|
};
|
||||||
|
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BeltRotationAxis)
|
||||||
|
|
||||||
|
static t_config_enum_values s_keys_map_RemapAxis {
|
||||||
|
{ "pos_x", int(RemapAxis::PosX) },
|
||||||
|
{ "pos_y", int(RemapAxis::PosY) },
|
||||||
|
{ "pos_z", int(RemapAxis::PosZ) },
|
||||||
|
{ "neg_x", int(RemapAxis::NegX) },
|
||||||
|
{ "neg_y", int(RemapAxis::NegY) },
|
||||||
|
{ "neg_z", int(RemapAxis::NegZ) },
|
||||||
|
{ "rev_x", int(RemapAxis::RevX) },
|
||||||
|
{ "rev_y", int(RemapAxis::RevY) },
|
||||||
|
{ "rev_z", int(RemapAxis::RevZ) },
|
||||||
|
};
|
||||||
|
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(RemapAxis)
|
||||||
|
|
||||||
|
static t_config_enum_values s_keys_map_BeltSupportFloorMode {
|
||||||
|
{ "none", int(BeltSupportFloorMode::None) },
|
||||||
|
{ "generator_only", int(BeltSupportFloorMode::GeneratorOnly) },
|
||||||
|
{ "clip_only", int(BeltSupportFloorMode::ClipOnly) },
|
||||||
|
{ "both", int(BeltSupportFloorMode::Both) },
|
||||||
|
};
|
||||||
|
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BeltSupportFloorMode)
|
||||||
|
|
||||||
|
static t_config_enum_values s_keys_map_BeltSupportZOffsetMode {
|
||||||
|
{ "none", int(BeltSupportZOffsetMode::None) },
|
||||||
|
{ "unconditional", int(BeltSupportZOffsetMode::Unconditional) },
|
||||||
|
{ "raft_only", int(BeltSupportZOffsetMode::RaftOnly) },
|
||||||
|
};
|
||||||
|
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BeltSupportZOffsetMode)
|
||||||
|
|
||||||
|
static t_config_enum_values s_keys_map_FirstLayerPlaneMode {
|
||||||
|
{ "auto", int(FirstLayerPlaneMode::Auto) },
|
||||||
|
{ "xy", int(FirstLayerPlaneMode::XY) },
|
||||||
|
{ "yz", int(FirstLayerPlaneMode::YZ) },
|
||||||
|
{ "xz", int(FirstLayerPlaneMode::XZ) },
|
||||||
|
{ "belt_affine", int(FirstLayerPlaneMode::BeltAffine) },
|
||||||
|
// Back-compat alias: pre-rotation builds serialised this mode as
|
||||||
|
// "belt_shear". Accept it on parse so old 3MFs / presets keep loading.
|
||||||
|
{ "belt_shear", int(FirstLayerPlaneMode::BeltAffine) },
|
||||||
|
};
|
||||||
|
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(FirstLayerPlaneMode)
|
||||||
|
|
||||||
static t_config_enum_values s_keys_map_SupportMaterialPattern {
|
static t_config_enum_values s_keys_map_SupportMaterialPattern {
|
||||||
{ "rectilinear", smpRectilinear },
|
{ "rectilinear", smpRectilinear },
|
||||||
{ "rectilinear-grid", smpRectilinearGrid },
|
{ "rectilinear-grid", smpRectilinearGrid },
|
||||||
@@ -463,6 +513,7 @@ static const t_config_enum_values s_keys_map_BrimType = {
|
|||||||
{"auto_brim", btAutoBrim}, // BBS
|
{"auto_brim", btAutoBrim}, // BBS
|
||||||
{"brim_ears", btEar}, // Orca
|
{"brim_ears", btEar}, // Orca
|
||||||
{"painted", btPainted}, // BBS
|
{"painted", btPainted}, // BBS
|
||||||
|
{"leading_edge_only", btLeadingEdgeOnly}, // belt printers
|
||||||
};
|
};
|
||||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BrimType)
|
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BrimType)
|
||||||
|
|
||||||
@@ -1853,6 +1904,45 @@ void PrintConfigDef::init_fff_params()
|
|||||||
def->mode = comSimple;
|
def->mode = comSimple;
|
||||||
def->set_default_value(new ConfigOptionFloat(0.));
|
def->set_default_value(new ConfigOptionFloat(0.));
|
||||||
|
|
||||||
|
def = this->add("leading_brim_length", coFloat);
|
||||||
|
def->label = L("Leading brim length");
|
||||||
|
def->category = L("Support");
|
||||||
|
def->tooltip = L("Belt printers only. Extends the brim AHEAD of the object along the belt, on "
|
||||||
|
"every downhill-facing edge of its contact area - both the object's first "
|
||||||
|
"contact with the belt and any island that lands later. This apron is laid "
|
||||||
|
"onto the belt before the object reaches it, so the leading edge has "
|
||||||
|
"something already stuck down to hold on to.\n\n"
|
||||||
|
"Measured on the belt surface, and added on top of Brim width: the brim "
|
||||||
|
"reaches Brim-object gap + Leading brim length + Brim width ahead of the "
|
||||||
|
"object. Set Brim-object gap to 0, or the apron will not touch the object it "
|
||||||
|
"is meant to anchor.\n\n"
|
||||||
|
"On a tilted belt each layer lays one strip of the brim, so the thickness of "
|
||||||
|
"the resulting brim sheet is set by flow rather than by layer height. Use "
|
||||||
|
"Brim flow ratio to tune it.\n\n"
|
||||||
|
"Set to 0 to disable.");
|
||||||
|
def->sidetext = L("mm"); // millimeters, CIS languages need translation
|
||||||
|
def->min = 0;
|
||||||
|
def->max = 100;
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->set_default_value(new ConfigOptionFloat(0.));
|
||||||
|
|
||||||
|
def = this->add("extra_brim_width", coFloat);
|
||||||
|
def->label = L("Extra brim width");
|
||||||
|
def->category = L("Support");
|
||||||
|
def->tooltip = L("Belt printers only. Widens the brim SIDEWAYS, across the belt, without "
|
||||||
|
"extending it further ahead of or behind the object. Use it when a part needs "
|
||||||
|
"more grip along its length than Brim width alone gives.\n\n"
|
||||||
|
"Measured on the belt surface, and added on top of Brim width: the brim "
|
||||||
|
"reaches Brim-object gap + Brim width + Extra brim width to either side of "
|
||||||
|
"the object. To extend the brim ahead of the object instead, use Leading brim "
|
||||||
|
"length.\n\n"
|
||||||
|
"Set to 0 to disable.");
|
||||||
|
def->sidetext = L("mm"); // millimeters, CIS languages need translation
|
||||||
|
def->min = 0;
|
||||||
|
def->max = 100;
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->set_default_value(new ConfigOptionFloat(0.));
|
||||||
|
|
||||||
def = this->add("brim_type", coEnum);
|
def = this->add("brim_type", coEnum);
|
||||||
def->label = L("Brim type");
|
def->label = L("Brim type");
|
||||||
def->category = L("Support");
|
def->category = L("Support");
|
||||||
@@ -1866,6 +1956,7 @@ void PrintConfigDef::init_fff_params()
|
|||||||
def->enum_values.emplace_back("inner_only");
|
def->enum_values.emplace_back("inner_only");
|
||||||
def->enum_values.emplace_back("outer_and_inner");
|
def->enum_values.emplace_back("outer_and_inner");
|
||||||
def->enum_values.emplace_back("no_brim");
|
def->enum_values.emplace_back("no_brim");
|
||||||
|
def->enum_values.emplace_back("leading_edge_only");
|
||||||
def->enum_labels.emplace_back(L("Auto"));
|
def->enum_labels.emplace_back(L("Auto"));
|
||||||
def->enum_labels.emplace_back(L("Mouse ear"));
|
def->enum_labels.emplace_back(L("Mouse ear"));
|
||||||
def->enum_labels.emplace_back(L("Painted"));
|
def->enum_labels.emplace_back(L("Painted"));
|
||||||
@@ -1873,6 +1964,7 @@ void PrintConfigDef::init_fff_params()
|
|||||||
def->enum_labels.emplace_back(L("Inner brim only"));
|
def->enum_labels.emplace_back(L("Inner brim only"));
|
||||||
def->enum_labels.emplace_back(L("Outer and inner brim"));
|
def->enum_labels.emplace_back(L("Outer and inner brim"));
|
||||||
def->enum_labels.emplace_back(L("No-brim"));
|
def->enum_labels.emplace_back(L("No-brim"));
|
||||||
|
def->enum_labels.emplace_back(L("Leading edge only"));
|
||||||
def->mode = comSimple;
|
def->mode = comSimple;
|
||||||
def->set_default_value(new ConfigOptionEnum<BrimType>(btAutoBrim));
|
def->set_default_value(new ConfigOptionEnum<BrimType>(btAutoBrim));
|
||||||
|
|
||||||
@@ -7063,6 +7155,297 @@ void PrintConfigDef::init_fff_params()
|
|||||||
def->mode = comSimple;
|
def->mode = comSimple;
|
||||||
def->set_default_value(new ConfigOptionFloatOrPercent(50., true));
|
def->set_default_value(new ConfigOptionFloatOrPercent(50., true));
|
||||||
|
|
||||||
|
def = this->add("build_plate_tilt_x", coFloat);
|
||||||
|
def->label = L("Build plate tilt X");
|
||||||
|
def->category = L("Support");
|
||||||
|
def->tooltip = L("Tilt angle of the build plate along the X axis. "
|
||||||
|
"A positive value tilts the plate so the +X side is higher, shifting gravity toward -X and increasing overhangs on the +X side. "
|
||||||
|
"A negative value tilts the -X side higher. Set to 0 for no X-axis tilt. "
|
||||||
|
"In belt printer mode, this is automatically synced to the belt angle.");
|
||||||
|
def->sidetext = u8"\u00B0";
|
||||||
|
def->min = -90;
|
||||||
|
def->max = 90;
|
||||||
|
def->mode = comExpert;
|
||||||
|
def->set_default_value(new ConfigOptionFloat(0.));
|
||||||
|
|
||||||
|
def = this->add("build_plate_tilt_y", coFloat);
|
||||||
|
def->label = L("Build plate tilt Y");
|
||||||
|
def->category = L("Support");
|
||||||
|
def->tooltip = L("Tilt angle of the build plate along the Y axis. "
|
||||||
|
"A positive value tilts the plate so the +Y side is higher, shifting gravity toward -Y and increasing overhangs on the +Y side. "
|
||||||
|
"A negative value tilts the -Y side higher. Set to 0 for no Y-axis tilt.");
|
||||||
|
def->sidetext = u8"\u00B0";
|
||||||
|
def->min = -90;
|
||||||
|
def->max = 90;
|
||||||
|
def->mode = comExpert;
|
||||||
|
def->set_default_value(new ConfigOptionFloat(0.));
|
||||||
|
|
||||||
|
def = this->add("belt_printer", coBool);
|
||||||
|
def->label = L("Enable belt printing");
|
||||||
|
def->category = L("Printable space");
|
||||||
|
def->tooltip = L("Enable belt printer mode. Belt printers use a conveyor belt as the build surface, "
|
||||||
|
"tilted at an angle (typically 45 degrees). The slicer will rotate the slicing plane "
|
||||||
|
"and transform G-code coordinates for the tilted build surface.");
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->set_default_value(new ConfigOptionBool(false));
|
||||||
|
|
||||||
|
def = this->add("belt_printer_infinite_y", coBool);
|
||||||
|
def->label = L("Infinite Y axis");
|
||||||
|
def->category = L("Printable space");
|
||||||
|
def->tooltip = L("Enable infinite Y axis for belt printers. "
|
||||||
|
"When enabled, the Y axis build volume limit is effectively removed, "
|
||||||
|
"allowing objects of any length to be printed along the belt direction.");
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->set_default_value(new ConfigOptionBool(true));
|
||||||
|
|
||||||
|
// Mesh rotation applied before slicing — the sole mesh-side belt transform AND
|
||||||
|
// the single source of truth for the physical belt tilt (bed rendering, support
|
||||||
|
// gravity tilt and bed-exclusion projection all derive their angle from this).
|
||||||
|
def = this->add("belt_slice_rotation", coEnum);
|
||||||
|
def->label = L("Belt tilt axis");
|
||||||
|
def->category = L("Printable space");
|
||||||
|
def->tooltip = L("Axis the mesh is rotated about before slicing. This is the belt "
|
||||||
|
"printer's tilt: an isometric (no distortion) rotation that also "
|
||||||
|
"drives bed rendering and support gravity tilt, and that the g-code "
|
||||||
|
"back-transform inverts before the machine-frame shear/scale and remap. "
|
||||||
|
"X is the typical gantry tilt (belt travels along Y).");
|
||||||
|
def->enum_keys_map = &ConfigOptionEnum<BeltRotationAxis>::get_enum_values();
|
||||||
|
def->enum_values = {"none", "x", "y", "z"};
|
||||||
|
def->enum_labels = {L("None"), L("X"), L("Y"), L("Z")};
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->set_default_value(new ConfigOptionEnum<BeltRotationAxis>(BeltRotationAxis::X));
|
||||||
|
|
||||||
|
def = this->add("belt_slice_rotation_angle", coFloat);
|
||||||
|
def->label = L("Belt tilt angle");
|
||||||
|
def->category = L("Printable space");
|
||||||
|
def->tooltip = L("Tilt angle of the belt surface, in degrees. Most belt printers use "
|
||||||
|
"45°. Positive values rotate counter-clockwise looking down the "
|
||||||
|
"positive tilt axis; the magnitude is also the physical belt tilt "
|
||||||
|
"used for bed rendering and support gravity.");
|
||||||
|
def->sidetext = L("°");
|
||||||
|
def->min = -180.;
|
||||||
|
def->max = 180.;
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->set_default_value(new ConfigOptionFloat(45.));
|
||||||
|
|
||||||
|
def = this->add("belt_slice_rotation_global", coBool);
|
||||||
|
def->label = L("Global");
|
||||||
|
def->category = L("Printable space");
|
||||||
|
def->tooltip = L("Treat the slicing rotation as part of the global forward transform "
|
||||||
|
"that BeltBackTransform inverts before the machine-frame remap. "
|
||||||
|
"Required for rotation-mode belt printers. "
|
||||||
|
"Defaults to on because virtually all rotation-mode printers need it.");
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->set_default_value(new ConfigOptionBool(true));
|
||||||
|
|
||||||
|
def = this->add("belt_frame_tilt_decouple", coBool);
|
||||||
|
def->label = L("Decouple machine-frame tilt");
|
||||||
|
def->category = L("Printable space");
|
||||||
|
def->tooltip = L("Expert override: set the machine-frame (g-code shear/scale) tilt angle "
|
||||||
|
"independently of the pre-slice rotation angle. When disabled, the "
|
||||||
|
"machine-frame transform is derived from the belt tilt angle, so a single "
|
||||||
|
"angle drives both stages. Enable only to compensate for a machine whose "
|
||||||
|
"physical gantry tilt differs from the slicing rotation.");
|
||||||
|
def->mode = comExpert;
|
||||||
|
def->set_default_value(new ConfigOptionBool(false));
|
||||||
|
|
||||||
|
def = this->add("belt_frame_tilt_angle", coFloat);
|
||||||
|
def->label = L("Machine-frame tilt angle");
|
||||||
|
def->category = L("Printable space");
|
||||||
|
def->tooltip = L("Tilt angle (degrees) used to derive the machine-frame shear (tan) and "
|
||||||
|
"scale (1/cos) applied to G-code. Only used when 'Decouple machine-frame "
|
||||||
|
"tilt' is enabled; otherwise the belt tilt angle is used.");
|
||||||
|
def->sidetext = L("°");
|
||||||
|
def->min = -89.9;
|
||||||
|
def->max = 89.9;
|
||||||
|
def->mode = comExpert;
|
||||||
|
def->set_default_value(new ConfigOptionFloat(45.));
|
||||||
|
|
||||||
|
// G-code axis remap with sign
|
||||||
|
auto add_belt_remap = [this](const char *key, const char *label, const char *tooltip,
|
||||||
|
RemapAxis default_axis, ConfigOptionMode mode = comSimple) {
|
||||||
|
auto def = this->add(key, coEnum);
|
||||||
|
def->label = L(label);
|
||||||
|
def->category = L("Printable space");
|
||||||
|
def->tooltip = L(tooltip);
|
||||||
|
def->enum_keys_map = &ConfigOptionEnum<RemapAxis>::get_enum_values();
|
||||||
|
def->enum_values = {"pos_x", "pos_y", "pos_z", "neg_x", "neg_y", "neg_z", "rev_x", "rev_y", "rev_z"};
|
||||||
|
def->enum_labels = {L("+X"), L("+Y"), L("+Z"), L("-X"), L("-Y"), L("-Z"), L("Rev X"), L("Rev Y"), L("Rev Z")};
|
||||||
|
def->mode = mode; // Visibility may also be gated by toggle_line in Tab.cpp
|
||||||
|
def->set_default_value(new ConfigOptionEnum<RemapAxis>(default_axis));
|
||||||
|
};
|
||||||
|
|
||||||
|
add_belt_remap("preslice_remap_x", "X",
|
||||||
|
"Before slicing, which model-space axis becomes the slicer's X axis. "
|
||||||
|
"Use this to re-orient the coordinate system so the slicer's XY plane matches "
|
||||||
|
"your belt printer's physical bed plane. For a printer whose bed is in the XZ plane, "
|
||||||
|
"set Y to +Z and Z to +Y (or -Y) to swap the vertical and belt-travel axes. "
|
||||||
|
"Default +X: no change.",
|
||||||
|
RemapAxis::PosX, comExpert);
|
||||||
|
add_belt_remap("preslice_remap_y", "Y",
|
||||||
|
"Before slicing, which model-space axis becomes the slicer's Y axis. "
|
||||||
|
"The slicer treats Y as one of the two horizontal bed axes. If your physical "
|
||||||
|
"belt surface runs along the Z axis, map Y to +Z here so the slicer slices "
|
||||||
|
"along the correct plane. Default +Y: no change.",
|
||||||
|
RemapAxis::PosY, comExpert);
|
||||||
|
add_belt_remap("preslice_remap_z", "Z",
|
||||||
|
"Before slicing, which model-space axis becomes the slicer's Z axis (layer stacking direction). "
|
||||||
|
"The slicer builds layers upward along this axis. If your printer's layer-stacking "
|
||||||
|
"direction is the physical Y axis, map Z to +Y (or -Y for inverted direction). "
|
||||||
|
"Rev mode mirrors relative to the build volume maximum. Default +Z: no change.",
|
||||||
|
RemapAxis::PosZ, comExpert);
|
||||||
|
|
||||||
|
def = this->add("preslice_remap_global", coBool);
|
||||||
|
def->label = L("Global");
|
||||||
|
def->category = L("Printable space");
|
||||||
|
def->tooltip = L("When enabled, the pre-slice axis remap accounts for each object's bed position. "
|
||||||
|
"Without this, the remap is applied locally around each object's center, so "
|
||||||
|
"objects at different positions don't get a position-dependent contribution. "
|
||||||
|
"Mirrors the 'Global' option on the belt slicing rotation, but for the remap.");
|
||||||
|
def->mode = comExpert;
|
||||||
|
def->set_default_value(new ConfigOptionBool(false));
|
||||||
|
|
||||||
|
add_belt_remap("gcode_remap_x", "X", "Which slicing axis maps to machine X in G-code output. Applied AFTER slicing, during G-code generation.", RemapAxis::PosX, comExpert);
|
||||||
|
add_belt_remap("gcode_remap_y", "Y", "Which slicing axis maps to machine Y in G-code output. Applied AFTER slicing, during G-code generation.", RemapAxis::PosY, comExpert);
|
||||||
|
add_belt_remap("gcode_remap_z", "Z", "Which slicing axis maps to machine Z in G-code output. Applied AFTER slicing, during G-code generation.", RemapAxis::PosZ, comExpert);
|
||||||
|
|
||||||
|
// The machine-frame G-code transform (shear + scale) is no longer configured
|
||||||
|
// by per-axis keys: it is derived from the belt tilt (belt_slice_rotation axis
|
||||||
|
// + angle, or belt_frame_tilt_angle when decoupled) in MachineFrameTransform.
|
||||||
|
|
||||||
|
def = this->add("gcode_back_transform", coBool);
|
||||||
|
def->label = L("G-code back-transform");
|
||||||
|
def->category = L("Printable space");
|
||||||
|
def->tooltip = L("Undo the pre-slice mesh transform before applying the G-code axis remap "
|
||||||
|
"and machine-frame shear/scale. Required for the standard belt-printer "
|
||||||
|
"rotation pipeline.");
|
||||||
|
def->mode = comExpert;
|
||||||
|
def->set_default_value(new ConfigOptionBool(true));
|
||||||
|
|
||||||
|
def = this->add("belt_preslice_global", coBool);
|
||||||
|
def->label = L("Global mesh transforms");
|
||||||
|
def->category = L("Printable space");
|
||||||
|
def->tooltip = L("When enabled, pre-slice belt transforms (remap, shear, scale) account for "
|
||||||
|
"each object's bed position, producing correct machine coordinates without "
|
||||||
|
"relying on origin snap. Each instance gets its own PrintObject.");
|
||||||
|
def->mode = comExpert;
|
||||||
|
def->set_default_value(new ConfigOptionBool(true));
|
||||||
|
|
||||||
|
// First-layer plane: which surface defines "first layer" for fan / speed /
|
||||||
|
// accel decisions. On belt printers the slicing-frame layer 0 is a tilted
|
||||||
|
// slab that no longer corresponds to the physical first printed layer.
|
||||||
|
// Auto picks BeltAffine when any belt-side affine transform is active
|
||||||
|
// (Z shear or slicing rotation), otherwise XY (legacy).
|
||||||
|
def = this->add("first_layer_plane", coEnum);
|
||||||
|
def->label = L("First layer plane");
|
||||||
|
def->category = L("Printable space");
|
||||||
|
def->tooltip = L("Selects the reference plane used to decide which extrusions get "
|
||||||
|
"first-layer settings (no fan, slow speed, initial-layer accel/jerk, "
|
||||||
|
"deferred temperature drop). On belt printers a single slicing layer "
|
||||||
|
"contains paths at many machine-Z values, so layer-index based detection "
|
||||||
|
"fails. Auto resolves to Belt affine plane when any belt-side affine "
|
||||||
|
"transform (Z shear or slicing rotation) is active, otherwise XY (legacy). "
|
||||||
|
"Pick XY explicitly to opt out and force the legacy slicing-layer-0 "
|
||||||
|
"detection.");
|
||||||
|
def->enum_keys_map = &ConfigOptionEnum<FirstLayerPlaneMode>::get_enum_values();
|
||||||
|
def->enum_values = {"auto", "xy", "yz", "xz", "belt_affine"};
|
||||||
|
def->enum_labels = {L("Auto"), L("XY (machine bed)"), L("YZ"), L("XZ"), L("Belt affine plane")};
|
||||||
|
def->mode = comExpert;
|
||||||
|
// Auto, not BeltAffine: BeltAffine activates the plane evaluator unconditionally, so on a
|
||||||
|
// non-belt printer on_first_layer(point) stopped agreeing with the legacy slicing-layer-0
|
||||||
|
// test and first-layer speeds were skipped (brim printed at the volumetric fallback rather
|
||||||
|
// than initial_layer_speed). Auto resolves to BeltAffine only when belt_printer is set with
|
||||||
|
// a non-zero slicing rotation, and to XY (evaluator inactive, legacy behaviour) otherwise --
|
||||||
|
// which is what this option's own description promises.
|
||||||
|
def->set_default_value(new ConfigOptionEnum<FirstLayerPlaneMode>(FirstLayerPlaneMode::Auto));
|
||||||
|
|
||||||
|
def = this->add("first_layer_plane_offset", coFloat);
|
||||||
|
def->label = L("Belt plane offset");
|
||||||
|
def->category = L("Printable space");
|
||||||
|
def->tooltip = L("Shifts the first-layer plane along its normal (mm). For axis-aligned "
|
||||||
|
"planes this is just a coordinate shift. Positive values move the plane "
|
||||||
|
"away from the belt surface (deeper into the model).");
|
||||||
|
def->sidetext = L("mm");
|
||||||
|
def->min = -1000;
|
||||||
|
def->max = 1000;
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->set_default_value(new ConfigOptionFloat(0.0));
|
||||||
|
|
||||||
|
def = this->add("first_layer_plane_thickness", coFloat);
|
||||||
|
def->label = L("Plane band thickness");
|
||||||
|
def->category = L("Printable space");
|
||||||
|
def->tooltip = L("Thickness of one 'band' relative to the first-layer plane, in mm. "
|
||||||
|
"Used as the unit by which 'No cooling for the first N layers' (and "
|
||||||
|
"similar layer-count thresholds) is multiplied when the first-layer "
|
||||||
|
"plane is active. -1 means use initial_layer_print_height.");
|
||||||
|
def->sidetext = L("mm");
|
||||||
|
def->min = -1;
|
||||||
|
def->max = 100;
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->set_default_value(new ConfigOptionFloat(-1.0));
|
||||||
|
|
||||||
|
// Belt support floor debug controls
|
||||||
|
def = this->add("belt_support_floor_offset", coFloat);
|
||||||
|
def->label = L("Support Floor Z offset");
|
||||||
|
def->category = L("Printable space");
|
||||||
|
def->tooltip = L("Shifts the computed belt floor up or down (mm). Negative values lower the floor, allowing more supports to survive. Use this to diagnose belt floor formula issues.");
|
||||||
|
def->sidetext = L("mm");
|
||||||
|
def->min = -500;
|
||||||
|
def->max = 500;
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->set_default_value(new ConfigOptionFloat(0));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto def = this->add("belt_support_floor_mode", coEnum);
|
||||||
|
def->label = L("Floor mode");
|
||||||
|
def->category = L("Printable space");
|
||||||
|
def->tooltip = L("Controls belt floor awareness for supports. 'None' disables belt floor logic. "
|
||||||
|
"'Generator only' stops support generation at the belt floor plane.");
|
||||||
|
def->enum_keys_map = &ConfigOptionEnum<BeltSupportFloorMode>::get_enum_values();
|
||||||
|
def->enum_values = {"none", "generator_only"};
|
||||||
|
def->enum_labels = {L("None"), L("Generator only")};
|
||||||
|
def->mode = comDevelop;
|
||||||
|
def->set_default_value(new ConfigOptionEnum<BeltSupportFloorMode>(BeltSupportFloorMode::GeneratorOnly));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto def = this->add("belt_support_z_offset_mode", coEnum);
|
||||||
|
def->label = L("Z offset mode");
|
||||||
|
def->category = L("Printable space");
|
||||||
|
def->tooltip = L("How global Z offset is applied to support layers for belt printers with global shear. "
|
||||||
|
"'None' = don't offset. 'Unconditional' = offset all layers. 'Raft only' = only offset raft layers.");
|
||||||
|
def->enum_keys_map = &ConfigOptionEnum<BeltSupportZOffsetMode>::get_enum_values();
|
||||||
|
def->enum_values = {"none", "unconditional", "raft_only"};
|
||||||
|
def->enum_labels = {L("None"), L("Unconditional"), L("Raft only")};
|
||||||
|
def->mode = comExpert;
|
||||||
|
def->set_default_value(new ConfigOptionEnum<BeltSupportZOffsetMode>(BeltSupportZOffsetMode::Unconditional));
|
||||||
|
}
|
||||||
|
|
||||||
|
def = this->add("enable_belt_purge_tower", coBool);
|
||||||
|
def->label = L("Enable belt purge tower");
|
||||||
|
def->category = L("Multimaterial");
|
||||||
|
def->tooltip = L("Belt-printer replacement for the wipe/prime tower. When enabled on a belt "
|
||||||
|
"printer, a purge prism is automatically generated next to the printed parts "
|
||||||
|
"and filament-change purging is routed into it (the classic wipe tower cannot "
|
||||||
|
"be used on belt printers because its G-code bypasses the belt transform). "
|
||||||
|
"Only available on belt printers.");
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->set_default_value(new ConfigOptionBool(false));
|
||||||
|
|
||||||
|
def = this->add("belt_purge_tower_width", coFloat);
|
||||||
|
def->label = L("Belt purge tower width");
|
||||||
|
def->category = L("Printable space");
|
||||||
|
def->tooltip = L("Width (machine X, across the belt) of the purge prism that is automatically "
|
||||||
|
"generated on belt printers when the prime tower is enabled and multiple "
|
||||||
|
"filaments are used. Filament-change purging is routed into this prism's "
|
||||||
|
"extrusions instead of a classic wipe tower. Its height is computed "
|
||||||
|
"automatically from the worst-case purge volume per layer: a wider prism "
|
||||||
|
"results in a shorter one.");
|
||||||
|
def->sidetext = L("mm");
|
||||||
|
def->min = 1.;
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->set_default_value(new ConfigOptionFloat(35.));
|
||||||
|
|
||||||
def = this->add("tree_support_branch_angle", coFloat);
|
def = this->add("tree_support_branch_angle", coFloat);
|
||||||
def->label = L("Tree support branch angle");
|
def->label = L("Tree support branch angle");
|
||||||
def->category = L("Support");
|
def->category = L("Support");
|
||||||
@@ -7716,6 +8099,16 @@ void PrintConfigDef::init_fff_params()
|
|||||||
"It will not take effect unless the prime tower is enabled.");
|
"It will not take effect unless the prime tower is enabled.");
|
||||||
def->set_default_value(new ConfigOptionBool(false));
|
def->set_default_value(new ConfigOptionBool(false));
|
||||||
|
|
||||||
|
// Internal marker (not shown in any settings tab): identifies the auto-generated
|
||||||
|
// belt purge prism so it can be updated/removed by the auto-manager and aligned
|
||||||
|
// to the object layer grid by the backend. Persisted to 3mf like any per-object key.
|
||||||
|
def = this->add("belt_purge_tower_object", coBool);
|
||||||
|
def->category = L("Flush options");
|
||||||
|
def->label = L("Belt purge tower object");
|
||||||
|
def->tooltip = L("Marks the auto-generated belt purge prism. Managed automatically; do not set manually.");
|
||||||
|
def->mode = comDevelop;
|
||||||
|
def->set_default_value(new ConfigOptionBool(false));
|
||||||
|
|
||||||
def = this->add("wipe_tower_bridging", coFloat);
|
def = this->add("wipe_tower_bridging", coFloat);
|
||||||
def->label = L("Maximal bridging distance");
|
def->label = L("Maximal bridging distance");
|
||||||
def->tooltip = L("Maximal distance between supports on sparse infill sections.");
|
def->tooltip = L("Maximal distance between supports on sparse infill sections.");
|
||||||
@@ -12729,10 +13122,22 @@ Polygons get_bed_excluded_area(const PrintConfig& cfg)
|
|||||||
{
|
{
|
||||||
const Pointfs exclude_area_points = cfg.bed_exclude_area.values;
|
const Pointfs exclude_area_points = cfg.bed_exclude_area.values;
|
||||||
|
|
||||||
|
// Belt printer: project exclusion zone points from the belt surface to machine-frame XY.
|
||||||
|
// On the belt surface Z=0, so the in-plane axis foreshortens by cos(tilt). The tilt
|
||||||
|
// axis decides which bed axis foreshortens: tilt about X (belt along Y) scales Y,
|
||||||
|
// tilt about Y (belt along X) scales X. Derived from belt_slice_rotation.
|
||||||
|
const bool is_belt = cfg.belt_printer.value;
|
||||||
|
const auto tilt = BeltTransformPipeline::physical_tilt(
|
||||||
|
cfg.belt_slice_rotation.value, cfg.belt_slice_rotation_angle.value);
|
||||||
|
const double cos_x = is_belt ? std::cos(Geometry::deg2rad(tilt.tilt_x_deg)) : 1.0; // foreshortens Y
|
||||||
|
const double cos_y = is_belt ? std::cos(Geometry::deg2rad(tilt.tilt_y_deg)) : 1.0; // foreshortens X
|
||||||
|
|
||||||
Polygon exclude_poly;
|
Polygon exclude_poly;
|
||||||
for (int i = 0; i < exclude_area_points.size(); i++) {
|
for (int i = 0; i < exclude_area_points.size(); i++) {
|
||||||
auto pt = exclude_area_points[i];
|
auto pt = exclude_area_points[i];
|
||||||
exclude_poly.points.emplace_back(scale_(pt.x()), scale_(pt.y()));
|
double x = is_belt ? pt.x() * cos_y : pt.x();
|
||||||
|
double y = is_belt ? pt.y() * cos_x : pt.y();
|
||||||
|
exclude_poly.points.emplace_back(scale_(x), scale_(y));
|
||||||
}
|
}
|
||||||
|
|
||||||
exclude_poly.make_counter_clockwise();
|
exclude_poly.make_counter_clockwise();
|
||||||
|
|||||||
@@ -252,6 +252,61 @@ enum class SlicingMode
|
|||||||
CloseHoles,
|
CloseHoles,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Axis around which the mesh is rotated before slicing, when
|
||||||
|
// `belt_slice_rotation` is set. None disables the rotation stage. This is the
|
||||||
|
// single "belt tilt" axis: it drives both the pre-slice mesh rotation and the
|
||||||
|
// post-slice machine-frame transform (shear + scale derived from the tilt angle).
|
||||||
|
enum class BeltRotationAxis
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
X = 1,
|
||||||
|
Y = 2,
|
||||||
|
Z = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class RemapAxis
|
||||||
|
{
|
||||||
|
PosX = 0, PosY = 1, PosZ = 2,
|
||||||
|
NegX = 3, NegY = 4, NegZ = 5,
|
||||||
|
RevX = 6, RevY = 7, RevZ = 8, // Reversed: max - pos
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class BeltSupportFloorMode
|
||||||
|
{
|
||||||
|
None, // No belt floor awareness
|
||||||
|
GeneratorOnly, // Only in tree support drop_nodes/contact_points
|
||||||
|
ClipOnly, // Only post-processing clipping
|
||||||
|
Both, // Both generator and clipping
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class BeltSupportZOffsetMode
|
||||||
|
{
|
||||||
|
None, // Don't apply global_z_offset to support layers
|
||||||
|
Unconditional, // Apply to all support layers
|
||||||
|
RaftOnly, // Only apply to raft layers
|
||||||
|
};
|
||||||
|
|
||||||
|
// Selects which plane the slicer treats as the "first layer plane" — the
|
||||||
|
// reference surface used to decide which extrusions get first-layer settings
|
||||||
|
// (no fan, slow speed, initial-layer accel/jerk, deferred temperature drop).
|
||||||
|
//
|
||||||
|
// Auto resolves to:
|
||||||
|
// - XY (inactive, legacy behavior) for non-belt printers and for belt
|
||||||
|
// printers with no active belt-side transform.
|
||||||
|
// - BeltAffine for belt printers with any active belt-side affine
|
||||||
|
// transform (Z shear, slicing rotation, or both).
|
||||||
|
//
|
||||||
|
// XY is also used as an explicit "opt out" mode that forces legacy
|
||||||
|
// per-layer first-layer detection even on belt printers.
|
||||||
|
enum class FirstLayerPlaneMode
|
||||||
|
{
|
||||||
|
Auto = 0,
|
||||||
|
XY,
|
||||||
|
YZ,
|
||||||
|
XZ,
|
||||||
|
BeltAffine, // formerly BeltShear; renamed to reflect rotation support
|
||||||
|
};
|
||||||
|
|
||||||
enum SupportMaterialPattern {
|
enum SupportMaterialPattern {
|
||||||
smpDefault,
|
smpDefault,
|
||||||
smpRectilinear, smpRectilinearGrid, smpHoneycomb,
|
smpRectilinear, smpRectilinearGrid, smpHoneycomb,
|
||||||
@@ -359,6 +414,10 @@ enum BrimType {
|
|||||||
btInnerOnly,
|
btInnerOnly,
|
||||||
btOuterAndInner,
|
btOuterAndInner,
|
||||||
btNoBrim,
|
btNoBrim,
|
||||||
|
// Belt printers: brim only where the part first touches the belt, nothing after
|
||||||
|
// that. Appended last so no existing value shifts. On a non-belt printer this
|
||||||
|
// has no meaning and behaves as btOuterOnly.
|
||||||
|
btLeadingEdgeOnly,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum TimelapseType : int {
|
enum TimelapseType : int {
|
||||||
@@ -670,6 +729,11 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(NoiseType)
|
|||||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(InfillPattern)
|
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(InfillPattern)
|
||||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(IroningType)
|
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(IroningType)
|
||||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SlicingMode)
|
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SlicingMode)
|
||||||
|
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltRotationAxis)
|
||||||
|
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(RemapAxis)
|
||||||
|
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltSupportFloorMode)
|
||||||
|
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BeltSupportZOffsetMode)
|
||||||
|
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(FirstLayerPlaneMode)
|
||||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialPattern)
|
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialPattern)
|
||||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialStyle)
|
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialStyle)
|
||||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialInterfacePattern)
|
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialInterfacePattern)
|
||||||
@@ -1111,6 +1175,8 @@ PRINT_CONFIG_CLASS_DEFINE(
|
|||||||
((ConfigOptionBool, brim_use_efc_outline))
|
((ConfigOptionBool, brim_use_efc_outline))
|
||||||
((ConfigOptionEnum<BrimType>, brim_type))
|
((ConfigOptionEnum<BrimType>, brim_type))
|
||||||
((ConfigOptionFloat, brim_width))
|
((ConfigOptionFloat, brim_width))
|
||||||
|
((ConfigOptionFloat, leading_brim_length))
|
||||||
|
((ConfigOptionFloat, extra_brim_width))
|
||||||
((ConfigOptionFloat, brim_ears_detection_length))
|
((ConfigOptionFloat, brim_ears_detection_length))
|
||||||
((ConfigOptionFloat, brim_ears_max_angle))
|
((ConfigOptionFloat, brim_ears_max_angle))
|
||||||
((ConfigOptionBool, brim_ears_outer_only))
|
((ConfigOptionBool, brim_ears_outer_only))
|
||||||
@@ -1194,6 +1260,9 @@ PRINT_CONFIG_CLASS_DEFINE(
|
|||||||
// BBS
|
// BBS
|
||||||
((ConfigOptionBool, flush_into_infill))
|
((ConfigOptionBool, flush_into_infill))
|
||||||
((ConfigOptionBool, flush_into_support))
|
((ConfigOptionBool, flush_into_support))
|
||||||
|
// Marker for the auto-generated belt purge prism; identifies the object to
|
||||||
|
// the auto-manager (GUI) and the layer-grid alignment step (backend).
|
||||||
|
((ConfigOptionBool, belt_purge_tower_object))
|
||||||
// BBS
|
// BBS
|
||||||
((ConfigOptionFloat, tree_support_branch_distance))
|
((ConfigOptionFloat, tree_support_branch_distance))
|
||||||
((ConfigOptionFloat, tree_support_tip_diameter))
|
((ConfigOptionFloat, tree_support_tip_diameter))
|
||||||
@@ -1744,6 +1813,45 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
|
|||||||
PrintConfig,
|
PrintConfig,
|
||||||
(MachineEnvelopeConfig, GCodeConfig),
|
(MachineEnvelopeConfig, GCodeConfig),
|
||||||
|
|
||||||
|
// Build plate tilt for off-axis gravity support generation (printer-level setting).
|
||||||
|
((ConfigOptionFloat, build_plate_tilt_x))
|
||||||
|
((ConfigOptionFloat, build_plate_tilt_y))
|
||||||
|
// Belt printer settings (printer-level).
|
||||||
|
((ConfigOptionBool, belt_printer))
|
||||||
|
((ConfigOptionBool, belt_printer_infinite_y))
|
||||||
|
// Mesh rotation applied before slicing — the single source of truth for the
|
||||||
|
// physical belt tilt. Its angle + axis drive bed rendering, support gravity
|
||||||
|
// tilt, the bed-exclusion projection, AND the post-slice machine-frame
|
||||||
|
// transform (shear + scale, derived from the tilt angle; see
|
||||||
|
// MachineFrameTransform). Isometric (no distortion) on the mesh side; the
|
||||||
|
// g-code back-transform inverts the rotation before the machine-frame stage.
|
||||||
|
((ConfigOptionEnum<BeltRotationAxis>, belt_slice_rotation))
|
||||||
|
((ConfigOptionFloat, belt_slice_rotation_angle))
|
||||||
|
((ConfigOptionBool, belt_slice_rotation_global))
|
||||||
|
// Expert override: decouple the machine-frame tilt angle from the pre-slice
|
||||||
|
// rotation angle. When disabled, the machine frame uses belt_slice_rotation_angle.
|
||||||
|
((ConfigOptionBool, belt_frame_tilt_decouple))
|
||||||
|
((ConfigOptionFloat, belt_frame_tilt_angle))
|
||||||
|
((ConfigOptionEnum<RemapAxis>, preslice_remap_x))
|
||||||
|
((ConfigOptionEnum<RemapAxis>, preslice_remap_y))
|
||||||
|
((ConfigOptionEnum<RemapAxis>, preslice_remap_z))
|
||||||
|
((ConfigOptionBool, preslice_remap_global))
|
||||||
|
((ConfigOptionEnum<RemapAxis>, gcode_remap_x))
|
||||||
|
((ConfigOptionEnum<RemapAxis>, gcode_remap_y))
|
||||||
|
((ConfigOptionEnum<RemapAxis>, gcode_remap_z))
|
||||||
|
((ConfigOptionBool, gcode_back_transform))
|
||||||
|
((ConfigOptionBool, belt_preslice_global))
|
||||||
|
((ConfigOptionEnum<FirstLayerPlaneMode>, first_layer_plane))
|
||||||
|
((ConfigOptionFloat, first_layer_plane_offset))
|
||||||
|
((ConfigOptionFloat, first_layer_plane_thickness))
|
||||||
|
((ConfigOptionFloat, belt_support_floor_offset))
|
||||||
|
((ConfigOptionEnum<BeltSupportFloorMode>, belt_support_floor_mode))
|
||||||
|
((ConfigOptionEnum<BeltSupportZOffsetMode>, belt_support_z_offset_mode))
|
||||||
|
// Width (machine X, across the belt) of the auto-generated belt purge prism.
|
||||||
|
((ConfigOptionFloat, belt_purge_tower_width))
|
||||||
|
// Belt-printer-only "type" of purge tower: enables the auto-generated belt
|
||||||
|
// purge prism (the belt replacement for the classic wipe/prime tower).
|
||||||
|
((ConfigOptionBool, enable_belt_purge_tower))
|
||||||
//BBS
|
//BBS
|
||||||
((ConfigOptionInts, additional_cooling_fan_speed))
|
((ConfigOptionInts, additional_cooling_fan_speed))
|
||||||
((ConfigOptionInts, close_additional_fan_first_x_layers))
|
((ConfigOptionInts, close_additional_fan_first_x_layers))
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
#include "Model.hpp"
|
#include "Model.hpp"
|
||||||
#include "Point.hpp"
|
#include "Point.hpp"
|
||||||
#include "Print.hpp"
|
#include "Print.hpp"
|
||||||
|
#include "BeltTransform.hpp"
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
#include "BoundingBox.hpp"
|
#include "BoundingBox.hpp"
|
||||||
#include "ClipperUtils.hpp"
|
#include "ClipperUtils.hpp"
|
||||||
#include "Clipper2Utils.hpp"
|
#include "Clipper2Utils.hpp"
|
||||||
@@ -13,6 +16,7 @@
|
|||||||
#include "PrintConfig.hpp"
|
#include "PrintConfig.hpp"
|
||||||
#include "SLA/IndexedMesh.hpp"
|
#include "SLA/IndexedMesh.hpp"
|
||||||
#include "Support/SupportMaterial.hpp"
|
#include "Support/SupportMaterial.hpp"
|
||||||
|
#include "Support/SupportCommon.hpp"
|
||||||
#include "Support/SupportSpotsGenerator.hpp"
|
#include "Support/SupportSpotsGenerator.hpp"
|
||||||
#include "Support/TreeSupport.hpp"
|
#include "Support/TreeSupport.hpp"
|
||||||
#include "Surface.hpp"
|
#include "Surface.hpp"
|
||||||
@@ -456,11 +460,15 @@ std::vector<std::set<int>> PrintObject::detect_extruder_geometric_unprintables()
|
|||||||
// 3) Generates perimeters, gap fills and fill regions (fill regions of type stInternal).
|
// 3) Generates perimeters, gap fills and fill regions (fill regions of type stInternal).
|
||||||
void PrintObject::make_perimeters()
|
void PrintObject::make_perimeters()
|
||||||
{
|
{
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "[BELTRACE] make_perimeters request tid=" << std::this_thread::get_id() << " obj=" << this;
|
||||||
// prerequisites
|
// prerequisites
|
||||||
this->slice();
|
this->slice();
|
||||||
|
|
||||||
if (! this->set_started(posPerimeters))
|
if (! this->set_started(posPerimeters)) {
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "[BELTRACE] make_perimeters SKIP tid=" << std::this_thread::get_id() << " obj=" << this << " (already started/done)";
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "[BELTRACE] make_perimeters ENTER tid=" << std::this_thread::get_id() << " obj=" << this;
|
||||||
|
|
||||||
m_print->set_status(15, L("Generating walls"));
|
m_print->set_status(15, L("Generating walls"));
|
||||||
BOOST_LOG_TRIVIAL(info) << "Generating walls..." << log_memory_info();
|
BOOST_LOG_TRIVIAL(info) << "Generating walls..." << log_memory_info();
|
||||||
@@ -558,6 +566,7 @@ void PrintObject::make_perimeters()
|
|||||||
m_print->throw_if_canceled();
|
m_print->throw_if_canceled();
|
||||||
BOOST_LOG_TRIVIAL(debug) << "Generating perimeters in parallel - end";
|
BOOST_LOG_TRIVIAL(debug) << "Generating perimeters in parallel - end";
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "[BELTRACE] make_perimeters EXIT tid=" << std::this_thread::get_id() << " obj=" << this;
|
||||||
this->set_done(posPerimeters);
|
this->set_done(posPerimeters);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -946,7 +955,9 @@ void PrintObject::detect_overhangs_for_lift()
|
|||||||
|
|
||||||
void PrintObject::generate_support_material()
|
void PrintObject::generate_support_material()
|
||||||
{
|
{
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "[BELTRACE] generate_support_material request tid=" << std::this_thread::get_id() << " obj=" << this;
|
||||||
if (this->set_started(posSupportMaterial)) {
|
if (this->set_started(posSupportMaterial)) {
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "[BELTRACE] generate_support_material ENTER tid=" << std::this_thread::get_id() << " obj=" << this;
|
||||||
this->clear_support_layers();
|
this->clear_support_layers();
|
||||||
|
|
||||||
if(!has_support() && !m_print->get_no_check_flag()) {
|
if(!has_support() && !m_print->get_no_check_flag()) {
|
||||||
@@ -987,7 +998,17 @@ void PrintObject::generate_support_material()
|
|||||||
this->_generate_support_material();
|
this->_generate_support_material();
|
||||||
m_print->throw_if_canceled();
|
m_print->throw_if_canceled();
|
||||||
}
|
}
|
||||||
|
// Belt brim rides here rather than in the brim step because its apron
|
||||||
|
// prologue introduces print_z values below the object's first layer, and
|
||||||
|
// those must exist before ToolOrdering is built at psWipeTower - one step
|
||||||
|
// ahead of psSkirtBrim. The brim options already invalidate
|
||||||
|
// posSupportMaterial, so this needs no extra invalidation edges.
|
||||||
|
make_belt_brim(*this);
|
||||||
|
m_print->throw_if_canceled();
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "[BELTRACE] generate_support_material EXIT tid=" << std::this_thread::get_id() << " obj=" << this;
|
||||||
this->set_done(posSupportMaterial);
|
this->set_done(posSupportMaterial);
|
||||||
|
} else {
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "[BELTRACE] generate_support_material SKIP tid=" << std::this_thread::get_id() << " obj=" << this << " (already started/done)";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1124,6 +1145,9 @@ void PrintObject::clear_layers()
|
|||||||
for (Layer *l : m_layers)
|
for (Layer *l : m_layers)
|
||||||
delete l;
|
delete l;
|
||||||
m_layers.clear();
|
m_layers.clear();
|
||||||
|
for (Layer *l : m_belt_truncated_layers)
|
||||||
|
delete l;
|
||||||
|
m_belt_truncated_layers.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1157,6 +1181,95 @@ void PrintObject::clear_support_layers()
|
|||||||
l->cantilevers.clear();
|
l->cantilevers.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Belt brim is owned by the same step, so it must die with it or an
|
||||||
|
// invalidate-without-rerun would leave stale bands (and stale prologue Zs)
|
||||||
|
// behind. Unconditional: unlike support layers it is never shared.
|
||||||
|
this->clear_belt_brim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Belt brim ------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// The tilt test is answered from the print CONFIG, not from SlicingParameters:
|
||||||
|
// invalidating posSupportMaterial clears m_slicing_params.valid, and this is
|
||||||
|
// queried from Print::process() dispatch, Brim.cpp and the G-code emitter, where
|
||||||
|
// a stale zero shear factor would silently drop the brim. BeltBrim.cpp itself
|
||||||
|
// reads the real belt floor through BeltFloorContext, where the parameters are
|
||||||
|
// guaranteed current.
|
||||||
|
bool PrintObject::has_belt_brim() const
|
||||||
|
{
|
||||||
|
if (! m_print->has_tilted_belt())
|
||||||
|
return false;
|
||||||
|
if (! this->belt_brim_instances_compatible())
|
||||||
|
return false;
|
||||||
|
if (m_config.brim_type == btNoBrim)
|
||||||
|
return false;
|
||||||
|
// An inner-only brim has no leading/extra geometry: leading_brim_length and
|
||||||
|
// extra_brim_width both widen the OUTER ring, which btInnerOnly never emits, so it
|
||||||
|
// produces nothing unless brim_width itself is positive. Every other brim type is
|
||||||
|
// satisfied by any one of the three widths. Requiring the width here (instead of
|
||||||
|
// "any width") stops has_belt_brim() - and therefore Print::validate() - from
|
||||||
|
// rejecting the prime tower / spiral vase for a brim that would never be drawn.
|
||||||
|
if (m_config.brim_type == btInnerOnly) {
|
||||||
|
if (m_config.brim_width.value <= 0.)
|
||||||
|
return false;
|
||||||
|
} else if (m_config.brim_width.value <= 0. && m_config.leading_brim_length.value <= 0.
|
||||||
|
&& m_config.extra_brim_width.value <= 0.) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return ! this->has_raft();
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int PrintObject::belt_brim_filament() const
|
||||||
|
{
|
||||||
|
// 1-based, matching PrintRegion::outer_wall_filament_id and the raw values pushed
|
||||||
|
// into LayerTools::extruders in ToolOrdering::collect_extruders (the whole list is
|
||||||
|
// reindexed to 0-based later). Lowest positive outer-wall filament over the
|
||||||
|
// printing regions; 1 when none is explicitly set.
|
||||||
|
unsigned int brim_filament = 0;
|
||||||
|
for (size_t i = 0; i < this->num_printing_regions(); ++ i) {
|
||||||
|
const unsigned int f = this->printing_region(i).config().outer_wall_filament_id.value;
|
||||||
|
if (f > 0 && (brim_filament == 0 || f < brim_filament))
|
||||||
|
brim_filament = f;
|
||||||
|
}
|
||||||
|
return brim_filament == 0 ? 1u : brim_filament;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PrintObject::belt_brim_instances_compatible() const
|
||||||
|
{
|
||||||
|
// One set of bands is shared by every instance of this object, so they must all sit at
|
||||||
|
// the same height on the belt. Moving an instance ALONG the belt axis changes its
|
||||||
|
// physical belt-floor Z and would put its brim at the wrong height; moving it ACROSS
|
||||||
|
// the belt does not, so side-by-side copies are fine.
|
||||||
|
//
|
||||||
|
// belt_force_separate() in PrintApply.cpp already gives one instance per PrintObject
|
||||||
|
// whenever a global belt flag is set, which the shipped belt profiles do - this only
|
||||||
|
// matters for configurations that do not.
|
||||||
|
if (m_instances.size() <= 1)
|
||||||
|
return true;
|
||||||
|
const int axis = m_slicing_params.belt_floor_from_axis;
|
||||||
|
const Point &ref = m_instances.front().shift;
|
||||||
|
for (const PrintInstance &inst : m_instances) {
|
||||||
|
const coord_t along = axis == 0 ? inst.shift.x() - ref.x() : inst.shift.y() - ref.y();
|
||||||
|
if (std::abs(along) > SCALED_EPSILON)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintObject::clear_belt_brim()
|
||||||
|
{
|
||||||
|
m_belt_brim_by_layer.clear();
|
||||||
|
m_belt_brim_areas_by_layer.clear();
|
||||||
|
m_belt_brim_prologue.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintObject::set_belt_brim(std::vector<ExtrusionEntityCollection> &&by_layer,
|
||||||
|
std::vector<ExPolygons> &&areas,
|
||||||
|
std::vector<BeltBrimBand> &&prologue)
|
||||||
|
{
|
||||||
|
m_belt_brim_by_layer = std::move(by_layer);
|
||||||
|
m_belt_brim_areas_by_layer = std::move(areas);
|
||||||
|
m_belt_brim_prologue = std::move(prologue);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<TreeSupportData> PrintObject::alloc_tree_support_preview_cache()
|
std::shared_ptr<TreeSupportData> PrintObject::alloc_tree_support_preview_cache()
|
||||||
@@ -1199,6 +1312,8 @@ bool PrintObject::invalidate_state_by_config_options(
|
|||||||
bool invalidated = false;
|
bool invalidated = false;
|
||||||
for (const t_config_option_key &opt_key : opt_keys) {
|
for (const t_config_option_key &opt_key : opt_keys) {
|
||||||
if ( opt_key == "brim_width"
|
if ( opt_key == "brim_width"
|
||||||
|
|| opt_key == "leading_brim_length"
|
||||||
|
|| opt_key == "extra_brim_width"
|
||||||
|| opt_key == "brim_object_gap"
|
|| opt_key == "brim_object_gap"
|
||||||
|| opt_key == "brim_use_efc_outline"
|
|| opt_key == "brim_use_efc_outline"
|
||||||
|| opt_key == "brim_type"
|
|| opt_key == "brim_type"
|
||||||
@@ -1580,7 +1695,8 @@ bool PrintObject::invalidate_state_by_config_options(
|
|||||||
} else if (
|
} else if (
|
||||||
opt_key == "flush_into_infill"
|
opt_key == "flush_into_infill"
|
||||||
|| opt_key == "flush_into_objects"
|
|| opt_key == "flush_into_objects"
|
||||||
|| opt_key == "flush_into_support") {
|
|| opt_key == "flush_into_support"
|
||||||
|
|| opt_key == "belt_purge_tower_object") {
|
||||||
invalidated |= m_print->invalidate_step(psWipeTower);
|
invalidated |= m_print->invalidate_step(psWipeTower);
|
||||||
invalidated |= m_print->invalidate_step(psGCodeExport);
|
invalidated |= m_print->invalidate_step(psGCodeExport);
|
||||||
} else {
|
} else {
|
||||||
@@ -1613,9 +1729,15 @@ bool PrintObject::invalidate_step(PrintObjectStep step)
|
|||||||
invalidated |= this->invalidate_steps({ posPerimeters, posPrepareInfill, posInfill, posIroning, posContouring, posSupportMaterial, posSimplifyPath, posSimplifyInfill });
|
invalidated |= this->invalidate_steps({ posPerimeters, posPrepareInfill, posInfill, posIroning, posContouring, posSupportMaterial, posSimplifyPath, posSimplifyInfill });
|
||||||
invalidated |= m_print->invalidate_steps({ psSkirtBrim });
|
invalidated |= m_print->invalidate_steps({ psSkirtBrim });
|
||||||
m_slicing_params.valid = false;
|
m_slicing_params.valid = false;
|
||||||
|
// The exact belt_floor_z_shift is recomputed when slice() runs again.
|
||||||
|
m_belt_floor_z_shift_cache_valid = false;
|
||||||
} else if (step == posSupportMaterial) {
|
} else if (step == posSupportMaterial) {
|
||||||
invalidated |= this->invalidate_steps({ posSimplifySupportPath });
|
invalidated |= this->invalidate_steps({ posSimplifySupportPath });
|
||||||
invalidated |= m_print->invalidate_steps({ psSkirtBrim });
|
invalidated |= m_print->invalidate_steps({ psSkirtBrim });
|
||||||
|
// SlicingParameters depend on support config (enable_support /
|
||||||
|
// raft_layers / enforce_support_layers feed min/max layer height in
|
||||||
|
// Slicing.cpp), so invalidate them here. The vertex-scan
|
||||||
|
// belt_floor_z_shift is preserved via m_belt_floor_z_shift_cached.
|
||||||
m_slicing_params.valid = false;
|
m_slicing_params.valid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1636,6 +1758,7 @@ bool PrintObject::invalidate_all_steps()
|
|||||||
bool result = inherited_invalidated || print_invalidated;
|
bool result = inherited_invalidated || print_invalidated;
|
||||||
// Then reset some of the depending values.
|
// Then reset some of the depending values.
|
||||||
m_slicing_params.valid = false;
|
m_slicing_params.valid = false;
|
||||||
|
m_belt_floor_z_shift_cache_valid = false;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3973,8 +4096,28 @@ void PrintObject::update_slicing_parameters()
|
|||||||
{
|
{
|
||||||
// Orca: updated function call for XYZ shrinkage compensation
|
// Orca: updated function call for XYZ shrinkage compensation
|
||||||
if (!m_slicing_params.valid) {
|
if (!m_slicing_params.valid) {
|
||||||
m_slicing_params = SlicingParameters::create_from_config(this->print()->config(), m_config, this->model_object()->max_z(),
|
coordf_t object_height = this->model_object()->max_z();
|
||||||
|
BeltTransformPipeline::BeltFloorParams belt_floor;
|
||||||
|
const auto &pcfg = this->print()->config();
|
||||||
|
if (pcfg.belt_printer.value) {
|
||||||
|
BoundingBoxf3 bb = BeltTransformPipeline::remap_bbox(*this->model_object(), pcfg);
|
||||||
|
if (BeltTransformPipeline::has_preslice_remap(pcfg))
|
||||||
|
object_height = bb.size().z();
|
||||||
|
auto hr = BeltTransformPipeline::compute_belt_height_and_floor(pcfg, bb, object_height);
|
||||||
|
object_height = hr.object_height;
|
||||||
|
belt_floor = hr.floor_params;
|
||||||
|
}
|
||||||
|
m_slicing_params = SlicingParameters::create_from_config(pcfg, m_config, object_height,
|
||||||
this->object_extruders(), this->print()->shrinkage_compensation());
|
this->object_extruders(), this->print()->shrinkage_compensation());
|
||||||
|
// Populate belt floor parameters into slicing params for support clipping.
|
||||||
|
m_slicing_params.belt_floor_shear_factor = belt_floor.shear_factor;
|
||||||
|
m_slicing_params.belt_floor_from_axis = belt_floor.from_axis;
|
||||||
|
m_slicing_params.belt_floor_z_shift = belt_floor.z_shift;
|
||||||
|
// Prefer the vertex-scan z_shift over the bbox approximation when
|
||||||
|
// slice() has already produced one (e.g. this rebuild was triggered
|
||||||
|
// by a support-config change, which doesn't move the belt floor).
|
||||||
|
if (m_belt_floor_z_shift_cache_valid)
|
||||||
|
m_slicing_params.belt_floor_z_shift = m_belt_floor_z_shift_cached;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4015,9 +4158,24 @@ SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full
|
|||||||
sort_remove_duplicates(object_extruders);
|
sort_remove_duplicates(object_extruders);
|
||||||
//FIXME add painting extruders
|
//FIXME add painting extruders
|
||||||
|
|
||||||
if (object_max_z <= 0.f)
|
BeltTransformPipeline::BeltFloorParams belt_floor;
|
||||||
object_max_z = (float)model_object.raw_bounding_box().size().z();
|
if (object_max_z <= 0.f) {
|
||||||
return SlicingParameters::create_from_config(print_config, object_config, object_max_z, object_extruders, object_shrinkage_compensation);
|
BoundingBoxf3 bb = model_object.raw_bounding_box();
|
||||||
|
object_max_z = (float)bb.size().z();
|
||||||
|
if (print_config.belt_printer.value) {
|
||||||
|
bb = BeltTransformPipeline::remap_bbox(model_object, print_config);
|
||||||
|
if (BeltTransformPipeline::has_preslice_remap(print_config))
|
||||||
|
object_max_z = (float)bb.size().z();
|
||||||
|
auto hr = BeltTransformPipeline::compute_belt_height_and_floor(print_config, bb, object_max_z);
|
||||||
|
object_max_z = (float)hr.object_height;
|
||||||
|
belt_floor = hr.floor_params;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SlicingParameters params = SlicingParameters::create_from_config(print_config, object_config, object_max_z, object_extruders, object_shrinkage_compensation);
|
||||||
|
params.belt_floor_shear_factor = belt_floor.shear_factor;
|
||||||
|
params.belt_floor_from_axis = belt_floor.from_axis;
|
||||||
|
params.belt_floor_z_shift = belt_floor.z_shift;
|
||||||
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns 0-based indices of extruders used to print the object (without brim, support and other helper extrusions)
|
// returns 0-based indices of extruders used to print the object (without brim, support and other helper extrusions)
|
||||||
@@ -4526,6 +4684,67 @@ void PrintObject::combine_infill()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Belt printer: clip an ExtrusionEntityCollection to a region defined by clip_expoly.
|
||||||
|
// Handles ExtrusionPath, ExtrusionMultiPath, ExtrusionLoop, and nested ExtrusionEntityCollection.
|
||||||
|
static void clip_support_fills(ExtrusionEntityCollection &fills, const ExPolygons &clip_region)
|
||||||
|
{
|
||||||
|
ExtrusionEntitiesPtr new_entities;
|
||||||
|
for (ExtrusionEntity *entity : fills.entities) {
|
||||||
|
if (auto *path = dynamic_cast<ExtrusionPath *>(entity)) {
|
||||||
|
ExtrusionEntityCollection clipped;
|
||||||
|
path->intersect_expolygons(clip_region, &clipped);
|
||||||
|
if (!clipped.empty()) {
|
||||||
|
for (ExtrusionEntity *e : clipped.entities)
|
||||||
|
new_entities.push_back(e->clone());
|
||||||
|
}
|
||||||
|
delete entity;
|
||||||
|
} else if (auto *multipath = dynamic_cast<ExtrusionMultiPath *>(entity)) {
|
||||||
|
ExtrusionPaths new_paths;
|
||||||
|
for (const ExtrusionPath &p : multipath->paths) {
|
||||||
|
ExtrusionEntityCollection clipped;
|
||||||
|
p.intersect_expolygons(clip_region, &clipped);
|
||||||
|
for (ExtrusionEntity *e : clipped.entities)
|
||||||
|
if (auto *cp = dynamic_cast<ExtrusionPath *>(e))
|
||||||
|
new_paths.push_back(std::move(*cp));
|
||||||
|
}
|
||||||
|
if (!new_paths.empty()) {
|
||||||
|
multipath->paths = std::move(new_paths);
|
||||||
|
new_entities.push_back(multipath);
|
||||||
|
} else {
|
||||||
|
delete entity;
|
||||||
|
}
|
||||||
|
} else if (auto *loop = dynamic_cast<ExtrusionLoop *>(entity)) {
|
||||||
|
ExtrusionPaths new_paths;
|
||||||
|
for (const ExtrusionPath &p : loop->paths) {
|
||||||
|
ExtrusionEntityCollection clipped;
|
||||||
|
p.intersect_expolygons(clip_region, &clipped);
|
||||||
|
for (ExtrusionEntity *e : clipped.entities)
|
||||||
|
if (auto *cp = dynamic_cast<ExtrusionPath *>(e))
|
||||||
|
new_paths.push_back(std::move(*cp));
|
||||||
|
}
|
||||||
|
if (!new_paths.empty()) {
|
||||||
|
// Loop is no longer a closed loop after clipping; emit as individual paths.
|
||||||
|
for (auto &p : new_paths)
|
||||||
|
new_entities.push_back(new ExtrusionPath(std::move(p)));
|
||||||
|
delete entity;
|
||||||
|
} else {
|
||||||
|
delete entity;
|
||||||
|
}
|
||||||
|
} else if (auto *coll = dynamic_cast<ExtrusionEntityCollection *>(entity)) {
|
||||||
|
clip_support_fills(*coll, clip_region);
|
||||||
|
if (!coll->empty()) {
|
||||||
|
new_entities.push_back(coll);
|
||||||
|
} else {
|
||||||
|
delete entity;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Unknown entity type — keep as-is.
|
||||||
|
new_entities.push_back(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fills.entities = std::move(new_entities);
|
||||||
|
}
|
||||||
|
|
||||||
void PrintObject::_generate_support_material()
|
void PrintObject::_generate_support_material()
|
||||||
{
|
{
|
||||||
if (is_tree(m_config.support_type.value)) {
|
if (is_tree(m_config.support_type.value)) {
|
||||||
@@ -4537,6 +4756,25 @@ void PrintObject::_generate_support_material()
|
|||||||
PrintObjectSupportMaterial support_material(this, m_slicing_params);
|
PrintObjectSupportMaterial support_material(this, m_slicing_params);
|
||||||
support_material.generate(*this);
|
support_material.generate(*this);
|
||||||
}
|
}
|
||||||
|
// Global Z offset for support layers:
|
||||||
|
// - Normal support: layers already inherit global_z_offset from object layers.
|
||||||
|
// - Non-organic tree support (slim/strong/hybrid): plan_layer_heights() reads
|
||||||
|
// from globally-offset object layers, so support layers already have it.
|
||||||
|
// - Organic tree support: generate_tree_support_3D() computes its own Z values
|
||||||
|
// independently and does NOT inherit the offset — apply it here.
|
||||||
|
// Belt floor polygon clipping for non-organic tree support is done inside
|
||||||
|
// draw_circles() before area_groups and toolpaths are built.
|
||||||
|
if (is_tree(m_config.support_type.value) && std::abs(m_belt_global_z_offset) > EPSILON) {
|
||||||
|
// Resolve effective support style (same logic as SupportParameters).
|
||||||
|
auto style = m_config.support_style.value;
|
||||||
|
if (style == smsDefault)
|
||||||
|
style = smsTreeOrganic;
|
||||||
|
if (style == smsTreeOrganic) {
|
||||||
|
for (SupportLayer *sl : m_support_layers)
|
||||||
|
sl->print_z += m_belt_global_z_offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BBS
|
// BBS
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
|
#include <limits>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
#include <tbb/parallel_for.h>
|
#include <tbb/parallel_for.h>
|
||||||
|
|
||||||
@@ -9,6 +11,9 @@
|
|||||||
#include "Layer.hpp"
|
#include "Layer.hpp"
|
||||||
#include "MultiMaterialSegmentation.hpp"
|
#include "MultiMaterialSegmentation.hpp"
|
||||||
#include "Print.hpp"
|
#include "Print.hpp"
|
||||||
|
#include "BeltTransform.hpp"
|
||||||
|
#include "BeltSliceStrategy.hpp"
|
||||||
|
#include "Geometry.hpp"
|
||||||
//BBS
|
//BBS
|
||||||
#include "ShortestPath.hpp"
|
#include "ShortestPath.hpp"
|
||||||
#include "libslic3r/Feature/Interlocking/InterlockingGenerator.hpp"
|
#include "libslic3r/Feature/Interlocking/InterlockingGenerator.hpp"
|
||||||
@@ -152,7 +157,8 @@ static std::vector<VolumeSlices> slice_volumes_inner(
|
|||||||
ModelVolumePtrs model_volumes,
|
ModelVolumePtrs model_volumes,
|
||||||
const std::vector<PrintObjectRegions::LayerRangeRegions> &layer_ranges,
|
const std::vector<PrintObjectRegions::LayerRangeRegions> &layer_ranges,
|
||||||
const std::vector<float> &zs,
|
const std::vector<float> &zs,
|
||||||
const std::function<void()> &throw_on_cancel_callback)
|
const std::function<void()> &throw_on_cancel_callback,
|
||||||
|
double *out_belt_min_z = nullptr)
|
||||||
{
|
{
|
||||||
model_volumes_sort_by_id(model_volumes);
|
model_volumes_sort_by_id(model_volumes);
|
||||||
|
|
||||||
@@ -167,6 +173,10 @@ static std::vector<VolumeSlices> slice_volumes_inner(
|
|||||||
params_base.closing_radius = print_object_config.slice_closing_radius.value;
|
params_base.closing_radius = print_object_config.slice_closing_radius.value;
|
||||||
params_base.extra_offset = 0;
|
params_base.extra_offset = 0;
|
||||||
params_base.trafo = object_trafo;
|
params_base.trafo = object_trafo;
|
||||||
|
// Pre-slice mesh transforms: axis remap (standalone — works without belt
|
||||||
|
// mode), belt rotation, and the per-object Z-shift. Owned by BeltSliceStrategy
|
||||||
|
// so this belt/remap-specific logic stays out of the generic slicing pipeline.
|
||||||
|
BeltSliceStrategy::apply_preslice_transforms(params_base.trafo, print_config, model_volumes, out_belt_min_z);
|
||||||
//BBS: 0.0025mm is safe enough to simplify the data to speed slicing up for high-resolution model.
|
//BBS: 0.0025mm is safe enough to simplify the data to speed slicing up for high-resolution model.
|
||||||
//Also has on influence on arc fitting which has default resolution 0.0125mm.
|
//Also has on influence on arc fitting which has default resolution 0.0125mm.
|
||||||
params_base.resolution = print_config.resolution <= 0.001 ? 0.0f : 0.0025;
|
params_base.resolution = print_config.resolution <= 0.001 ? 0.0f : 0.0025;
|
||||||
@@ -300,14 +310,39 @@ static std::vector<std::vector<ExPolygons>> slices_to_regions(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
zs_complex.reserve(zs.size());
|
zs_complex.reserve(zs.size());
|
||||||
|
// region.bbox is computed in pre-belt-transform slicer space (see PrintApply.cpp::trafo_for_bbox).
|
||||||
|
// When belt transforms are active, layer Z values are in post-rotation/shear/scale/remap space,
|
||||||
|
// so the Z components of region.bbox aren't comparable to z. Skipping the Z filter here
|
||||||
|
// pushes those layers into the parallel_for path below, which handles multi-volume
|
||||||
|
// clipping per layer without relying on the bbox Z range.
|
||||||
|
const bool bbox_z_in_layer_frame = !(print_config.belt_printer.value &&
|
||||||
|
(BeltTransformPipeline::has_rotation(print_config)
|
||||||
|
|| BeltTransformPipeline::has_preslice_remap(print_config)));
|
||||||
|
// Belt-transform addendum: with bbox-Z untrusted, the simple path's
|
||||||
|
// "first model_part wins" logic drops subsequent volumes' slices unless
|
||||||
|
// they XY-overlap with the first. Assemblies whose volumes are stacked
|
||||||
|
// or side-by-side in pre-transform Z (different bbox.z ranges) thus lose
|
||||||
|
// the volumes that originally sat outside the first volume's Z range —
|
||||||
|
// showing up as truncation at the top or bottom of the assembly. Force
|
||||||
|
// every layer in a multi-volume range through the parallel_for path,
|
||||||
|
// which correctly merges all volumes per layer.
|
||||||
|
int num_model_parts = 0;
|
||||||
|
for (const PrintObjectRegions::VolumeRegion &vr : layer_range.volume_regions)
|
||||||
|
if (vr.model_volume->is_model_part())
|
||||||
|
++num_model_parts;
|
||||||
|
const bool force_complex_for_belt = !bbox_z_in_layer_frame && num_model_parts > 1;
|
||||||
for (; z_idx < zs.size() && zs[z_idx] < layer_range.layer_height_range.second; ++ z_idx) {
|
for (; z_idx < zs.size() && zs[z_idx] < layer_range.layer_height_range.second; ++ z_idx) {
|
||||||
float z = zs[z_idx];
|
float z = zs[z_idx];
|
||||||
|
if (force_complex_for_belt) {
|
||||||
|
zs_complex.push_back({ z_idx, z });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
int idx_first_printable_region = -1;
|
int idx_first_printable_region = -1;
|
||||||
bool complex = false;
|
bool complex = false;
|
||||||
std::vector<int> printable_region_ids;
|
std::vector<int> printable_region_ids;
|
||||||
for (int idx_region = 0; idx_region < int(layer_range.volume_regions.size()); ++ idx_region) {
|
for (int idx_region = 0; idx_region < int(layer_range.volume_regions.size()); ++ idx_region) {
|
||||||
const PrintObjectRegions::VolumeRegion ®ion = layer_range.volume_regions[idx_region];
|
const PrintObjectRegions::VolumeRegion ®ion = layer_range.volume_regions[idx_region];
|
||||||
if (region.bbox->min().z() <= z && region.bbox->max().z() >= z) {
|
if (!bbox_z_in_layer_frame || (region.bbox->min().z() <= z && region.bbox->max().z() >= z)) {
|
||||||
if (region.model_volume->is_model_part())
|
if (region.model_volume->is_model_part())
|
||||||
printable_region_ids.push_back(idx_region);
|
printable_region_ids.push_back(idx_region);
|
||||||
|
|
||||||
@@ -318,7 +353,9 @@ static std::vector<std::vector<ExPolygons>> slices_to_regions(
|
|||||||
// Test for overlap with some other region.
|
// Test for overlap with some other region.
|
||||||
for (int idx_region2 = idx_first_printable_region; idx_region2 < idx_region; ++ idx_region2) {
|
for (int idx_region2 = idx_first_printable_region; idx_region2 < idx_region; ++ idx_region2) {
|
||||||
const PrintObjectRegions::VolumeRegion ®ion2 = layer_range.volume_regions[idx_region2];
|
const PrintObjectRegions::VolumeRegion ®ion2 = layer_range.volume_regions[idx_region2];
|
||||||
if (region2.bbox->min().z() <= z && region2.bbox->max().z() >= z && overlap_in_xy(*region.bbox, *region2.bbox)) {
|
const bool region2_in_z = !bbox_z_in_layer_frame
|
||||||
|
|| (region2.bbox->min().z() <= z && region2.bbox->max().z() >= z);
|
||||||
|
if (region2_in_z && overlap_in_xy(*region.bbox, *region2.bbox)) {
|
||||||
complex = true;
|
complex = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -823,8 +860,12 @@ void groupingVolumesForBrim(PrintObject* object, LayerPtrs& layers, int firstLay
|
|||||||
// Resulting expolygons of layer regions are marked as Internal.
|
// Resulting expolygons of layer regions are marked as Internal.
|
||||||
void PrintObject::slice()
|
void PrintObject::slice()
|
||||||
{
|
{
|
||||||
if (! this->set_started(posSlice))
|
BOOST_LOG_TRIVIAL(trace) << "[BELTRACE] slice request tid=" << std::this_thread::get_id() << " obj=" << this;
|
||||||
|
if (! this->set_started(posSlice)) {
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "[BELTRACE] slice SKIP tid=" << std::this_thread::get_id() << " obj=" << this << " (already started/done)";
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "[BELTRACE] slice ENTER tid=" << std::this_thread::get_id() << " obj=" << this;
|
||||||
//BBS: add flag to reload scene for shell rendering
|
//BBS: add flag to reload scene for shell rendering
|
||||||
m_print->set_status(5, L("Slicing mesh"), PrintBase::SlicingStatus::RELOAD_SCENE);
|
m_print->set_status(5, L("Slicing mesh"), PrintBase::SlicingStatus::RELOAD_SCENE);
|
||||||
std::vector<coordf_t> layer_height_profile;
|
std::vector<coordf_t> layer_height_profile;
|
||||||
@@ -835,6 +876,34 @@ void PrintObject::slice()
|
|||||||
m_layers = new_layers(this, generate_object_layers(m_slicing_params, layer_height_profile, m_config.precise_z_height.value));
|
m_layers = new_layers(this, generate_object_layers(m_slicing_params, layer_height_profile, m_config.precise_z_height.value));
|
||||||
this->slice_volumes();
|
this->slice_volumes();
|
||||||
m_print->throw_if_canceled();
|
m_print->throw_if_canceled();
|
||||||
|
|
||||||
|
// Belt floor Z-shift: where is the belt surface in final slicer space?
|
||||||
|
//
|
||||||
|
// The belt surface is at model_Y=0 (XZ belt plane). After the full
|
||||||
|
// pipeline (trafo_centered → pre_remap → shear → z_shift), the belt
|
||||||
|
// surface equation in slicer space is:
|
||||||
|
// Z_belt = sf * from_axis + belt_surface_z_centered + z_shift_val
|
||||||
|
//
|
||||||
|
// belt_surface_z_centered = remapped_bbox.min.z() (the Z position of
|
||||||
|
// the belt surface in centered-pre-shear slicer space, which is 0
|
||||||
|
// without pre-remap but nonzero when e.g. Y↔Z swap shifts the belt
|
||||||
|
// surface away from Z=0 by the centering offset).
|
||||||
|
//
|
||||||
|
// z_shift_val = max(0, -m_belt_min_z) (lifts mesh above Z=0).
|
||||||
|
//
|
||||||
|
// So: belt_floor_z_shift = remapped_bb.min.z() + z_shift_val
|
||||||
|
if (std::abs(m_slicing_params.belt_floor_shear_factor) > EPSILON) {
|
||||||
|
double z_shift_val = (m_belt_min_z < 0.) ? -m_belt_min_z : 0.;
|
||||||
|
// With pre-remap, the belt surface (model_Y=0) may not be at Z=0 in
|
||||||
|
// centered slicer space — add the remapped bbox min Z to compensate.
|
||||||
|
// Without pre-remap, the belt surface IS at Z=0 and bb.min.z() is
|
||||||
|
// already folded into m_belt_min_z, so use 0.
|
||||||
|
const auto &pcfg = this->print()->config();
|
||||||
|
double belt_surface_z = BeltTransformPipeline::has_preslice_remap(pcfg)
|
||||||
|
? BeltTransformPipeline::remap_bbox(*this->model_object(), pcfg).min.z() : 0.;
|
||||||
|
m_slicing_params.belt_floor_z_shift = belt_surface_z + z_shift_val;
|
||||||
|
}
|
||||||
|
|
||||||
int firstLayerReplacedBy = 0;
|
int firstLayerReplacedBy = 0;
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
@@ -873,7 +942,193 @@ void PrintObject::slice()
|
|||||||
if (m_layers.empty())
|
if (m_layers.empty())
|
||||||
throw Slic3r::SlicingError(L("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n"));
|
throw Slic3r::SlicingError(L("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n"));
|
||||||
|
|
||||||
|
// Belt printer global mode: offset all layer Z values so objects at
|
||||||
|
// different bed positions print at different heights on the tilted belt.
|
||||||
|
// This is a post-slicing adjustment — the sliced geometry is identical
|
||||||
|
// regardless of global mode, only the output Z coordinates change.
|
||||||
|
{
|
||||||
|
const auto &pcfg = this->print()->config();
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "Belt global check: belt_printer=" << pcfg.belt_printer.value
|
||||||
|
<< " belt_slice_rotation=" << int(pcfg.belt_slice_rotation.value)
|
||||||
|
<< " belt_slice_rotation_global=" << pcfg.belt_slice_rotation_global.value
|
||||||
|
<< " belt_preslice_global=" << pcfg.belt_preslice_global.value
|
||||||
|
<< " object=" << this->model_object()->name;
|
||||||
|
if (pcfg.belt_printer.value) {
|
||||||
|
|
||||||
|
Point inst_shift = this->instances().empty() ? Point(0, 0)
|
||||||
|
: this->instances().front().shift - this->center_offset();
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "Belt global: object " << this->model_object()->name
|
||||||
|
<< " instances=" << this->instances().size()
|
||||||
|
<< " shift=(" << unscale<double>(inst_shift.x()) << ", " << unscale<double>(inst_shift.y()) << ")";
|
||||||
|
|
||||||
|
// Per-object Z-shift compensation, applied regardless of global mode.
|
||||||
|
//
|
||||||
|
// BeltSliceStrategy::apply_preslice_transforms lifts the mesh by max(0, -m_belt_min_z)
|
||||||
|
// so the slicer can slice with slicer_z >= 0. BeltBackTransform inverts
|
||||||
|
// build_forward_transform() which DOES NOT include this per-object
|
||||||
|
// Z-shift (it's not known until vertex scan time). Result: G-code
|
||||||
|
// coords emerge offset by the un-undone Z-shift — the inverse rotation
|
||||||
|
// couples slicer_z back into both machine_y and machine_z. Compensating
|
||||||
|
// layer.print_z by belt_z_shift here makes the back-transform produce
|
||||||
|
// correct machine-frame coordinates whether or not a global mode is active.
|
||||||
|
double belt_surface_z = BeltTransformPipeline::has_preslice_remap(pcfg)
|
||||||
|
? BeltTransformPipeline::remap_bbox(*this->model_object(), pcfg).min.z() : 0.;
|
||||||
|
// The compensation must mirror the Z-shift actually applied, which
|
||||||
|
// is max(0, -m_belt_min_z): when the transformed mesh starts ABOVE
|
||||||
|
// slicer Z=0 (m_belt_min_z > 0 — possible for counter-rotated or
|
||||||
|
// asymmetric geometry whose centered-frame minimum lands positive)
|
||||||
|
// no lift was applied, and an unclamped m_belt_min_z here would
|
||||||
|
// leak straight into the layer Z values, floating the whole object
|
||||||
|
// off the belt by exactly that amount.
|
||||||
|
double belt_z_shift = std::min(m_belt_min_z, 0.) - belt_surface_z;
|
||||||
|
double global_z_offset = belt_z_shift;
|
||||||
|
|
||||||
|
// Centering correction: trafo_centered pretranslates by
|
||||||
|
// -m_center_offset.{x,y}. Under the belt forward transform, the
|
||||||
|
// Y component of that pretranslate couples into slicer-Z (shear:
|
||||||
|
// tan*c.y, rotation: sin*c.y). BeltBackTransform inverts the
|
||||||
|
// rotation/shear but doesn't undo centering, so this Z component
|
||||||
|
// leaks into machine output as a position offset whenever
|
||||||
|
// m_center_offset != 0. When a user moves a volume within an
|
||||||
|
// assembly such that the combined bbox center shifts, this shows
|
||||||
|
// up as a small Z translation in the print. Compensate by adding
|
||||||
|
// the Z component of the centering through the forward transform.
|
||||||
|
{
|
||||||
|
Transform3d T_fwd = BeltTransformPipeline::build_forward_transform(pcfg);
|
||||||
|
Vec3d c_off(unscale<double>(m_center_offset.x()),
|
||||||
|
unscale<double>(m_center_offset.y()),
|
||||||
|
0.);
|
||||||
|
double centering_z_corr = (T_fwd.linear() * c_off).z();
|
||||||
|
global_z_offset += centering_z_corr;
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "[BELT-DEBUG] centering correction"
|
||||||
|
<< " obj=" << this->model_object()->name
|
||||||
|
<< " m_center_offset_mm=(" << c_off.x() << "," << c_off.y() << ")"
|
||||||
|
<< " centering_z_corr=" << centering_z_corr
|
||||||
|
<< " (added to global_z_offset)";
|
||||||
|
}
|
||||||
|
|
||||||
|
// [BELT-DEBUG] Per-object summary so Case A vs Case B can be compared
|
||||||
|
// side-by-side. Lays out every value that feeds into the final layer
|
||||||
|
// print_z adjustment.
|
||||||
|
{
|
||||||
|
BoundingBoxf3 raw_bb = this->model_object()->raw_bounding_box();
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "[BELT-DEBUG] slice() per-object summary"
|
||||||
|
<< " obj=" << this->model_object()->name
|
||||||
|
<< " n_volumes=" << this->model_object()->volumes.size()
|
||||||
|
<< " raw_bbox.min=(" << raw_bb.min.x() << "," << raw_bb.min.y() << "," << raw_bb.min.z() << ")"
|
||||||
|
<< " raw_bbox.max=(" << raw_bb.max.x() << "," << raw_bb.max.y() << "," << raw_bb.max.z() << ")"
|
||||||
|
<< " raw_bbox.center=(" << raw_bb.center().x() << "," << raw_bb.center().y() << ")"
|
||||||
|
<< " m_center_offset=(" << unscale<double>(m_center_offset.x()) << "," << unscale<double>(m_center_offset.y()) << ")"
|
||||||
|
<< " inst_shift=(" << unscale<double>(inst_shift.x()) << "," << unscale<double>(inst_shift.y()) << ")"
|
||||||
|
<< " m_belt_min_z=" << m_belt_min_z
|
||||||
|
<< " belt_surface_z=" << belt_surface_z
|
||||||
|
<< " belt_z_shift=" << belt_z_shift;
|
||||||
|
// Per-volume bbox + get_matrix translation so order/composition is visible.
|
||||||
|
int vi = 0;
|
||||||
|
for (const ModelVolume *mv : this->model_object()->volumes) {
|
||||||
|
if (!mv->is_model_part()) { ++vi; continue; }
|
||||||
|
BoundingBoxf3 vol_bb = mv->mesh().transformed_bounding_box(mv->get_matrix());
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "[BELT-DEBUG] vol[" << vi
|
||||||
|
<< "] id=" << mv->id().id << " name='" << mv->name << "'"
|
||||||
|
<< " get_matrix.translation=(" << mv->get_matrix().translation().x() << "," << mv->get_matrix().translation().y() << "," << mv->get_matrix().translation().z() << ")"
|
||||||
|
<< " object_bbox.min=(" << vol_bb.min.x() << "," << vol_bb.min.y() << "," << vol_bb.min.z() << ")"
|
||||||
|
<< " object_bbox.max=(" << vol_bb.max.x() << "," << vol_bb.max.y() << "," << vol_bb.max.z() << ")";
|
||||||
|
++vi;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pcfg.belt_preslice_global.value) {
|
||||||
|
// Global pre-slice mode: compute full correction c = (T.linear() - I) * d
|
||||||
|
// where T is the belt forward transform and d is the bed position.
|
||||||
|
Transform3d T = BeltTransformPipeline::build_forward_transform(pcfg);
|
||||||
|
Vec3d d(unscale<double>(inst_shift.x()), unscale<double>(inst_shift.y()), 0.);
|
||||||
|
Vec3d c = T.linear() * d - d;
|
||||||
|
global_z_offset += c.z();
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "[BELTRACE] write m_belt_global_xy_correction tid=" << std::this_thread::get_id()
|
||||||
|
<< " obj=" << this << " old=(" << m_belt_global_xy_correction.x() << "," << m_belt_global_xy_correction.y()
|
||||||
|
<< ") new=(" << c.x() << "," << c.y() << ")";
|
||||||
|
m_belt_global_xy_correction = Vec2d(c.x(), c.y());
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "Belt preslice_global: correction=("
|
||||||
|
<< c.x() << ", " << c.y() << ", " << c.z() << ")"
|
||||||
|
<< " belt_z_shift=" << belt_z_shift << " (m_belt_min_z=" << m_belt_min_z << ")";
|
||||||
|
} else {
|
||||||
|
// Slicing rotation in global mode: bed-position-dependent Z offset.
|
||||||
|
// For R(α, X): c.z = sin(α)*d.y so objects at different bed-Y
|
||||||
|
// values print at different machine Z values along the inclined belt.
|
||||||
|
if (pcfg.belt_slice_rotation_global.value
|
||||||
|
&& pcfg.belt_slice_rotation.value != BeltRotationAxis::None
|
||||||
|
&& std::abs(pcfg.belt_slice_rotation_angle.value) > EPSILON) {
|
||||||
|
Transform3d T = BeltTransformPipeline::build_forward_transform(pcfg);
|
||||||
|
Vec3d d(unscale<double>(inst_shift.x()), unscale<double>(inst_shift.y()), 0.);
|
||||||
|
Vec3d c = T.linear() * d - d;
|
||||||
|
global_z_offset += c.z();
|
||||||
|
m_belt_global_xy_correction = Vec2d(c.x(), c.y());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre-slice remap global mode: when on, the remap accounts for the
|
||||||
|
// instance bed position. The Z component of the correction
|
||||||
|
// (R - I) * d shifts layer print_z so e.g. a Y↔Z swap with an
|
||||||
|
// object at Y=50 prints at Z=50.
|
||||||
|
if (pcfg.preslice_remap_global.value
|
||||||
|
&& BeltTransformPipeline::has_preslice_remap(pcfg)) {
|
||||||
|
Transform3d R = BeltTransformPipeline::build_preslice_remap(pcfg);
|
||||||
|
Vec3d d(unscale<double>(inst_shift.x()), unscale<double>(inst_shift.y()), 0.);
|
||||||
|
Vec3d remap_correction = R.linear() * d - d;
|
||||||
|
global_z_offset += remap_correction.z();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "Belt global: z_offset=" << global_z_offset
|
||||||
|
<< " (relative to min across " << this->print()->objects().size() << " objects)";
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "[BELTRACE] write m_belt_global_z_offset tid=" << std::this_thread::get_id()
|
||||||
|
<< " obj=" << this << " old=" << m_belt_global_z_offset << " new=" << global_z_offset;
|
||||||
|
m_belt_global_z_offset = global_z_offset;
|
||||||
|
// [BELT-DEBUG] Final breakdown of all contributions to layer.print_z
|
||||||
|
// and where the first / last layer end up post-adjustment.
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "[BELT-DEBUG] global_z_offset breakdown"
|
||||||
|
<< " obj=" << this->model_object()->name
|
||||||
|
<< " belt_z_shift=" << belt_z_shift
|
||||||
|
<< " total_global_z_offset=" << global_z_offset
|
||||||
|
<< " xy_correction=(" << m_belt_global_xy_correction.x() << "," << m_belt_global_xy_correction.y() << ")"
|
||||||
|
<< " belt_floor_z_shift_before=" << (m_slicing_params.belt_floor_z_shift)
|
||||||
|
<< " n_layers=" << m_layers.size();
|
||||||
|
if (std::abs(global_z_offset) > EPSILON) {
|
||||||
|
for (Layer *layer : m_layers)
|
||||||
|
layer->print_z += global_z_offset;
|
||||||
|
// Keep belt floor clipping in sync with the shifted print_z
|
||||||
|
// values — the support generator sees globally-offset object
|
||||||
|
// layer print_z, so belt_floor_z_shift must match.
|
||||||
|
m_slicing_params.belt_floor_z_shift += global_z_offset;
|
||||||
|
}
|
||||||
|
if (!m_layers.empty()) {
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "[BELT-DEBUG] post-adjustment"
|
||||||
|
<< " first_layer.print_z=" << m_layers.front()->print_z
|
||||||
|
<< " last_layer.print_z=" << m_layers.back()->print_z
|
||||||
|
<< " belt_floor_z_shift_after=" << m_slicing_params.belt_floor_z_shift;
|
||||||
|
}
|
||||||
|
if (!m_layers.empty()) {
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "Belt global: first_layer_z=" << m_layers.front()->print_z
|
||||||
|
<< " last_layer_z=" << m_layers.back()->print_z
|
||||||
|
<< " num_layers=" << m_layers.size()
|
||||||
|
<< " center_offset=(" << unscale<double>(m_center_offset.x())
|
||||||
|
<< ", " << unscale<double>(m_center_offset.y()) << ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache the final patched belt_floor_z_shift so a later support-only
|
||||||
|
// invalidation can rebuild m_slicing_params without losing this exact
|
||||||
|
// (vertex-scan-derived) value. update_slicing_parameters() will
|
||||||
|
// restore it after create_from_config() seeds the bbox approximation.
|
||||||
|
m_belt_floor_z_shift_cached = m_slicing_params.belt_floor_z_shift;
|
||||||
|
m_belt_floor_z_shift_cache_valid = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// BBS
|
// BBS
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "[BELTRACE] slice EXIT tid=" << std::this_thread::get_id() << " obj=" << this
|
||||||
|
<< " layers=" << m_layers.size() << " belt_min_z=" << m_belt_min_z
|
||||||
|
<< " belt_global_z_offset=" << m_belt_global_z_offset
|
||||||
|
<< " belt_xy=(" << m_belt_global_xy_correction.x() << "," << m_belt_global_xy_correction.y() << ")";
|
||||||
this->set_done(posSlice);
|
this->set_done(posSlice);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1144,6 +1399,7 @@ void apply_fuzzy_skin_segmentation(PrintObject &print_object, ThrowOnCancel thro
|
|||||||
}); // end of parallel_for
|
}); // end of parallel_for
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 1) Decides Z positions of the layers,
|
// 1) Decides Z positions of the layers,
|
||||||
// 2) Initializes layers and their regions
|
// 2) Initializes layers and their regions
|
||||||
// 3) Slices the object meshes
|
// 3) Slices the object meshes
|
||||||
@@ -1177,7 +1433,8 @@ void PrintObject::slice_volumes()
|
|||||||
if (!slice_zs.empty()) {
|
if (!slice_zs.empty()) {
|
||||||
objSliceByVolume = slice_volumes_inner(
|
objSliceByVolume = slice_volumes_inner(
|
||||||
print->config(), this->config(), this->trafo_centered(),
|
print->config(), this->config(), this->trafo_centered(),
|
||||||
this->model_object()->volumes, m_shared_regions->layer_ranges, slice_zs, throw_on_cancel_callback);
|
this->model_object()->volumes, m_shared_regions->layer_ranges, slice_zs, throw_on_cancel_callback,
|
||||||
|
&m_belt_min_z);
|
||||||
}
|
}
|
||||||
|
|
||||||
//BBS: "model_part" volumes are grouded according to their connections
|
//BBS: "model_part" volumes are grouded according to their connections
|
||||||
|
|||||||
@@ -111,6 +111,13 @@ struct SlicingParameters
|
|||||||
coordf_t object_print_z_uncompensated_max { 0 };
|
coordf_t object_print_z_uncompensated_max { 0 };
|
||||||
// Scaling factor for compensating shrinkage in Z-axis.
|
// Scaling factor for compensating shrinkage in Z-axis.
|
||||||
coordf_t object_shrinkage_compensation_z { 0 };
|
coordf_t object_shrinkage_compensation_z { 0 };
|
||||||
|
|
||||||
|
// Belt printer: floor plane parameters for support clipping.
|
||||||
|
// Belt contact surface in slicing coords: Z = bb_min_z + sf*Y + slicing_z_shift.
|
||||||
|
// cutoff = (print_z - belt_floor_z_shift - floor_offset) / shear_factor
|
||||||
|
double belt_floor_shear_factor { 0.0 }; // shear factor (e.g. cot(45deg))
|
||||||
|
int belt_floor_from_axis { 1 }; // which axis the shear is from (0=X, 1=Y)
|
||||||
|
double belt_floor_z_shift { 0.0 }; // bb_min_z + max(0, -min_z_after_shear)
|
||||||
};
|
};
|
||||||
static_assert(IsTriviallyCopyable<SlicingParameters>::value, "SlicingParameters class is not POD (and it should be - see constructor).");
|
static_assert(IsTriviallyCopyable<SlicingParameters>::value, "SlicingParameters class is not POD (and it should be - see constructor).");
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
#include "BeltFloorContext.hpp"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
bool BeltFloorContext::init(const SlicingParameters &sp, const PrintConfig &pcfg)
|
||||||
|
{
|
||||||
|
m_active = false;
|
||||||
|
m_shear_factor = sp.belt_floor_shear_factor;
|
||||||
|
m_from_axis = sp.belt_floor_from_axis;
|
||||||
|
m_z_shift = sp.belt_floor_z_shift;
|
||||||
|
m_floor_offset = pcfg.belt_support_floor_offset.value;
|
||||||
|
|
||||||
|
if (std::abs(m_shear_factor) < EPSILON)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
m_active = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BeltFloorContext::init_local(const SlicingParameters &sp, const PrintConfig &pcfg,
|
||||||
|
double global_z_offset)
|
||||||
|
{
|
||||||
|
if (!init(sp, pcfg))
|
||||||
|
return false;
|
||||||
|
// Local Z: subtract the global Z offset so polygon computation
|
||||||
|
// works in the object's local coordinate space.
|
||||||
|
m_z_shift -= global_z_offset;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Polygons BeltFloorContext::surface_polygon(coordf_t print_z) const
|
||||||
|
{
|
||||||
|
return half_plane(print_z, /*belt_surface=*/true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Polygons BeltFloorContext::valid_region_polygon(coordf_t print_z) const
|
||||||
|
{
|
||||||
|
return half_plane(print_z, /*belt_surface=*/false);
|
||||||
|
}
|
||||||
|
|
||||||
|
double BeltFloorContext::floor_print_z(const Point &pos_slicing) const
|
||||||
|
{
|
||||||
|
if (!m_active)
|
||||||
|
return -std::numeric_limits<double>::infinity();
|
||||||
|
double pos = unscale<double>(m_from_axis == 0 ? pos_slicing.x() : pos_slicing.y());
|
||||||
|
return m_shear_factor * pos + m_floor_offset + m_z_shift;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Polygons> BeltFloorContext::compute_per_layer_floors(
|
||||||
|
size_t num_layers,
|
||||||
|
const std::function<double(size_t)> &layer_print_z) const
|
||||||
|
{
|
||||||
|
std::vector<Polygons> result(num_layers);
|
||||||
|
if (!m_active)
|
||||||
|
return result;
|
||||||
|
for (size_t i = 0; i < num_layers; ++i)
|
||||||
|
result[i] = surface_polygon(layer_print_z(i));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Polygons BeltFloorContext::half_plane(coordf_t print_z, bool belt_surface) const
|
||||||
|
{
|
||||||
|
if (!m_active)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
const double cutoff = this->cutoff_u(print_z);
|
||||||
|
const coord_t cutoff_scaled = scale_(cutoff);
|
||||||
|
const coord_t large_bound = scale_(1e3);
|
||||||
|
|
||||||
|
// The belt surface is on one side of the cutoff line; the valid region
|
||||||
|
// is on the other side. Which side depends on shear_factor sign.
|
||||||
|
//
|
||||||
|
// belt_surface=true → the belt side (where support should NOT exist)
|
||||||
|
// belt_surface=false → the valid side (where support IS allowed)
|
||||||
|
//
|
||||||
|
// For shear_factor > 0: belt surface is from_axis >= cutoff
|
||||||
|
// For shear_factor < 0: belt surface is from_axis <= cutoff
|
||||||
|
bool high_side = (m_shear_factor > 0) == belt_surface;
|
||||||
|
|
||||||
|
Polygon poly;
|
||||||
|
if (m_from_axis == 0) {
|
||||||
|
if (high_side) {
|
||||||
|
// X >= cutoff
|
||||||
|
poly.points = {
|
||||||
|
Point(cutoff_scaled, -large_bound),
|
||||||
|
Point(large_bound, -large_bound),
|
||||||
|
Point(large_bound, large_bound),
|
||||||
|
Point(cutoff_scaled, large_bound)
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// X < cutoff
|
||||||
|
poly.points = {
|
||||||
|
Point(-large_bound, -large_bound),
|
||||||
|
Point(cutoff_scaled, -large_bound),
|
||||||
|
Point(cutoff_scaled, large_bound),
|
||||||
|
Point(-large_bound, large_bound)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (high_side) {
|
||||||
|
// Y >= cutoff
|
||||||
|
poly.points = {
|
||||||
|
Point(-large_bound, cutoff_scaled),
|
||||||
|
Point( large_bound, cutoff_scaled),
|
||||||
|
Point( large_bound, large_bound),
|
||||||
|
Point(-large_bound, large_bound)
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Y < cutoff
|
||||||
|
poly.points = {
|
||||||
|
Point(-large_bound, -large_bound),
|
||||||
|
Point( large_bound, -large_bound),
|
||||||
|
Point( large_bound, cutoff_scaled),
|
||||||
|
Point(-large_bound, cutoff_scaled)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { poly };
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../libslic3r.h"
|
||||||
|
#include "../Point.hpp"
|
||||||
|
#include "../Polygon.hpp"
|
||||||
|
#include "../Slicing.hpp"
|
||||||
|
#include "../PrintConfig.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
class PrintObject;
|
||||||
|
|
||||||
|
// Belt floor context: encapsulates the parameters and polygon computation
|
||||||
|
// for belt printer floor clipping in support generation.
|
||||||
|
//
|
||||||
|
// All belt floor code across SupportMaterial, TreeSupport, TreeSupport3D,
|
||||||
|
// and TreeModelVolumes uses the same 4 parameters and the same 4-case
|
||||||
|
// polygon construction. This class consolidates that logic.
|
||||||
|
//
|
||||||
|
// Construct once per PrintObject, then call surface_polygon() or
|
||||||
|
// valid_region_polygon() per layer with the layer's print_z.
|
||||||
|
class BeltFloorContext
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BeltFloorContext() = default;
|
||||||
|
|
||||||
|
// Initialize from slicing parameters and print config.
|
||||||
|
// Uses global Z coordinates (for SupportMaterial, non-organic TreeSupport).
|
||||||
|
bool init(const SlicingParameters &sp, const PrintConfig &pcfg);
|
||||||
|
|
||||||
|
// Initialize with a Z offset subtracted from z_shift.
|
||||||
|
// Uses local Z coordinates (for TreeSupport3D, TreeModelVolumes organic pipeline).
|
||||||
|
bool init_local(const SlicingParameters &sp, const PrintConfig &pcfg,
|
||||||
|
double global_z_offset);
|
||||||
|
|
||||||
|
bool is_active() const { return m_active; }
|
||||||
|
|
||||||
|
// Compute the belt-side half-plane polygon at a given print_z.
|
||||||
|
// This is the region where the belt surface exists.
|
||||||
|
Polygons surface_polygon(coordf_t print_z) const;
|
||||||
|
|
||||||
|
// Compute the valid-region half-plane polygon at a given print_z.
|
||||||
|
// This is the complement: the region where support is allowed.
|
||||||
|
Polygons valid_region_polygon(coordf_t print_z) const;
|
||||||
|
|
||||||
|
// Compute the belt floor Z position at a given XY position (in slicing coords).
|
||||||
|
// Returns -infinity if not active.
|
||||||
|
double floor_print_z(const Point &pos_slicing) const;
|
||||||
|
|
||||||
|
// The from_axis coordinate (unscaled, slicing frame) where the belt surface
|
||||||
|
// crosses a horizontal plane at print_z. Inverse of floor_print_z() along
|
||||||
|
// the shear axis. Only meaningful when is_active().
|
||||||
|
coordf_t cutoff_u(coordf_t print_z) const
|
||||||
|
{ return (print_z - m_z_shift - m_floor_offset) / m_shear_factor; }
|
||||||
|
|
||||||
|
// Pre-compute belt floor polygons for a range of layers.
|
||||||
|
// layer_print_z(i) returns the print_z for layer index i.
|
||||||
|
std::vector<Polygons> compute_per_layer_floors(
|
||||||
|
size_t num_layers,
|
||||||
|
const std::function<double(size_t)> &layer_print_z) const;
|
||||||
|
|
||||||
|
// Accessors
|
||||||
|
double shear_factor() const { return m_shear_factor; }
|
||||||
|
int from_axis() const { return m_from_axis; }
|
||||||
|
double z_shift() const { return m_z_shift; }
|
||||||
|
double floor_offset() const { return m_floor_offset; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool m_active = false;
|
||||||
|
double m_shear_factor = 0.0;
|
||||||
|
int m_from_axis = 1; // 0=X, 1=Y
|
||||||
|
double m_z_shift = 0.0;
|
||||||
|
double m_floor_offset = 0.0;
|
||||||
|
|
||||||
|
// Internal: compute the raw half-plane polygon.
|
||||||
|
// If belt_surface=true, returns the belt side; otherwise the valid (complement) side.
|
||||||
|
Polygons half_plane(coordf_t print_z, bool belt_surface) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
@@ -270,7 +270,9 @@ SupportGeneratorLayersPtr generate_raft_base(
|
|||||||
// The object does not have a raft.
|
// The object does not have a raft.
|
||||||
// Calculate the area covered by the brim.
|
// Calculate the area covered by the brim.
|
||||||
const BrimType brim_type = object.config().brim_type;
|
const BrimType brim_type = object.config().brim_type;
|
||||||
const bool brim_outer = brim_type == btOuterOnly || brim_type == btOuterAndInner;
|
// btLeadingEdgeOnly only means anything on a belt printer, where this code path
|
||||||
|
// does not run; elsewhere it degrades to an outer brim (see Brim.cpp).
|
||||||
|
const bool brim_outer = brim_type == btOuterOnly || brim_type == btOuterAndInner || brim_type == btLeadingEdgeOnly;
|
||||||
const bool brim_inner = brim_type == btInnerOnly || brim_type == btOuterAndInner;
|
const bool brim_inner = brim_type == btInnerOnly || brim_type == btOuterAndInner;
|
||||||
// BBS: the pattern of raft and brim are the same, thus the brim can be serpated by support raft.
|
// BBS: the pattern of raft and brim are the same, thus the brim can be serpated by support raft.
|
||||||
const auto brim_object_gap = scaled<float>(object.config().brim_object_gap.value);
|
const auto brim_object_gap = scaled<float>(object.config().brim_object_gap.value);
|
||||||
@@ -1802,7 +1804,14 @@ void generate_support_toolpaths(
|
|||||||
bool sheath = support_params.with_sheath;
|
bool sheath = support_params.with_sheath;
|
||||||
bool no_sort = false;
|
bool no_sort = false;
|
||||||
bool done = false;
|
bool done = false;
|
||||||
if (base_layer.layer->bottom_z < EPSILON) {
|
// Belt printers have no flat bed first layer — the belt is the tilted
|
||||||
|
// build surface — so the dense raft_first_layer_density flange must not
|
||||||
|
// fire anywhere, including the layer at z=0 (the belt-surface line).
|
||||||
|
// (belt_floor_shear_factor is non-zero only when belt_printer is on.)
|
||||||
|
// For every other printer type, support z is never negative, so this
|
||||||
|
// matches the original "first layer at z=0" behaviour unchanged.
|
||||||
|
const bool is_belt_printer = std::abs(slicing_params.belt_floor_shear_factor) > EPSILON;
|
||||||
|
if (! is_belt_printer && base_layer.layer->bottom_z < EPSILON) {
|
||||||
// Base flange (the 1st layer).
|
// Base flange (the 1st layer).
|
||||||
filler = filler_first_layer;
|
filler = filler_first_layer;
|
||||||
filler->angle = Geometry::deg2rad(float(config.support_angle.value + 90.));
|
filler->angle = Geometry::deg2rad(float(config.support_angle.value + 90.));
|
||||||
|
|||||||
@@ -144,6 +144,12 @@ int idx_lower_or_equal(const std::vector<T*> &vec, int idx, FN_LOWER_EQUAL fn_lo
|
|||||||
return idx_lower_or_equal(vec.begin(), vec.end(), idx, fn_lower_equal);
|
return idx_lower_or_equal(vec.begin(), vec.end(), idx, fn_lower_equal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Belt floor: compute the belt-side half-plane polygon at a given print_z.
|
||||||
|
// Used to clip support polygons against the belt surface.
|
||||||
|
Polygons belt_floor_surface_polygon(
|
||||||
|
const SlicingParameters &slicing_params, const PrintConfig &print_config,
|
||||||
|
const PrintObject &object, coordf_t print_z);
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|
||||||
#endif /* slic3r_SupportCommon_hpp_ */
|
#endif /* slic3r_SupportCommon_hpp_ */
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "Print.hpp"
|
#include "Print.hpp"
|
||||||
#include "SupportMaterial.hpp"
|
#include "SupportMaterial.hpp"
|
||||||
#include "SupportCommon.hpp"
|
#include "SupportCommon.hpp"
|
||||||
|
#include "BeltFloorContext.hpp"
|
||||||
#include "Geometry.hpp"
|
#include "Geometry.hpp"
|
||||||
#include "Point.hpp"
|
#include "Point.hpp"
|
||||||
#include "MutablePolygon.hpp"
|
#include "MutablePolygon.hpp"
|
||||||
@@ -366,10 +367,21 @@ inline void layers_append(SupportGeneratorLayersPtr &dst, const SupportGenerator
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Support layer that is covered by some form of dense interface.
|
// Support layer that is covered by some form of dense interface.
|
||||||
static constexpr const std::initializer_list<SupporLayerType> support_types_interface {
|
static constexpr const std::initializer_list<SupporLayerType> support_types_interface {
|
||||||
SupporLayerType::RaftInterface, SupporLayerType::BottomContact, SupporLayerType::BottomInterface, SupporLayerType::TopContact, SupporLayerType::TopInterface
|
SupporLayerType::RaftInterface, SupporLayerType::BottomContact, SupporLayerType::BottomInterface, SupporLayerType::TopContact, SupporLayerType::TopInterface
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Forward declarations for belt floor helpers (defined later in this file).
|
||||||
|
// belt_floor_surface_polygon is declared in SupportCommon.hpp (non-static,
|
||||||
|
// shared with TreeSupport.cpp).
|
||||||
|
|
||||||
|
static Polygons belt_floor_valid_region_polygon(
|
||||||
|
const SlicingParameters &slicing_params, const PrintConfig &print_config,
|
||||||
|
const PrintObject &object, coordf_t print_z);
|
||||||
|
static void trim_support_layers_by_belt_floor(
|
||||||
|
const SlicingParameters &slicing_params, const PrintConfig &print_config,
|
||||||
|
const PrintObject &object, SupportGeneratorLayersPtr &support_layers);
|
||||||
|
|
||||||
void PrintObjectSupportMaterial::generate(PrintObject &object)
|
void PrintObjectSupportMaterial::generate(PrintObject &object)
|
||||||
{
|
{
|
||||||
BOOST_LOG_TRIVIAL(info) << "Support generator - Start";
|
BOOST_LOG_TRIVIAL(info) << "Support generator - Start";
|
||||||
@@ -442,11 +454,12 @@ void PrintObjectSupportMaterial::generate(PrintObject &object)
|
|||||||
object, bottom_contacts, top_contacts, layer_storage);
|
object, bottom_contacts, top_contacts, layer_storage);
|
||||||
|
|
||||||
this->trim_support_layers_by_object(object, top_contacts, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_support_params.gap_xy);
|
this->trim_support_layers_by_object(object, top_contacts, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_support_params.gap_xy);
|
||||||
|
trim_support_layers_by_belt_floor(m_slicing_params, *m_print_config, object, top_contacts);
|
||||||
|
|
||||||
#ifdef SLIC3R_DEBUG
|
#ifdef SLIC3R_DEBUG
|
||||||
for (const SupportGeneratorLayer *layer : top_contacts)
|
for (const SupportGeneratorLayer *layer : top_contacts)
|
||||||
Slic3r::SVG::export_expolygons(
|
Slic3r::SVG::export_expolygons(
|
||||||
debug_out_path("support-top-contacts-trimmed-by-object-%d-%lf.svg", iRun, layer->print_z),
|
debug_out_path("support-top-contacts-trimmed-by-object-%d-%lf.svg", iRun, layer->print_z),
|
||||||
union_ex(layer->polygons));
|
union_ex(layer->polygons));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -602,6 +615,37 @@ Polygons collect_region_slices_by_type(const Layer &layer, SurfaceType surface_t
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Belt printer: compute the belt-side half-plane polygon at a given print_z.
|
||||||
|
// This represents the region where the belt surface exists (the "phantom top surface").
|
||||||
|
// Support that overlaps with this polygon should terminate with a bottom contact.
|
||||||
|
// Returns empty if belt floor is not active.
|
||||||
|
Polygons belt_floor_surface_polygon(
|
||||||
|
const SlicingParameters &slicing_params,
|
||||||
|
const PrintConfig &print_config,
|
||||||
|
const PrintObject &object,
|
||||||
|
coordf_t print_z)
|
||||||
|
{
|
||||||
|
BeltFloorContext ctx;
|
||||||
|
if (!ctx.init(slicing_params, print_config))
|
||||||
|
return {};
|
||||||
|
return ctx.surface_polygon(print_z);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Belt printer: compute the valid-region half-plane polygon at a given print_z.
|
||||||
|
// This is the region where support is allowed to exist (above the belt).
|
||||||
|
// Used to clip the downward-propagating support projection.
|
||||||
|
static Polygons belt_floor_valid_region_polygon(
|
||||||
|
const SlicingParameters &slicing_params,
|
||||||
|
const PrintConfig &print_config,
|
||||||
|
const PrintObject &object,
|
||||||
|
coordf_t print_z)
|
||||||
|
{
|
||||||
|
BeltFloorContext ctx;
|
||||||
|
if (!ctx.init(slicing_params, print_config))
|
||||||
|
return {};
|
||||||
|
return ctx.valid_region_polygon(print_z);
|
||||||
|
}
|
||||||
|
|
||||||
// Collect outer contours of all slices of this layer.
|
// Collect outer contours of all slices of this layer.
|
||||||
// This is useful for calculating the support base with holes filled.
|
// This is useful for calculating the support base with holes filled.
|
||||||
Polygons collect_slices_outer(const Layer &layer)
|
Polygons collect_slices_outer(const Layer &layer)
|
||||||
@@ -1393,6 +1437,10 @@ static inline ExPolygons detect_overhangs(
|
|||||||
const double threshold_rad = Geometry::deg2rad(thresh_angle);
|
const double threshold_rad = Geometry::deg2rad(thresh_angle);
|
||||||
const bool bridge_no_support = object_config.bridge_no_support.value;
|
const bool bridge_no_support = object_config.bridge_no_support.value;
|
||||||
const coordf_t xy_expansion = scale_(object_config.support_expansion.value);
|
const coordf_t xy_expansion = scale_(object_config.support_expansion.value);
|
||||||
|
// Build plate tilt: compute per-layer XY shift for tilted gravity direction
|
||||||
|
const double tilt_x_rad = Geometry::deg2rad(print_config.build_plate_tilt_x.value);
|
||||||
|
const double tilt_y_rad = Geometry::deg2rad(print_config.build_plate_tilt_y.value);
|
||||||
|
const bool has_tilt = std::abs(tilt_x_rad) > EPSILON || std::abs(tilt_y_rad) > EPSILON;
|
||||||
float lower_layer_offset = 0;
|
float lower_layer_offset = 0;
|
||||||
|
|
||||||
if (layer_id == 0)
|
if (layer_id == 0)
|
||||||
@@ -1426,6 +1474,19 @@ static inline ExPolygons detect_overhangs(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply build plate tilt: shift lower layer polygons to simulate tilted gravity.
|
||||||
|
// This is loop-invariant across regions, so compute it once here.
|
||||||
|
const Polygons *effective_lower = &lower_layer_polygons;
|
||||||
|
Polygons tilted_lower;
|
||||||
|
if (has_tilt) {
|
||||||
|
tilted_lower = lower_layer_polygons;
|
||||||
|
const double lh = lower_layer.height;
|
||||||
|
Point tilt_shift(coord_t(scale_(lh * tan(tilt_y_rad))),
|
||||||
|
coord_t(scale_(lh * tan(tilt_x_rad))));
|
||||||
|
translate(tilted_lower, tilt_shift);
|
||||||
|
effective_lower = &tilted_lower;
|
||||||
|
}
|
||||||
|
|
||||||
for (LayerRegion *layerm : layer.regions()) {
|
for (LayerRegion *layerm : layer.regions()) {
|
||||||
// Extrusion width accounts for the roundings of the extrudates.
|
// Extrusion width accounts for the roundings of the extrudates.
|
||||||
// It is the maximum widh of the extrudate.
|
// It is the maximum widh of the extrudate.
|
||||||
@@ -1444,7 +1505,7 @@ static inline ExPolygons detect_overhangs(
|
|||||||
Polygons layerm_polygons = to_polygons(layerm->slices.surfaces);
|
Polygons layerm_polygons = to_polygons(layerm->slices.surfaces);
|
||||||
if (lower_layer_offset == 0.f) {
|
if (lower_layer_offset == 0.f) {
|
||||||
// Support everything.
|
// Support everything.
|
||||||
diff_polygons = diff(layerm_polygons, lower_layer_polygons);
|
diff_polygons = diff(layerm_polygons, *effective_lower);
|
||||||
if (buildplate_only) {
|
if (buildplate_only) {
|
||||||
// Don't support overhangs above the top surfaces.
|
// Don't support overhangs above the top surfaces.
|
||||||
// This step is done before the contact surface is calculated by growing the overhang region.
|
// This step is done before the contact surface is calculated by growing the overhang region.
|
||||||
@@ -1453,9 +1514,9 @@ static inline ExPolygons detect_overhangs(
|
|||||||
} else if (auto_normal_support) {
|
} else if (auto_normal_support) {
|
||||||
// Get the regions needing a suport, collapse very tiny spots.
|
// Get the regions needing a suport, collapse very tiny spots.
|
||||||
//FIXME cache the lower layer offset if this layer has multiple regions.
|
//FIXME cache the lower layer offset if this layer has multiple regions.
|
||||||
diff_polygons =
|
diff_polygons =
|
||||||
diff(layerm_polygons,
|
diff(layerm_polygons,
|
||||||
expand(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS));
|
expand(*effective_lower, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS));
|
||||||
if (buildplate_only && ! annotations.buildplate_covered[layer_id].empty()) {
|
if (buildplate_only && ! annotations.buildplate_covered[layer_id].empty()) {
|
||||||
// Don't support overhangs above the top surfaces.
|
// Don't support overhangs above the top surfaces.
|
||||||
// This step is done before the contact surface is calculated by growing the overhang region.
|
// This step is done before the contact surface is calculated by growing the overhang region.
|
||||||
@@ -1463,9 +1524,9 @@ static inline ExPolygons detect_overhangs(
|
|||||||
}
|
}
|
||||||
if (! diff_polygons.empty()) {
|
if (! diff_polygons.empty()) {
|
||||||
// Offset the support regions back to a full overhang, restrict them to the full overhang.
|
// Offset the support regions back to a full overhang, restrict them to the full overhang.
|
||||||
// This is done to increase size of the supporting columns below, as they are calculated by
|
// This is done to increase size of the supporting columns below, as they are calculated by
|
||||||
// propagating these contact surfaces downwards.
|
// propagating these contact surfaces downwards.
|
||||||
diff_polygons = diff(intersection(expand(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons), lower_layer_polygons);
|
diff_polygons = diff(intersection(expand(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons), *effective_lower);
|
||||||
}
|
}
|
||||||
//FIXME add user defined filtering here based on minimal area or minimum radius or whatever.
|
//FIXME add user defined filtering here based on minimal area or minimum radius or whatever.
|
||||||
|
|
||||||
@@ -2505,6 +2566,82 @@ static inline SupportGeneratorLayer* detect_bottom_contacts(
|
|||||||
return &layer_new;
|
return &layer_new;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Belt printer: detect bottom contacts where support meets the belt floor plane.
|
||||||
|
// Modeled on detect_bottom_contacts() but uses the belt plane polygon instead of stTop surfaces.
|
||||||
|
static inline SupportGeneratorLayer* detect_belt_floor_bottom_contacts(
|
||||||
|
const SlicingParameters &slicing_params,
|
||||||
|
const SupportParameters &support_params,
|
||||||
|
const PrintConfig &print_config,
|
||||||
|
const PrintObject &object,
|
||||||
|
const Layer &layer,
|
||||||
|
// Existing top contact layers, for snapping.
|
||||||
|
const SupportGeneratorLayersPtr &top_contacts,
|
||||||
|
size_t contact_idx,
|
||||||
|
SupportGeneratorLayerStorage &layer_storage,
|
||||||
|
std::vector<Polygons> &layer_support_areas,
|
||||||
|
const Polygons &supports_projected)
|
||||||
|
{
|
||||||
|
// Compute the belt surface polygon at this layer's Z.
|
||||||
|
Polygons belt_surface = belt_floor_surface_polygon(slicing_params, print_config, object, layer.print_z);
|
||||||
|
if (belt_surface.empty())
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
// Find where projected support overlaps the belt surface.
|
||||||
|
Polygons touching = intersection(belt_surface, supports_projected);
|
||||||
|
if (touching.empty())
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
assert(layer.id() >= slicing_params.raft_layers());
|
||||||
|
size_t layer_id = layer.id() - slicing_params.raft_layers();
|
||||||
|
|
||||||
|
// Allocate a new bottom contact layer resting on the belt plane.
|
||||||
|
SupportGeneratorLayer &layer_new = layer_storage.allocate_unguarded(SupporLayerType::BottomContact);
|
||||||
|
|
||||||
|
// No object layer to sync with -- compute heights directly from flow parameters.
|
||||||
|
layer_new.height = support_params.support_material_bottom_interface_flow.height();
|
||||||
|
layer_new.print_z = layer.print_z + layer_new.height + slicing_params.gap_object_support;
|
||||||
|
layer_new.bottom_z = layer.print_z;
|
||||||
|
layer_new.idx_object_layer_below = layer_id;
|
||||||
|
layer_new.bridging = ! slicing_params.zero_gap_interface_bottom && object.config().thick_bridges;
|
||||||
|
layer_new.polygons = expand(touching, float(support_params.support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS);
|
||||||
|
|
||||||
|
if (! slicing_params.zero_gap_interface_bottom) {
|
||||||
|
// Snap to nearby top contact layers to avoid very thin support layers.
|
||||||
|
for (size_t top_idx = size_t(std::max<int>(0, int(contact_idx)));
|
||||||
|
top_idx < top_contacts.size() && top_contacts[top_idx]->print_z < layer_new.print_z + support_params.support_layer_height_min + EPSILON;
|
||||||
|
++ top_idx) {
|
||||||
|
if (top_contacts[top_idx]->print_z > layer_new.print_z - support_params.support_layer_height_min - EPSILON) {
|
||||||
|
coordf_t diff = layer_new.print_z - top_contacts[top_idx]->print_z;
|
||||||
|
assert(std::abs(diff) <= support_params.support_layer_height_min + EPSILON);
|
||||||
|
if (diff > 0.F) {
|
||||||
|
if (layer_new.height - diff > support_params.support_layer_height_min) {
|
||||||
|
layer_new.print_z = top_contacts[top_idx]->print_z;
|
||||||
|
layer_new.height -= diff;
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
layer_new.print_z = top_contacts[top_idx]->print_z;
|
||||||
|
layer_new.height -= diff;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim the already created base layers above this belt contact.
|
||||||
|
touching = expand(touching, float(SCALED_EPSILON));
|
||||||
|
for (int layer_id_above = int(layer_id) + 1; layer_id_above < int(object.total_layer_count()); ++ layer_id_above) {
|
||||||
|
const Layer &layer_above = *object.layers()[layer_id_above];
|
||||||
|
if (layer_above.print_z > layer_new.print_z - EPSILON)
|
||||||
|
break;
|
||||||
|
if (Polygons &above = layer_support_areas[layer_id_above]; ! above.empty())
|
||||||
|
above = diff(above, touching);
|
||||||
|
}
|
||||||
|
|
||||||
|
return &layer_new;
|
||||||
|
}
|
||||||
|
|
||||||
// Returns polygons to print + polygons to propagate downwards.
|
// Returns polygons to print + polygons to propagate downwards.
|
||||||
// Called twice: First for normal supports, possibly trimmed by "on build plate only", second for support enforcers not trimmed by "on build plate only".
|
// Called twice: First for normal supports, possibly trimmed by "on build plate only", second for support enforcers not trimmed by "on build plate only".
|
||||||
static inline std::pair<Polygons, Polygons> project_support_to_grid(const Layer &layer, const SupportGridParams &grid_params, const Polygons &overhangs, Polygons *layer_buildplate_covered
|
static inline std::pair<Polygons, Polygons> project_support_to_grid(const Layer &layer, const SupportGridParams &grid_params, const Polygons &overhangs, Polygons *layer_buildplate_covered
|
||||||
@@ -2604,6 +2741,8 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::bottom_contact_layers_and_
|
|||||||
//const auto expansion_to_slice = m_support_material_flow.scaled_spacing() / 2 + 25;
|
//const auto expansion_to_slice = m_support_material_flow.scaled_spacing() / 2 + 25;
|
||||||
const SupportGridParams grid_params(*m_object_config, m_support_params.support_material_flow);
|
const SupportGridParams grid_params(*m_object_config, m_support_params.support_material_flow);
|
||||||
const bool buildplate_only = ! buildplate_covered.empty();
|
const bool buildplate_only = ! buildplate_covered.empty();
|
||||||
|
const bool has_belt_floor = std::abs(m_slicing_params.belt_floor_shear_factor) > EPSILON
|
||||||
|
&& m_print_config->belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly;
|
||||||
|
|
||||||
// Allocate empty surface areas, one per object layer.
|
// Allocate empty surface areas, one per object layer.
|
||||||
layer_support_areas.assign(object.total_layer_count(), Polygons());
|
layer_support_areas.assign(object.total_layer_count(), Polygons());
|
||||||
@@ -2664,8 +2803,9 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::bottom_contact_layers_and_
|
|||||||
tbb::task_group task_group;
|
tbb::task_group task_group;
|
||||||
const Polygons &overhangs_for_bottom_contacts = buildplate_only ? enforcers_projection_raw : overhangs_projection_raw;
|
const Polygons &overhangs_for_bottom_contacts = buildplate_only ? enforcers_projection_raw : overhangs_projection_raw;
|
||||||
if (! overhangs_for_bottom_contacts.empty())
|
if (! overhangs_for_bottom_contacts.empty())
|
||||||
// Find the bottom contact layers above the top surfaces of this layer.
|
// Find the bottom contact layers above the top surfaces of this layer,
|
||||||
task_group.run([this, &object, &layer, &top_contacts, contact_idx, &layer_storage, &layer_support_areas, &bottom_contacts, &overhangs_for_bottom_contacts
|
// and also detect belt floor contacts if belt mode is active.
|
||||||
|
task_group.run([this, &object, &layer, &top_contacts, contact_idx, &layer_storage, &layer_support_areas, &bottom_contacts, &overhangs_for_bottom_contacts, has_belt_floor
|
||||||
#ifdef SLIC3R_DEBUG
|
#ifdef SLIC3R_DEBUG
|
||||||
, iRun, &polygons_new
|
, iRun, &polygons_new
|
||||||
#endif // SLIC3R_DEBUG
|
#endif // SLIC3R_DEBUG
|
||||||
@@ -2679,6 +2819,15 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::bottom_contact_layers_and_
|
|||||||
);
|
);
|
||||||
if (layer_new)
|
if (layer_new)
|
||||||
bottom_contacts.push_back(layer_new);
|
bottom_contacts.push_back(layer_new);
|
||||||
|
// Belt floor phantom surface: detect where support meets the belt plane.
|
||||||
|
if (has_belt_floor) {
|
||||||
|
SupportGeneratorLayer *belt_layer = detect_belt_floor_bottom_contacts(
|
||||||
|
m_slicing_params, m_support_params, *m_print_config, object,
|
||||||
|
layer, top_contacts, contact_idx, layer_storage,
|
||||||
|
layer_support_areas, overhangs_for_bottom_contacts);
|
||||||
|
if (belt_layer)
|
||||||
|
bottom_contacts.push_back(belt_layer);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Polygons &layer_support_area = layer_support_areas[layer_id];
|
Polygons &layer_support_area = layer_support_areas[layer_id];
|
||||||
@@ -2717,6 +2866,23 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::bottom_contact_layers_and_
|
|||||||
|
|
||||||
task_group.wait();
|
task_group.wait();
|
||||||
|
|
||||||
|
// Belt floor: clip projections and support areas so support doesn't
|
||||||
|
// propagate below the belt plane.
|
||||||
|
if (has_belt_floor) {
|
||||||
|
Polygons valid_region = belt_floor_valid_region_polygon(
|
||||||
|
m_slicing_params, *m_print_config, object, layer.print_z);
|
||||||
|
if (! valid_region.empty()) {
|
||||||
|
if (! overhangs_projection.empty())
|
||||||
|
overhangs_projection = intersection(overhangs_projection, valid_region);
|
||||||
|
if (! enforcers_projection.empty())
|
||||||
|
enforcers_projection = intersection(enforcers_projection, valid_region);
|
||||||
|
if (! layer_support_area.empty())
|
||||||
|
layer_support_area = intersection(layer_support_area, valid_region);
|
||||||
|
if (! layer_support_area_enforcers.empty())
|
||||||
|
layer_support_area_enforcers = intersection(layer_support_area_enforcers, valid_region);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (! layer_support_area_enforcers.empty()) {
|
if (! layer_support_area_enforcers.empty()) {
|
||||||
if (layer_support_area.empty())
|
if (layer_support_area.empty())
|
||||||
layer_support_area = std::move(layer_support_area_enforcers);
|
layer_support_area = std::move(layer_support_area_enforcers);
|
||||||
@@ -2727,6 +2893,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::bottom_contact_layers_and_
|
|||||||
|
|
||||||
std::reverse(bottom_contacts.begin(), bottom_contacts.end());
|
std::reverse(bottom_contacts.begin(), bottom_contacts.end());
|
||||||
trim_support_layers_by_object(object, bottom_contacts, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_support_params.gap_xy);
|
trim_support_layers_by_object(object, bottom_contacts, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_support_params.gap_xy);
|
||||||
|
trim_support_layers_by_belt_floor(m_slicing_params, *m_print_config, object, bottom_contacts);
|
||||||
return bottom_contacts;
|
return bottom_contacts;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3100,6 +3267,33 @@ void PrintObjectSupportMaterial::generate_base_layers(
|
|||||||
#endif /* SLIC3R_DEBUG */
|
#endif /* SLIC3R_DEBUG */
|
||||||
|
|
||||||
this->trim_support_layers_by_object(object, intermediate_layers, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_support_params.gap_xy);
|
this->trim_support_layers_by_object(object, intermediate_layers, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_support_params.gap_xy);
|
||||||
|
trim_support_layers_by_belt_floor(m_slicing_params, *m_print_config, object, intermediate_layers);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Belt printer: trim support layer polygons by the belt floor plane.
|
||||||
|
// For each support layer, computes the belt floor half-plane at that layer's print_z
|
||||||
|
// and subtracts it from the support polygons. This follows the same diff() pattern
|
||||||
|
// as trim_support_layers_by_object() so that interface layers derived from trimmed
|
||||||
|
// intermediates automatically inherit the belt floor trimming.
|
||||||
|
static void trim_support_layers_by_belt_floor(
|
||||||
|
const SlicingParameters &slicing_params,
|
||||||
|
const PrintConfig &print_config,
|
||||||
|
const PrintObject &object,
|
||||||
|
SupportGeneratorLayersPtr &support_layers)
|
||||||
|
{
|
||||||
|
BeltFloorContext ctx;
|
||||||
|
if (!ctx.init(slicing_params, print_config))
|
||||||
|
return;
|
||||||
|
if (print_config.belt_support_floor_mode.value != BeltSupportFloorMode::GeneratorOnly)
|
||||||
|
return;
|
||||||
|
|
||||||
|
tbb::parallel_for(tbb::blocked_range<size_t>(0, support_layers.size()),
|
||||||
|
[&](const tbb::blocked_range<size_t> &range) {
|
||||||
|
for (size_t i = range.begin(); i < range.end(); ++i)
|
||||||
|
if (support_layers[i])
|
||||||
|
support_layers[i]->polygons = diff(support_layers[i]->polygons,
|
||||||
|
ctx.surface_polygon(support_layers[i]->print_z));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void PrintObjectSupportMaterial::trim_support_layers_by_object(
|
void PrintObjectSupportMaterial::trim_support_layers_by_object(
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user