Compare commits

..
Author SHA1 Message Date
Hanif Koh 44a3033a96 Allow Unsigned Executable Memory in the macOS Entitlements
The Bambu network plug-in's code protector rewrites one page of its own signed __TEXT after loading. The hardened runtime tolerates that until the page is evicted; the next read of it then kills OrcaSlicer with CODESIGNING Invalid Page. Bambu Studio signs with allow-unsigned-executable-memory for this reason; with it added, the same build survives critical memory pressure that killed it in 30 s without.
2026-10-06 16:46:15 +08:00
yw4z f8dd56053c Match style of height range modifier section on sidebar (#15703)
* init

* update

* rescale layer icon
2026-10-06 11:00:04 +03:00
169 changed files with 446 additions and 13334 deletions
-14
View File
@@ -74,26 +74,12 @@ jobs:
set +e
./OrcaSlicer_profile_validator -p ${{ github.workspace }}/resources/profiles -l 2 2>&1 | tee ${{ runner.temp }}/validate_system.log
exit ${PIPESTATUS[0]}
# The validator above is the nightly build of main, so it cannot slice profiles that use
# settings a PR adds to the engine: it reports their placeholders as undefined. A PR that
# changes src/ also runs Build all, whose Slice check runs this same sweep with the
# validator built from the PR, so the sweep below only runs for the other PRs.
- name: Detect engine changes
id: engine_changes
if: ${{ github.event_name == 'pull_request' }}
run: |
base=${{ github.event.pull_request.base.sha }}
if git fetch --no-tags --depth=1 origin "$base" && ! git diff --quiet "$base" HEAD -- src/; then
echo "changed=true" >> "$GITHUB_OUTPUT"
echo "::notice::This PR changes src/, so Build all's Slice check slices the profiles with the PR-built validator."
fi
# Slice a two-colour cube through every printer, and through every system process/filament whose
# templates no printer's own slice reaches, so every custom g-code and filename_format shipped is
# expanded (names in {if} branches not taken included) - catches undefined-placeholder /
# invalid-flow bugs the static checks above cannot see.
- name: validate slice (expand custom g-code)
id: validate_slice
if: ${{ steps.engine_changes.outputs.changed != 'true' }}
continue-on-error: true
run: |
set +e
@@ -88,18 +88,6 @@ struct NfpPConfig {
*/
bool explore_holes = false;
/**
* @brief Keep the final pile on the bin.
*
* The final alignment centres the pile on the alignment target. A target
* near an edge (a belt printer starts its parts at the leading end of the
* belt) would push part of a pile that is larger than the room around that
* point off the bed; with this set the pile stops at the edge instead, and a
* pile that does not fit along an axis is centred on it. Off by default, so
* the alignment of every other printer is unchanged.
*/
bool clamp_to_bin = false;
/**
* @brief If true, use all CPUs available. Run on a single core otherwise.
*/
@@ -1123,24 +1111,7 @@ private:
default: ; // DONT_ALIGN
}
auto d = cb - ci;
// Keep the pile on the bin (see Config::clamp_to_bin). The items' boxes carry
// their inflation, which is the margin left at the edge.
if (config_.clamp_to_bin) {
auto on_bin = [](Coord lo, Coord hi, Coord bin_lo, Coord bin_hi, Coord shift) {
if (hi - lo >= bin_hi - bin_lo)
return (bin_lo + bin_hi) / 2 - (lo + hi) / 2;
if (lo + shift < bin_lo)
shift = bin_lo - lo;
if (hi + shift > bin_hi)
shift = bin_hi - hi;
return shift;
};
setX(d, on_bin(getX(bb.minCorner()), getX(bb.maxCorner()), getX(bbin.minCorner()), getX(bbin.maxCorner()), getX(d)));
setY(d, on_bin(getY(bb.minCorner()), getY(bb.maxCorner()), getY(bbin.minCorner()), getY(bbin.maxCorner()), getY(d)));
cb = ci + d;
}
auto d = cb - ci;
// BBS make sure the item won't clash with excluded regions
// do we have wipe tower after arranging?
@@ -1,79 +0,0 @@
#!/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 -33
View File
@@ -1,13 +1,9 @@
{
"name": "Custom Printer",
"version": "02.04.00.08",
"version": "02.04.00.07",
"force_update": "0",
"description": "My configurations",
"machine_model_list": [
{
"name": "Generic Belt Printer",
"sub_path": "machine/MyBeltPrinter.json"
},
{
"name": "Generic Klipper Printer",
"sub_path": "machine/MyKlipper.json"
@@ -54,10 +50,6 @@
"name": "0.08mm Extra Fine @MyKlipper",
"sub_path": "process/0.08mm Extra Fine @MyKlipper.json"
},
{
"name": "0.12mm Fine @MyBeltPrinter",
"sub_path": "process/0.12mm Fine @MyBeltPrinter.json"
},
{
"name": "0.12mm Fine @MyKlipper",
"sub_path": "process/0.12mm Fine @MyKlipper.json"
@@ -70,10 +62,6 @@
"name": "0.16mm Optimal @MyKlipper",
"sub_path": "process/0.16mm Optimal @MyKlipper.json"
},
{
"name": "0.20mm Standard @MyBeltPrinter",
"sub_path": "process/0.20mm Standard @MyBeltPrinter.json"
},
{
"name": "0.20mm Standard @MyKlipper",
"sub_path": "process/0.20mm Standard @MyKlipper.json"
@@ -274,10 +262,6 @@
"name": "MyKlipper 0.8 nozzle",
"sub_path": "machine/MyKlipper 0.8 nozzle.json"
},
{
"name": "fdm_belt_common",
"sub_path": "machine/fdm_belt_common.json"
},
{
"name": "fdm_toolchanger_common",
"sub_path": "machine/fdm_toolchanger_common.json"
@@ -290,22 +274,6 @@
"name": "MyRRF 0.4 nozzle",
"sub_path": "machine/MyRRF 0.4 nozzle.json"
},
{
"name": "MyBeltPrinter 0.2 nozzle",
"sub_path": "machine/MyBeltPrinter 0.2 nozzle.json"
},
{
"name": "MyBeltPrinter 0.4 nozzle",
"sub_path": "machine/MyBeltPrinter 0.4 nozzle.json"
},
{
"name": "MyBeltPrinter 0.6 nozzle",
"sub_path": "machine/MyBeltPrinter 0.6 nozzle.json"
},
{
"name": "MyBeltPrinter 0.8 nozzle",
"sub_path": "machine/MyBeltPrinter 0.8 nozzle.json"
},
{
"name": "MyToolChanger 0.2 nozzle",
"sub_path": "machine/MyToolChanger 0.2 nozzle.json"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

@@ -1,27 +0,0 @@
{
"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"
}
@@ -1,20 +0,0 @@
{
"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"
}
@@ -1,26 +0,0 @@
{
"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"
}
@@ -1,26 +0,0 @@
{
"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"
}
@@ -1,12 +0,0 @@
{
"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"
}
@@ -1,96 +0,0 @@
{
"type": "machine",
"name": "fdm_belt_common",
"inherits": "fdm_klipper_common",
"from": "system",
"instantiation": "false",
"gcode_flavor": "klipper",
"single_extruder_multi_material": "0",
"default_filament_profile": [
"Generic PLA @System"
],
"default_print_profile": "0.20mm Standard @MyBeltPrinter",
"max_layer_height": [
"0.32"
],
"min_layer_height": [
"0.08"
],
"deretraction_speed": [
"30"
],
"extruder_colour": [
"#FCE94F"
],
"extruder_offset": [
"0x0"
],
"long_retractions_when_cut": [
"0"
],
"nozzle_diameter": [
"0.4"
],
"retract_before_wipe": [
"70%"
],
"retract_length_toolchange": [
"2"
],
"retract_lift_above": [
"0"
],
"retract_lift_below": [
"0"
],
"retract_lift_enforce": [
"All Surfaces"
],
"retract_restart_extra": [
"0"
],
"retract_restart_extra_toolchange": [
"0"
],
"retract_when_changing_layer": [
"1"
],
"retraction_distances_when_cut": [
"18"
],
"retraction_length": [
"0.8"
],
"retraction_minimum_travel": [
"1"
],
"retraction_speed": [
"30"
],
"travel_slope": [
"3"
],
"wipe": [
"1"
],
"wipe_distance": [
"1"
],
"z_hop": [
"0"
],
"z_hop_types": [
"Normal Lift"
],
"gcode_remap_x": "rev_x",
"gcode_remap_y": "pos_z",
"gcode_remap_z": "pos_y",
"belt_printer": "1",
"belt_slice_rotation": "x",
"belt_slice_rotation_angle": "45",
"belt_slice_rotation_global": "1",
"build_plate_tilt_x": "45",
"purge_in_prime_tower": "0",
"scan_first_layer": "0",
"auxiliary_fan": "0"
}
@@ -1,20 +0,0 @@
{
"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"
]
}
@@ -1,17 +0,0 @@
{
"type": "process",
"name": "0.20mm Standard @MyBeltPrinter",
"inherits": "fdm_process_klipper_common",
"from": "system",
"setting_id": "YzCDAgH3uLOM53pF",
"instantiation": "true",
"layer_height": "0.2",
"initial_layer_print_height": "0.2",
"skirt_loops": "0",
"skirt_distance": "0",
"compatible_printers": [
"MyBeltPrinter 0.4 nozzle",
"MyBeltPrinter 0.6 nozzle",
"MyBeltPrinter 0.8 nozzle"
]
}
-54
View File
@@ -1,54 +0,0 @@
{
"name": "IdeaFormer",
"version": "02.00.00.06",
"force_update": "0",
"description": "IdeaFormer belt printer configurations",
"machine_model_list": [
{
"name": "IdeaFormer IR3 V2",
"sub_path": "machine/IdeaFormer IR3 V2.json"
}
],
"process_list": [
{
"name": "fdm_process_common",
"sub_path": "process/fdm_process_common.json"
},
{
"name": "0.20mm Standard @IdeaFormer IR3 V2",
"sub_path": "process/0.20mm Standard @IdeaFormer IR3 V2.json"
}
],
"filament_list": [
{
"name": "Generic PLA @IdeaFormer IR3 V2",
"sub_path": "filament/Generic PLA @IdeaFormer IR3 V2.json"
},
{
"name": "eSUN PLA @IdeaFormer IR3 V2",
"sub_path": "filament/eSUN PLA @IdeaFormer IR3 V2.json"
},
{
"name": "Generic PETG @IdeaFormer IR3 V2",
"sub_path": "filament/Generic PETG @IdeaFormer IR3 V2.json"
}
],
"machine_list": [
{
"name": "fdm_machine_common",
"sub_path": "machine/fdm_machine_common.json"
},
{
"name": "fdm_klipper_common",
"sub_path": "machine/fdm_klipper_common.json"
},
{
"name": "fdm_belt_common",
"sub_path": "machine/fdm_belt_common.json"
},
{
"name": "IdeaFormer IR3 V2 0.4 nozzle",
"sub_path": "machine/IdeaFormer IR3 V2 0.4 nozzle.json"
}
]
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 KiB

@@ -1,77 +0,0 @@
{
"type": "filament",
"name": "Generic PETG @IdeaFormer IR3 V2",
"inherits": "Generic PETG @System",
"from": "system",
"setting_id": "n4zaXcUUzTqAxq5f",
"instantiation": "true",
"filament_extruder_variant": [
"Direct Drive Standard"
],
"compatible_printers": [
"IdeaFormer IR3 V2 0.4 nozzle"
],
"filament_type": [
"PETG"
],
"filament_vendor": [
"Generic"
],
"filament_settings_id": [
"Generic PETG @IdeaFormer IR3 V2"
],
"filament_flow_ratio": [
"0.95"
],
"filament_cost": [
"25"
],
"nozzle_temperature": [
"240"
],
"nozzle_temperature_initial_layer": [
"245"
],
"cool_plate_temp": [
"80"
],
"cool_plate_temp_initial_layer": [
"80"
],
"fan_min_speed": [
"40"
],
"fan_max_speed": [
"60"
],
"overhang_fan_threshold": [
"25%"
],
"overhang_fan_speed": [
"80"
],
"full_fan_speed_layer": [
"8"
],
"slow_down_min_speed": [
"20"
],
"slow_down_layer_time": [
"4"
],
"fan_cooling_layer_time": [
"100"
],
"filament_retraction_length": [
"2"
],
"filament_retraction_speed": [
"40"
],
"filament_deretraction_speed": [
"40"
],
"filament_start_gcode": [
"; Generic PETG @IdeaFormer IR3 V2 — belt PETG, bed 80C"
]
}
@@ -1,65 +0,0 @@
{
"type": "filament",
"name": "Generic PLA @IdeaFormer IR3 V2",
"inherits": "Generic PLA @System",
"from": "system",
"setting_id": "1xjycsEAFh6KQIhp",
"instantiation": "true",
"filament_extruder_variant": [
"Direct Drive Standard"
],
"compatible_printers": [
"IdeaFormer IR3 V2 0.4 nozzle"
],
"filament_type": [
"PLA"
],
"filament_vendor": [
"Generic"
],
"filament_settings_id": [
"Generic PLA @IdeaFormer IR3 V2"
],
"nozzle_temperature": [
"215"
],
"hot_plate_temp": [
"75"
],
"hot_plate_temp_initial_layer": [
"75"
],
"cool_plate_temp": [
"75"
],
"cool_plate_temp_initial_layer": [
"75"
],
"textured_plate_temp": [
"75"
],
"textured_plate_temp_initial_layer": [
"75"
],
"close_fan_the_first_x_layers": [
"3"
],
"full_fan_speed_layer": [
"8"
],
"slow_down_min_speed": [
"20"
],
"filament_retraction_length": [
"1.5"
],
"filament_retraction_speed": [
"35"
],
"filament_deretraction_speed": [
"30"
],
"filament_start_gcode": [
"; Generic PLA @IdeaFormer IR3 V2 — belt PLA, bed 75C"
]
}
@@ -1,36 +0,0 @@
{
"type": "filament",
"name": "eSUN PLA @IdeaFormer IR3 V2",
"inherits": "Generic PLA @IdeaFormer IR3 V2",
"from": "system",
"setting_id": "XqkviBmFHEglXueX",
"filament_id": "OFkrxQC4",
"instantiation": "true",
"compatible_printers": [
"IdeaFormer IR3 V2 0.4 nozzle"
],
"filament_type": [
"PLA"
],
"filament_vendor": [
"eSUN"
],
"filament_settings_id": [
"eSUN PLA @IdeaFormer IR3 V2"
],
"nozzle_temperature_initial_layer": [
"200"
],
"nozzle_temperature": [
"200"
],
"enable_pressure_advance": [
"1"
],
"pressure_advance": [
"0.12"
],
"filament_max_volumetric_speed": [
"20"
]
}
@@ -1,98 +0,0 @@
{
"type": "machine",
"name": "IdeaFormer IR3 V2 0.4 nozzle",
"inherits": "fdm_belt_common",
"from": "system",
"setting_id": "MDQZgwRgg72lmjtu",
"instantiation": "true",
"printer_model": "IdeaFormer IR3 V2",
"printer_variant": "0.4",
"nozzle_diameter": [
"0.4"
],
"printable_area": [
"0x0",
"250x0",
"250x2000",
"0x2000"
],
"printable_height": "250",
"belt_printer_infinite_y": "1",
"thumbnails": [
"48x48/PNG",
"300x300/PNG"
],
"default_filament_profile": [
"Generic PLA @IdeaFormer IR3 V2"
],
"default_print_profile": "0.20mm Standard @IdeaFormer IR3 V2",
"use_relative_e_distances": "1",
"machine_max_acceleration_extruding": [
"5000",
"5000"
],
"machine_max_acceleration_retracting": [
"1000",
"1000"
],
"machine_max_acceleration_travel": [
"9000",
"9000"
],
"machine_max_acceleration_x": [
"5000",
"5000"
],
"machine_max_acceleration_y": [
"5000",
"5000"
],
"machine_max_acceleration_z": [
"100",
"100"
],
"machine_max_jerk_x": [
"10",
"10"
],
"machine_max_jerk_y": [
"10",
"10"
],
"machine_max_jerk_z": [
"0.4",
"0.4"
],
"machine_max_speed_e": [
"60",
"60"
],
"machine_max_speed_x": [
"500",
"500"
],
"machine_max_speed_y": [
"500",
"500"
],
"machine_max_speed_z": [
"20",
"20"
],
"retraction_length": [
"2"
],
"retraction_speed": [
"40"
],
"deretraction_speed": [
"40"
],
"retract_lift_below": [
"300"
],
"machine_start_gcode": "; === IdeaFormer IR3 V2 Belt Printer Start ===\n; Axes: X=lateral, Y=gantry height (probe), Z=belt\nG90 ; absolute positioning\nM82 ; absolute extruder\nG21 ; millimeters\nG28 ; home all axes\nG1 Y20 F500 ; lift nozzle 20mm from belt\n; Bed + hotend temps come from the active filament profile. Belt PLA requires 75 C bed — use Generic/eSun PLA @IdeaFormer IR3 V2 filament presets to get it automatically.\nM140 S[hot_plate_temp_initial_layer] ; set bed temp\nM104 S[nozzle_temperature_initial_layer] ; hotend temp\nM109 S[nozzle_temperature_initial_layer] ; wait hotend\nM190 S[hot_plate_temp_initial_layer] ; wait bed\n; --- Purge blob ---\nG92 E0 ; zero extruder\nG1 Y.1 ; nozzle 0.1mm above belt\nG1 E15 F1000 ; purge 15mm blob\nG1 Z20 E25 F800 ; belt advance 20mm + extrude\nG1 E23 ; retract 2mm\nG28 Y ; re-probe belt surface\nG1 E25 ; de-retract\n; --- Prime lines (full 250mm bed width) ---\nFMS_on ; filament motion sensor\nG1 X250 E50 F2000 ; prime line 1\nG92 Z0 ; reset belt origin\nG1 Z.4 ; belt advance 0.4mm\nG1 X0 E75 ; prime line 2\nG1 F1000 ; default feedrate\nG92 E0 Z0 ; zero extruder + belt = print origin\n",
"machine_end_gcode": "; === IdeaFormer IR3 V2 Belt Printer End ===\nM400 ; wait for moves to finish\nM104 S0 ; heater off\nM140 S0 ; bed off\nG92 E0 ; zero extruder\nG1 E-5 F300 ; retract 5mm\nG4 P5000 ; wait for ooze\nG91 ; relative mode - keep every end move relative on a belt\nG1 Y20 F1000 ; raise gantry 20mm for clearance over the part\nG1 Z676 F3000 ; advance belt one full machine-depth to eject the part and clean the belt\nG90 ; back to absolute\nG28 X ; home X only - NEVER 'G28' all: that homes Z/belt and reverses the whole print back into the gantry\nFMS_off ; filament motion sensor off\nBED_MESH_CLEAR\nM84 ; disable motors\n",
"machine_pause_gcode": "PAUSE",
"layer_change_gcode": "G92 E0 ; belt: reset extruder at layer change (relative E)"
}
@@ -1,12 +0,0 @@
{
"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"
}
@@ -1,99 +0,0 @@
{
"type": "machine",
"name": "fdm_belt_common",
"inherits": "fdm_klipper_common",
"from": "system",
"instantiation": "false",
"gcode_flavor": "klipper",
"single_extruder_multi_material": "0",
"default_filament_profile": [
"Generic PLA @System"
],
"default_print_profile": "0.20mm Standard @IdeaFormer IR3 V2",
"max_layer_height": [
"0.32"
],
"min_layer_height": [
"0.08"
],
"deretraction_speed": [
"30"
],
"extruder_colour": [
"#FCE94F"
],
"extruder_offset": [
"0x0"
],
"long_retractions_when_cut": [
"0"
],
"nozzle_diameter": [
"0.4"
],
"retract_before_wipe": [
"70%"
],
"retract_length_toolchange": [
"2"
],
"retract_lift_above": [
"0"
],
"retract_lift_below": [
"0"
],
"retract_lift_enforce": [
"All Surfaces"
],
"retract_restart_extra": [
"0"
],
"retract_restart_extra_toolchange": [
"0"
],
"retract_when_changing_layer": [
"1"
],
"retraction_distances_when_cut": [
"18"
],
"retraction_length": [
"0.8"
],
"retraction_minimum_travel": [
"1"
],
"retraction_speed": [
"30"
],
"travel_slope": [
"3"
],
"wipe": [
"1"
],
"wipe_distance": [
"1"
],
"z_hop": [
"0"
],
"z_hop_types": [
"Normal Lift"
],
"gcode_remap_x": "rev_x",
"gcode_remap_y": "pos_z",
"gcode_remap_z": "pos_y",
"printer_extruder_id": [
"1"
],
"belt_printer": "1",
"belt_slice_rotation": "x",
"belt_slice_rotation_angle": "45",
"belt_slice_rotation_global": "1",
"build_plate_tilt_x": "45",
"purge_in_prime_tower": "0",
"scan_first_layer": "0",
"auxiliary_fan": "0"
}
@@ -1,140 +0,0 @@
{
"type": "machine",
"name": "fdm_klipper_common",
"inherits": "fdm_machine_common",
"from": "system",
"instantiation": "false",
"gcode_flavor": "klipper",
"machine_max_acceleration_e": [
"5000",
"5000"
],
"machine_max_acceleration_extruding": [
"20000",
"20000"
],
"machine_max_acceleration_retracting": [
"5000",
"5000"
],
"machine_max_acceleration_travel": [
"20000",
"20000"
],
"machine_max_acceleration_x": [
"20000",
"20000"
],
"machine_max_acceleration_y": [
"20000",
"20000"
],
"machine_max_acceleration_z": [
"500",
"200"
],
"machine_max_speed_e": [
"25",
"25"
],
"machine_max_speed_x": [
"500",
"200"
],
"machine_max_speed_y": [
"500",
"200"
],
"machine_max_speed_z": [
"12",
"12"
],
"machine_max_jerk_e": [
"2.5",
"2.5"
],
"machine_max_jerk_x": [
"9",
"9"
],
"machine_max_jerk_y": [
"9",
"9"
],
"machine_max_jerk_z": [
"0.2",
"0.4"
],
"machine_min_extruding_rate": [
"0",
"0"
],
"machine_min_travel_rate": [
"0",
"0"
],
"max_layer_height": [
"0.32"
],
"min_layer_height": [
"0.08"
],
"printable_height": "250",
"extruder_clearance_radius": "65",
"extruder_clearance_height_to_rod": "36",
"extruder_clearance_height_to_lid": "140",
"printer_settings_id": "",
"printer_technology": "FFF",
"printer_variant": "0.4",
"retraction_minimum_travel": [
"1"
],
"retract_before_wipe": [
"70%"
],
"retract_when_changing_layer": [
"1"
],
"retraction_length": [
"0.8"
],
"retract_length_toolchange": [
"2"
],
"z_hop": [
"0.4"
],
"retract_restart_extra": [
"0"
],
"retract_restart_extra_toolchange": [
"0"
],
"retraction_speed": [
"30"
],
"deretraction_speed": [
"30"
],
"z_hop_types": "Normal Lift",
"single_extruder_multi_material": "1",
"change_filament_gcode": "",
"wipe": [
"1"
],
"default_filament_profile": [
"Generic PLA @System"
],
"default_print_profile": "0.20mm Standard @MyKlipper",
"bed_exclude_area": [
"0x0"
],
"machine_start_gcode": "M190 S[bed_temperature_initial_layer_single]\nM109 S[nozzle_temperature_initial_layer]\nPRINT_START EXTRUDER=[nozzle_temperature_initial_layer] BED=[bed_temperature_initial_layer_single]\n",
"machine_end_gcode": "PRINT_END",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n",
"machine_pause_gcode": "PAUSE",
"scan_first_layer": "0",
"nozzle_type": "undefine",
"auxiliary_fan": "0"
}
@@ -1,118 +0,0 @@
{
"type": "machine",
"name": "fdm_machine_common",
"from": "system",
"instantiation": "false",
"printer_technology": "FFF",
"deretraction_speed": [
"40"
],
"extruder_colour": [
"#FCE94F"
],
"extruder_offset": [
"0x0"
],
"gcode_flavor": "marlin",
"machine_max_acceleration_e": [
"5000"
],
"machine_max_acceleration_extruding": [
"10000"
],
"machine_max_acceleration_retracting": [
"1000"
],
"machine_max_acceleration_x": [
"10000"
],
"machine_max_acceleration_y": [
"10000"
],
"machine_max_acceleration_z": [
"500"
],
"machine_max_speed_e": [
"60"
],
"machine_max_speed_x": [
"500"
],
"machine_max_speed_y": [
"500"
],
"machine_max_speed_z": [
"10"
],
"machine_max_jerk_e": [
"5"
],
"machine_max_jerk_x": [
"8"
],
"machine_max_jerk_y": [
"8"
],
"machine_max_jerk_z": [
"0.4"
],
"machine_min_extruding_rate": [
"0"
],
"machine_min_travel_rate": [
"0"
],
"max_layer_height": [
"0.32"
],
"min_layer_height": [
"0.08"
],
"printable_height": "250",
"extruder_clearance_radius": "65",
"extruder_clearance_height_to_rod": "36",
"extruder_clearance_height_to_lid": "140",
"nozzle_diameter": [
"0.4"
],
"printer_settings_id": "",
"printer_variant": "0.4",
"retraction_minimum_travel": [
"2"
],
"retract_before_wipe": [
"70%"
],
"retract_when_changing_layer": [
"1"
],
"retraction_length": [
"1"
],
"retract_length_toolchange": [
"1"
],
"z_hop": [
"0"
],
"retract_restart_extra": [
"0"
],
"retract_restart_extra_toolchange": [
"0"
],
"retraction_speed": [
"60"
],
"single_extruder_multi_material": "1",
"change_filament_gcode": "",
"wipe": [
"1"
],
"default_print_profile": "",
"machine_start_gcode": "G0 Z20 F9000\nG92 E0; G1 E-10 F1200\nG28\nM970 Q1 A10 B10 C130 K0\nM970 Q1 A10 B131 C250 K1\nM974 Q1 S1 P0\nM970 Q0 A10 B10 C130 H20 K0\nM970 Q0 A10 B131 C250 K1\nM974 Q0 S1 P0\nM220 S100 ;Reset Feedrate\nM221 S100 ;Reset Flowrate\nG29 ;Home\nG90;\nG92 E0 ;Reset Extruder \nG1 Z2.0 F3000 ;Move Z Axis up \nG1 X10.1 Y20 Z0.28 F5000.0 ;Move to start position\nM109 S205;\nG1 X10.1 Y200.0 Z0.28 F1500.0 E15 ;Draw the first line\nG1 X10.4 Y200.0 Z0.28 F5000.0 ;Move to side a little\nG1 X10.4 Y20 Z0.28 F1500.0 E30 ;Draw the second line\nG92 E0 ;Reset Extruder \nG1 X110 Y110 Z2.0 F3000 ;Move Z Axis up",
"machine_end_gcode": "M400 ; wait for buffer to clear\nG92 E0 ; zero the extruder\nG1 E-4.0 F3600; retract \nG91\nG1 Z3;\nM104 S0 ; turn off hotend\nM140 S0 ; turn off bed\nM106 S0 ; turn off fan\nG90 \nG0 X110 Y200 F3600 \nprint_end",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n",
"machine_pause_gcode": "M601"
}
@@ -1,23 +0,0 @@
{
"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"
]
}
@@ -1,106 +0,0 @@
{
"type": "process",
"name": "fdm_process_common",
"from": "system",
"instantiation": "false",
"reduce_crossing_wall": "0",
"max_travel_detour_distance": "0",
"bottom_surface_pattern": "monotonic",
"bottom_shell_thickness": "0",
"bridge_speed": "50",
"brim_width": "5",
"brim_object_gap": "0.1",
"compatible_printers": [],
"compatible_printers_condition": "",
"print_sequence": "by layer",
"default_acceleration": "1000",
"initial_layer_acceleration": "500",
"top_surface_acceleration": "1000",
"travel_acceleration": "1000",
"inner_wall_acceleration": "1000",
"outer_wall_acceleration": "700",
"bridge_no_support": "0",
"draft_shield": "disabled",
"elefant_foot_compensation": "0",
"enable_arc_fitting": "0",
"wall_infill_order": "inner wall/outer wall/infill",
"infill_direction": "45",
"sparse_infill_density": "15%",
"sparse_infill_pattern": "crosshatch",
"initial_layer_print_height": "0.2",
"infill_combination": "0",
"infill_wall_overlap": "25%",
"interface_shells": "0",
"ironing_flow": "10%",
"ironing_spacing": "0.15",
"ironing_speed": "30",
"ironing_type": "no ironing",
"reduce_infill_retraction": "1",
"filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[initial_tool]}_{printer_model}_{print_time}.gcode",
"detect_overhang_wall": "1",
"slowdown_for_curled_perimeters": "1",
"overhang_1_4_speed": "0",
"overhang_2_4_speed": "50",
"overhang_3_4_speed": "30",
"overhang_4_4_speed": "10",
"line_width": "110%",
"inner_wall_line_width": "110%",
"outer_wall_line_width": "100%",
"top_surface_line_width": "93.75%",
"sparse_infill_line_width": "110%",
"initial_layer_line_width": "120%",
"internal_solid_infill_line_width": "120%",
"support_line_width": "96%",
"wall_loops": "3",
"print_settings_id": "",
"raft_layers": "0",
"seam_position": "aligned",
"skirt_distance": "2",
"skirt_height": "3",
"min_skirt_length": "4",
"skirt_loops": "0",
"minimum_sparse_infill_area": "15",
"spiral_mode": "0",
"standby_temperature_delta": "-5",
"enable_support": "0",
"resolution": "0.012",
"support_type": "normal(auto)",
"support_on_build_plate_only": "0",
"support_top_z_distance": "0.2",
"support_bottom_z_distance": "0.2",
"support_filament": "0",
"support_interface_loop_pattern": "0",
"support_interface_filament": "0",
"support_interface_top_layers": "2",
"support_interface_bottom_layers": "2",
"support_interface_spacing": "0.5",
"support_interface_speed": "80",
"support_base_pattern": "default",
"support_base_pattern_spacing": "2.5",
"support_speed": "150",
"support_threshold_angle": "30",
"support_object_xy_distance": "0.35",
"tree_support_branch_angle": "30",
"tree_support_wall_count": "0",
"detect_thin_wall": "0",
"top_surface_pattern": "monotonicline",
"top_shell_thickness": "0.8",
"enable_prime_tower": "1",
"wipe_tower_no_sparse_layers": "0",
"prime_tower_width": "60",
"xy_hole_compensation": "0",
"xy_contour_compensation": "0",
"layer_height": "0.2",
"bottom_shell_layers": "3",
"top_shell_layers": "4",
"bridge_flow": "1",
"initial_layer_speed": "45",
"initial_layer_infill_speed": "45",
"outer_wall_speed": "45",
"inner_wall_speed": "80",
"sparse_infill_speed": "150",
"internal_solid_infill_speed": "150",
"top_surface_speed": "50",
"gap_infill_speed": "30",
"travel_speed": "200"
}
-54
View File
@@ -1,54 +0,0 @@
{
"name": "Printcepts",
"version": "01.00.00.04",
"force_update": "0",
"description": "Printcepts belt printer configurations",
"machine_model_list": [
{
"name": "BabyBelt Pro",
"sub_path": "machine/BabyBelt Pro.json"
}
],
"process_list": [
{
"name": "fdm_process_common",
"sub_path": "process/fdm_process_common.json"
},
{
"name": "0.20mm Standard @BabyBelt Pro",
"sub_path": "process/0.20mm Standard @BabyBelt Pro.json"
}
],
"filament_list": [
{
"name": "Generic PLA @BabyBelt Pro",
"sub_path": "filament/Generic PLA @BabyBelt Pro.json"
},
{
"name": "eSUN PLA @BabyBelt Pro",
"sub_path": "filament/eSUN PLA @BabyBelt Pro.json"
},
{
"name": "Generic PETG @BabyBelt Pro",
"sub_path": "filament/Generic PETG @BabyBelt Pro.json"
}
],
"machine_list": [
{
"name": "fdm_machine_common",
"sub_path": "machine/fdm_machine_common.json"
},
{
"name": "fdm_klipper_common",
"sub_path": "machine/fdm_klipper_common.json"
},
{
"name": "fdm_belt_common",
"sub_path": "machine/fdm_belt_common.json"
},
{
"name": "BabyBelt Pro 0.4 nozzle",
"sub_path": "machine/BabyBelt Pro 0.4 nozzle.json"
}
]
}
@@ -1,70 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

@@ -1,77 +0,0 @@
{
"type": "filament",
"name": "Generic PETG @BabyBelt Pro",
"inherits": "Generic PETG @System",
"from": "system",
"setting_id": "gCzHpDNgVwQR6tgk",
"instantiation": "true",
"filament_extruder_variant": [
"Direct Drive Standard"
],
"compatible_printers": [
"BabyBelt Pro 0.4 nozzle"
],
"filament_type": [
"PETG"
],
"filament_vendor": [
"Generic"
],
"filament_settings_id": [
"Generic PETG @BabyBelt Pro"
],
"filament_flow_ratio": [
"0.95"
],
"filament_cost": [
"25"
],
"nozzle_temperature": [
"240"
],
"nozzle_temperature_initial_layer": [
"245"
],
"cool_plate_temp": [
"80"
],
"cool_plate_temp_initial_layer": [
"80"
],
"fan_min_speed": [
"40"
],
"fan_max_speed": [
"60"
],
"overhang_fan_threshold": [
"25%"
],
"overhang_fan_speed": [
"80"
],
"full_fan_speed_layer": [
"8"
],
"slow_down_min_speed": [
"20"
],
"slow_down_layer_time": [
"4"
],
"fan_cooling_layer_time": [
"100"
],
"filament_retraction_length": [
"2"
],
"filament_retraction_speed": [
"40"
],
"filament_deretraction_speed": [
"40"
],
"filament_start_gcode": [
"; Generic PETG @BabyBelt Pro — belt PETG, bed 80C"
]
}
@@ -1,65 +0,0 @@
{
"type": "filament",
"name": "Generic PLA @BabyBelt Pro",
"inherits": "Generic PLA @System",
"from": "system",
"setting_id": "24PpcnhVx9v5f4fD",
"instantiation": "true",
"filament_extruder_variant": [
"Direct Drive Standard"
],
"compatible_printers": [
"BabyBelt Pro 0.4 nozzle"
],
"filament_type": [
"PLA"
],
"filament_vendor": [
"Generic"
],
"filament_settings_id": [
"Generic PLA @BabyBelt Pro"
],
"nozzle_temperature": [
"215"
],
"hot_plate_temp": [
"75"
],
"hot_plate_temp_initial_layer": [
"75"
],
"cool_plate_temp": [
"75"
],
"cool_plate_temp_initial_layer": [
"75"
],
"textured_plate_temp": [
"75"
],
"textured_plate_temp_initial_layer": [
"75"
],
"close_fan_the_first_x_layers": [
"3"
],
"full_fan_speed_layer": [
"8"
],
"slow_down_min_speed": [
"20"
],
"filament_retraction_length": [
"1.5"
],
"filament_retraction_speed": [
"35"
],
"filament_deretraction_speed": [
"30"
],
"filament_start_gcode": [
"; Generic PLA @BabyBelt Pro — belt PLA, bed 75C"
]
}
@@ -1,36 +0,0 @@
{
"type": "filament",
"name": "eSUN PLA @BabyBelt Pro",
"inherits": "Generic PLA @BabyBelt Pro",
"from": "system",
"setting_id": "EH3X7oE0DU5tSpjW",
"filament_id": "OFkrxQC4",
"instantiation": "true",
"compatible_printers": [
"BabyBelt Pro 0.4 nozzle"
],
"filament_type": [
"PLA"
],
"filament_vendor": [
"eSUN"
],
"filament_settings_id": [
"eSUN PLA @BabyBelt Pro"
],
"nozzle_temperature_initial_layer": [
"200"
],
"nozzle_temperature": [
"200"
],
"enable_pressure_advance": [
"1"
],
"pressure_advance": [
"0.12"
],
"filament_max_volumetric_speed": [
"20"
]
}
@@ -1,88 +0,0 @@
{
"type": "machine",
"name": "BabyBelt Pro 0.4 nozzle",
"inherits": "fdm_belt_common",
"from": "system",
"setting_id": "34OWINlJpJgA9DwQ",
"instantiation": "true",
"printer_model": "BabyBelt Pro",
"printer_variant": "0.4",
"nozzle_diameter": [
"0.4"
],
"default_filament_profile": [
"Generic PLA @BabyBelt Pro"
],
"default_print_profile": "0.20mm Standard @BabyBelt Pro",
"printable_area": [
"0x0",
"95x0",
"95x500",
"0x500"
],
"printable_height": "100",
"best_object_pos": "0.5,0.05",
"nozzle_type": [
"hardened_steel"
],
"printer_extruder_id": [
"1"
],
"printer_extruder_variant": [
"Direct Drive Standard"
],
"thumbnails": [
"48x48/PNG",
"300x300/PNG"
],
"machine_max_acceleration_e": [
"500",
"5000"
],
"machine_max_acceleration_extruding": [
"500",
"20000"
],
"machine_max_acceleration_retracting": [
"500",
"5000"
],
"machine_max_acceleration_x": [
"500",
"20000"
],
"machine_max_acceleration_y": [
"500",
"20000"
],
"machine_max_junction_deviation": [
"0.01",
"0.01"
],
"machine_max_speed_x": [
"50",
"200"
],
"machine_max_speed_y": [
"50",
"200"
],
"machine_max_speed_z": [
"5",
"12"
],
"retraction_length": [
"1.5"
],
"retraction_speed": [
"20"
],
"deretraction_speed": [
"25"
],
"retract_lift_enforce": [
"Top and Bottom"
],
"support_chamber_temp_control": "0",
"machine_start_gcode": ";Start GCode\nPRINT_START ANGLE=[belt_slice_rotation_angle] EXTRUDER=[nozzle_temperature_initial_layer] BED=[hot_plate_temp_initial_layer] MATERIAL=[filament_type]\n"
}
@@ -1,12 +0,0 @@
{
"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"
}
@@ -1,99 +0,0 @@
{
"type": "machine",
"name": "fdm_belt_common",
"inherits": "fdm_klipper_common",
"from": "system",
"instantiation": "false",
"gcode_flavor": "klipper",
"single_extruder_multi_material": "0",
"default_filament_profile": [
"Generic PLA @System"
],
"default_print_profile": "0.20mm Standard @BabyBelt Pro",
"max_layer_height": [
"0.32"
],
"min_layer_height": [
"0.08"
],
"deretraction_speed": [
"30"
],
"extruder_colour": [
"#FCE94F"
],
"extruder_offset": [
"0x0"
],
"long_retractions_when_cut": [
"0"
],
"nozzle_diameter": [
"0.4"
],
"retract_before_wipe": [
"70%"
],
"retract_length_toolchange": [
"2"
],
"retract_lift_above": [
"0"
],
"retract_lift_below": [
"0"
],
"retract_lift_enforce": [
"All Surfaces"
],
"retract_restart_extra": [
"0"
],
"retract_restart_extra_toolchange": [
"0"
],
"retract_when_changing_layer": [
"1"
],
"retraction_distances_when_cut": [
"18"
],
"retraction_length": [
"0.8"
],
"retraction_minimum_travel": [
"1"
],
"retraction_speed": [
"30"
],
"travel_slope": [
"3"
],
"wipe": [
"1"
],
"wipe_distance": [
"1"
],
"z_hop": [
"0"
],
"z_hop_types": [
"Normal Lift"
],
"gcode_remap_x": "rev_x",
"gcode_remap_y": "pos_z",
"gcode_remap_z": "pos_y",
"printer_extruder_id": [
"1"
],
"belt_printer": "1",
"belt_slice_rotation": "x",
"belt_slice_rotation_angle": "45",
"belt_slice_rotation_global": "1",
"build_plate_tilt_x": "45",
"purge_in_prime_tower": "0",
"scan_first_layer": "0",
"auxiliary_fan": "0"
}
@@ -1,140 +0,0 @@
{
"type": "machine",
"name": "fdm_klipper_common",
"inherits": "fdm_machine_common",
"from": "system",
"instantiation": "false",
"gcode_flavor": "klipper",
"machine_max_acceleration_e": [
"5000",
"5000"
],
"machine_max_acceleration_extruding": [
"20000",
"20000"
],
"machine_max_acceleration_retracting": [
"5000",
"5000"
],
"machine_max_acceleration_travel": [
"20000",
"20000"
],
"machine_max_acceleration_x": [
"20000",
"20000"
],
"machine_max_acceleration_y": [
"20000",
"20000"
],
"machine_max_acceleration_z": [
"500",
"200"
],
"machine_max_speed_e": [
"25",
"25"
],
"machine_max_speed_x": [
"500",
"200"
],
"machine_max_speed_y": [
"500",
"200"
],
"machine_max_speed_z": [
"12",
"12"
],
"machine_max_jerk_e": [
"2.5",
"2.5"
],
"machine_max_jerk_x": [
"9",
"9"
],
"machine_max_jerk_y": [
"9",
"9"
],
"machine_max_jerk_z": [
"0.2",
"0.4"
],
"machine_min_extruding_rate": [
"0",
"0"
],
"machine_min_travel_rate": [
"0",
"0"
],
"max_layer_height": [
"0.32"
],
"min_layer_height": [
"0.08"
],
"printable_height": "250",
"extruder_clearance_radius": "65",
"extruder_clearance_height_to_rod": "36",
"extruder_clearance_height_to_lid": "140",
"printer_settings_id": "",
"printer_technology": "FFF",
"printer_variant": "0.4",
"retraction_minimum_travel": [
"1"
],
"retract_before_wipe": [
"70%"
],
"retract_when_changing_layer": [
"1"
],
"retraction_length": [
"0.8"
],
"retract_length_toolchange": [
"2"
],
"z_hop": [
"0.4"
],
"retract_restart_extra": [
"0"
],
"retract_restart_extra_toolchange": [
"0"
],
"retraction_speed": [
"30"
],
"deretraction_speed": [
"30"
],
"z_hop_types": "Normal Lift",
"single_extruder_multi_material": "1",
"change_filament_gcode": "",
"wipe": [
"1"
],
"default_filament_profile": [
"Generic PLA @System"
],
"default_print_profile": "0.20mm Standard @MyKlipper",
"bed_exclude_area": [
"0x0"
],
"machine_start_gcode": "M190 S[bed_temperature_initial_layer_single]\nM109 S[nozzle_temperature_initial_layer]\nPRINT_START EXTRUDER=[nozzle_temperature_initial_layer] BED=[bed_temperature_initial_layer_single]\n",
"machine_end_gcode": "PRINT_END",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n",
"machine_pause_gcode": "PAUSE",
"scan_first_layer": "0",
"nozzle_type": "undefine",
"auxiliary_fan": "0"
}
@@ -1,118 +0,0 @@
{
"type": "machine",
"name": "fdm_machine_common",
"from": "system",
"instantiation": "false",
"printer_technology": "FFF",
"deretraction_speed": [
"40"
],
"extruder_colour": [
"#FCE94F"
],
"extruder_offset": [
"0x0"
],
"gcode_flavor": "marlin",
"machine_max_acceleration_e": [
"5000"
],
"machine_max_acceleration_extruding": [
"10000"
],
"machine_max_acceleration_retracting": [
"1000"
],
"machine_max_acceleration_x": [
"10000"
],
"machine_max_acceleration_y": [
"10000"
],
"machine_max_acceleration_z": [
"500"
],
"machine_max_speed_e": [
"60"
],
"machine_max_speed_x": [
"500"
],
"machine_max_speed_y": [
"500"
],
"machine_max_speed_z": [
"10"
],
"machine_max_jerk_e": [
"5"
],
"machine_max_jerk_x": [
"8"
],
"machine_max_jerk_y": [
"8"
],
"machine_max_jerk_z": [
"0.4"
],
"machine_min_extruding_rate": [
"0"
],
"machine_min_travel_rate": [
"0"
],
"max_layer_height": [
"0.32"
],
"min_layer_height": [
"0.08"
],
"printable_height": "250",
"extruder_clearance_radius": "65",
"extruder_clearance_height_to_rod": "36",
"extruder_clearance_height_to_lid": "140",
"nozzle_diameter": [
"0.4"
],
"printer_settings_id": "",
"printer_variant": "0.4",
"retraction_minimum_travel": [
"2"
],
"retract_before_wipe": [
"70%"
],
"retract_when_changing_layer": [
"1"
],
"retraction_length": [
"1"
],
"retract_length_toolchange": [
"1"
],
"z_hop": [
"0"
],
"retract_restart_extra": [
"0"
],
"retract_restart_extra_toolchange": [
"0"
],
"retraction_speed": [
"60"
],
"single_extruder_multi_material": "1",
"change_filament_gcode": "",
"wipe": [
"1"
],
"default_print_profile": "",
"machine_start_gcode": "G0 Z20 F9000\nG92 E0; G1 E-10 F1200\nG28\nM970 Q1 A10 B10 C130 K0\nM970 Q1 A10 B131 C250 K1\nM974 Q1 S1 P0\nM970 Q0 A10 B10 C130 H20 K0\nM970 Q0 A10 B131 C250 K1\nM974 Q0 S1 P0\nM220 S100 ;Reset Feedrate\nM221 S100 ;Reset Flowrate\nG29 ;Home\nG90;\nG92 E0 ;Reset Extruder \nG1 Z2.0 F3000 ;Move Z Axis up \nG1 X10.1 Y20 Z0.28 F5000.0 ;Move to start position\nM109 S205;\nG1 X10.1 Y200.0 Z0.28 F1500.0 E15 ;Draw the first line\nG1 X10.4 Y200.0 Z0.28 F5000.0 ;Move to side a little\nG1 X10.4 Y20 Z0.28 F1500.0 E30 ;Draw the second line\nG92 E0 ;Reset Extruder \nG1 X110 Y110 Z2.0 F3000 ;Move Z Axis up",
"machine_end_gcode": "M400 ; wait for buffer to clear\nG92 E0 ; zero the extruder\nG1 E-4.0 F3600; retract \nG91\nG1 Z3;\nM104 S0 ; turn off hotend\nM140 S0 ; turn off bed\nM106 S0 ; turn off fan\nG90 \nG0 X110 Y200 F3600 \nprint_end",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n",
"machine_pause_gcode": "M601"
}
@@ -1,23 +0,0 @@
{
"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"
]
}
@@ -1,106 +0,0 @@
{
"type": "process",
"name": "fdm_process_common",
"from": "system",
"instantiation": "false",
"reduce_crossing_wall": "0",
"max_travel_detour_distance": "0",
"bottom_surface_pattern": "monotonic",
"bottom_shell_thickness": "0",
"bridge_speed": "50",
"brim_width": "5",
"brim_object_gap": "0.1",
"compatible_printers": [],
"compatible_printers_condition": "",
"print_sequence": "by layer",
"default_acceleration": "1000",
"initial_layer_acceleration": "500",
"top_surface_acceleration": "1000",
"travel_acceleration": "1000",
"inner_wall_acceleration": "1000",
"outer_wall_acceleration": "700",
"bridge_no_support": "0",
"draft_shield": "disabled",
"elefant_foot_compensation": "0",
"enable_arc_fitting": "0",
"wall_infill_order": "inner wall/outer wall/infill",
"infill_direction": "45",
"sparse_infill_density": "15%",
"sparse_infill_pattern": "crosshatch",
"initial_layer_print_height": "0.2",
"infill_combination": "0",
"infill_wall_overlap": "25%",
"interface_shells": "0",
"ironing_flow": "10%",
"ironing_spacing": "0.15",
"ironing_speed": "30",
"ironing_type": "no ironing",
"reduce_infill_retraction": "1",
"filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[initial_tool]}_{printer_model}_{print_time}.gcode",
"detect_overhang_wall": "1",
"slowdown_for_curled_perimeters": "1",
"overhang_1_4_speed": "0",
"overhang_2_4_speed": "50",
"overhang_3_4_speed": "30",
"overhang_4_4_speed": "10",
"line_width": "110%",
"inner_wall_line_width": "110%",
"outer_wall_line_width": "100%",
"top_surface_line_width": "93.75%",
"sparse_infill_line_width": "110%",
"initial_layer_line_width": "120%",
"internal_solid_infill_line_width": "120%",
"support_line_width": "96%",
"wall_loops": "3",
"print_settings_id": "",
"raft_layers": "0",
"seam_position": "aligned",
"skirt_distance": "2",
"skirt_height": "3",
"min_skirt_length": "4",
"skirt_loops": "0",
"minimum_sparse_infill_area": "15",
"spiral_mode": "0",
"standby_temperature_delta": "-5",
"enable_support": "0",
"resolution": "0.012",
"support_type": "normal(auto)",
"support_on_build_plate_only": "0",
"support_top_z_distance": "0.2",
"support_bottom_z_distance": "0.2",
"support_filament": "0",
"support_interface_loop_pattern": "0",
"support_interface_filament": "0",
"support_interface_top_layers": "2",
"support_interface_bottom_layers": "2",
"support_interface_spacing": "0.5",
"support_interface_speed": "80",
"support_base_pattern": "default",
"support_base_pattern_spacing": "2.5",
"support_speed": "150",
"support_threshold_angle": "30",
"support_object_xy_distance": "0.35",
"tree_support_branch_angle": "30",
"tree_support_wall_count": "0",
"detect_thin_wall": "0",
"top_surface_pattern": "monotonicline",
"top_shell_thickness": "0.8",
"enable_prime_tower": "1",
"wipe_tower_no_sparse_layers": "0",
"prime_tower_width": "60",
"xy_hole_compensation": "0",
"xy_contour_compensation": "0",
"layer_height": "0.2",
"bottom_shell_layers": "3",
"top_shell_layers": "4",
"bridge_flow": "1",
"initial_layer_speed": "45",
"initial_layer_infill_speed": "45",
"outer_wall_speed": "45",
"inner_wall_speed": "80",
"sparse_infill_speed": "150",
"internal_solid_infill_speed": "150",
"top_surface_speed": "50",
"gap_infill_speed": "30",
"travel_speed": "200"
}
-1
View File
@@ -26,7 +26,6 @@ struct SlopeDetection
bool actived;
float normal_z;
mat3 volume_world_normal_matrix;
vec3 up_direction;
};
uniform vec4 uniform_color;
+2 -3
View File
@@ -23,7 +23,6 @@ struct SlopeDetection
bool actived;
float normal_z;
mat3 volume_world_normal_matrix;
vec3 up_direction;
};
uniform mat4 view_model_matrix;
@@ -78,8 +77,8 @@ void main()
// Point in homogenous coordinates.
world_pos = volume_world_matrix * vec4(v_position, 1.0);
// dot product of world normal with up direction, used for slope shading
world_normal_z = slope.actived ? dot(normalize(slope.volume_world_normal_matrix * v_normal), slope.up_direction) : 0.0;
// z component of normal vector in world coordinate used for slope shading
world_normal_z = slope.actived ? (normalize(slope.volume_world_normal_matrix * v_normal)).z : 0.0;
gl_Position = projection_matrix * position;
if (is_outline) {
+1 -2
View File
@@ -37,7 +37,6 @@ struct SlopeDetection
bool actived;
float normal_z;
mat3 volume_world_normal_matrix;
vec3 up_direction;
};
uniform SlopeDetection slope;
@@ -86,7 +85,7 @@ void main()
color = LightBlue;
alpha = 1.0;
}
else if( dot(transformed_normal, slope.up_direction) < slope.normal_z - EPSILON)
else if( transformed_normal.z < slope.normal_z - EPSILON)
{
color = color * 0.5 + LightRed * 0.5;
alpha = 1.0;
-1
View File
@@ -24,7 +24,6 @@ struct SlopeDetection
bool actived;
float normal_z;
mat3 volume_world_normal_matrix;
vec3 up_direction;
};
uniform SlopeDetection slope;
void main()
+2 -3
View File
@@ -7,7 +7,6 @@ struct SlopeDetection
bool actived;
float normal_z;
mat3 volume_world_normal_matrix;
vec3 up_direction;
};
uniform mat4 view_model_matrix;
@@ -47,8 +46,8 @@ void main()
// Point in homogenous coordinates.
world_pos = volume_world_matrix * vec4(v_position, 1.0);
// dot product of world normal with up direction, used for slope shading
world_normal_z = slope.actived ? dot(normalize(slope.volume_world_normal_matrix * v_normal), slope.up_direction) : 0.0;
// z component of normal vector in world coordinate used for slope shading
world_normal_z = slope.actived ? (normalize(slope.volume_world_normal_matrix * v_normal)).z : 0.0;
gl_Position = projection_matrix * position;
if (is_outline) {
-1
View File
@@ -29,7 +29,6 @@ struct SlopeDetection
bool actived;
float normal_z;
mat3 volume_world_normal_matrix;
vec3 up_direction;
};
uniform vec4 uniform_color;
+2 -3
View File
@@ -23,7 +23,6 @@ struct SlopeDetection
bool actived;
float normal_z;
mat3 volume_world_normal_matrix;
vec3 up_direction;
};
uniform mat4 view_model_matrix;
@@ -78,8 +77,8 @@ void main()
// Point in homogenous coordinates.
world_pos = volume_world_matrix * vec4(v_position, 1.0);
// dot product of world normal with up direction, used for slope shading
world_normal_z = slope.actived ? dot(normalize(slope.volume_world_normal_matrix * v_normal), slope.up_direction) : 0.0;
// z component of normal vector in world coordinate used for slope shading
world_normal_z = slope.actived ? (normalize(slope.volume_world_normal_matrix * v_normal)).z : 0.0;
gl_Position = projection_matrix * position;
if (is_outline) {
+1 -2
View File
@@ -37,7 +37,6 @@ struct SlopeDetection
bool actived;
float normal_z;
mat3 volume_world_normal_matrix;
vec3 up_direction;
};
uniform SlopeDetection slope;
@@ -88,7 +87,7 @@ void main()
color = LightBlue;
alpha = 1.0;
}
else if( dot(transformed_normal, slope.up_direction) < slope.normal_z - EPSILON)
else if( transformed_normal.z < slope.normal_z - EPSILON)
{
color = color * 0.5 + LightRed * 0.5;
alpha = 1.0;
-1
View File
@@ -24,7 +24,6 @@ struct SlopeDetection
bool actived;
float normal_z;
mat3 volume_world_normal_matrix;
vec3 up_direction;
};
uniform SlopeDetection slope;
void main()
+2 -3
View File
@@ -7,7 +7,6 @@ struct SlopeDetection
bool actived;
float normal_z;
mat3 volume_world_normal_matrix;
vec3 up_direction;
};
uniform mat4 view_model_matrix;
@@ -47,8 +46,8 @@ void main()
// Point in homogenous coordinates.
world_pos = volume_world_matrix * vec4(v_position, 1.0);
// dot product of world normal with up direction, used for slope shading
world_normal_z = slope.actived ? dot(normalize(slope.volume_world_normal_matrix * v_normal), slope.up_direction) : 0.0;
// z component of normal vector in world coordinate used for slope shading
world_normal_z = slope.actived ? (normalize(slope.volume_world_normal_matrix * v_normal)).z : 0.0;
gl_Position = projection_matrix * position;
if (is_outline) {
+5
View File
@@ -4,5 +4,10 @@
<dict>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<!-- The Bambu network plug-in's code protector rewrites one of its own signed code pages
after loading. Under the hardened runtime macOS kills the process when that page is
paged back in; this lets it run, as Bambu Studio's signature does. -->
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
</dict>
</plist>
+1 -7
View File
@@ -91,12 +91,6 @@ if (SLIC3R_GUI)
# list(REMOVE_ITEM wxWidgets_LIBRARIES oleacc)
find_package(wxInspector REQUIRED)
# wxInspector 1.0.0 installs its headers but accidentally declares the
# INSTALL_INTERFACE include directory PRIVATE, so its imported target does
# not expose them to consumers. Restore the package prefix include path until
# the upstream export is fixed.
get_filename_component(WXINSPECTOR_PREFIX "${wxInspector_DIR}/../../.." ABSOLUTE)
target_include_directories(wxInspector::wxInspector INTERFACE "${WXINSPECTOR_PREFIX}/include")
# wxInspector's exported interface names the release wxWidgets import
# libraries, which a Debug build cannot link. wx is linked above instead.
@@ -192,7 +186,7 @@ endif ()
# Add the Slic3r GUI library, libcurl, OpenGL and GLU libraries.
if (SLIC3R_GUI)
# target_link_libraries(OrcaSlicer ws2_32 uxtheme setupapi libslic3r_gui ${wxWidgets_LIBRARIES})
target_link_libraries(OrcaSlicer libslic3r_gui wxInspector::wxInspector)
target_link_libraries(OrcaSlicer libslic3r_gui)
if (MSVC)
# Generate debug symbols even in release mode.
target_link_options(OrcaSlicer PUBLIC "$<$<CONFIG:RELEASE>:/DEBUG>")
+13 -46
View File
@@ -3434,14 +3434,9 @@ int CLI::run(int argc, char **argv)
max_self_index = std::max(max_self_index, v);
min_self_index = std::min(min_self_index, v);
}
// And a project saved with FEWER filaments than are now loaded (a
// one-filament project sliced with two --load-filaments) leaves the tables half filled:
// the variant matching below then reads past filament_extruder_variant and
// set_with_restore_2 throws an uncaught size error. Regenerate in that case too.
if (max_self_index > filament_count || min_self_index < 1 || max_self_index < filament_count
|| (int) filament_self_index_opt->values.size() < filament_count) {
BOOST_LOG_TRIVIAL(warning) << boost::format("filament_self_index range [%1%, %2%] (size %4%) is invalid for filament_count %3%, regenerating")
% min_self_index % max_self_index % filament_count % filament_self_index_opt->values.size();
if (max_self_index > filament_count || min_self_index < 1) {
BOOST_LOG_TRIVIAL(warning) << boost::format("filament_self_index range [%1%, %2%] is invalid for filament_count %3%, regenerating")
% min_self_index % max_self_index % filament_count;
need_regenerate_self_index = true;
}
}
@@ -3532,10 +3527,6 @@ int CLI::run(int argc, char **argv)
std::vector<string>& filament_variants = curr_variant_opt->values;
filament_variants.resize(filament_count, get_extruder_variant_string(etDirectDrive, nvtStandard));
}
// See the filament_self_index note above: one variant per filament for
// the filaments the project did not know about.
if ((int) curr_variant_opt->values.size() < filament_count)
curr_variant_opt->values.resize(filament_count, get_extruder_variant_string(etDirectDrive, nvtStandard));
const ConfigOptionStrings *new_variant_opt = dynamic_cast<const ConfigOptionStrings*>(config.option("filament_extruder_variant", true));
std::vector<int> new_variant_indice;
@@ -3544,7 +3535,7 @@ int CLI::run(int argc, char **argv)
for (int i = 0; i < new_variant_count; i++)
{
for (int j = old_start_indice[filament_index - 1]; j < old_start_indice[filament_index - 1] + old_variant_count && j < (int) curr_variant_opt->values.size(); j++)
for (int j = old_start_indice[filament_index - 1]; j < old_start_indice[filament_index - 1] + old_variant_count; j++)
{
if (curr_variant_opt->values[j] == new_variant_opt->values[i]) {
new_variant_indice[i] = j;
@@ -3596,18 +3587,7 @@ int CLI::run(int argc, char **argv)
ConfigOptionVectorBase* opt_vec_dst = static_cast<ConfigOptionVectorBase*>(opt);
const ConfigOptionVectorBase* opt_vec_src = static_cast<const ConfigOptionVectorBase*>(source_opt);
//set with index
try {
// A project with fewer filaments than are loaded: grow the
// destination to the filament's slot first (set_with_restore_2 only restores).
if (opt_vec_src->size() > 0 && opt_vec_dst->size() < size_t(old_start_indice[filament_index - 1] + old_variant_count))
opt_vec_dst->resize(size_t(old_start_indice[filament_index - 1] + old_variant_count), opt_vec_src);
opt_vec_dst->set_with_restore_2(opt_vec_src, new_variant_indice, old_start_indice[filament_index - 1], old_variant_count);
} catch (const std::exception &ex) { // Was an uncaught abort
BOOST_LOG_TRIVIAL(error) << boost::format("filament %1%: option %2% could not be applied: %3%") % filament_index % opt_key % ex.what();
boost::nowide::cerr << "filament " << filament_index << ": option " << opt_key << " could not be applied: " << ex.what() << std::endl;
record_exit_reson(outfile_dir, CLI_CONFIG_FILE_ERROR, 0, cli_errors[CLI_CONFIG_FILE_ERROR], sliced_info);
flush_and_exit(CLI_CONFIG_FILE_ERROR);
}
opt_vec_dst->set_with_restore_2(opt_vec_src, new_variant_indice, old_start_indice[filament_index - 1], old_variant_count);
}
continue;
@@ -3653,16 +3633,7 @@ int CLI::run(int argc, char **argv)
if (filament_options_with_variant.find(opt_key) != filament_options_with_variant.end()) {
std::vector<int> temp_variant_indice;
temp_variant_indice.resize(new_variant_count, -1);
try {
if (opt_vec_src->size() > 0 && opt_vec_dst->size() < size_t(old_start_indice[filament_index - 1] + old_variant_count)) // See above
opt_vec_dst->resize(size_t(old_start_indice[filament_index - 1] + old_variant_count), opt_vec_src);
opt_vec_dst->set_with_restore_2(opt_vec_src, temp_variant_indice, old_start_indice[filament_index - 1], old_variant_count, true);
} catch (const std::exception &ex) { // Was an uncaught abort
BOOST_LOG_TRIVIAL(error) << boost::format("filament %1%: option %2% could not be applied: %3%") % filament_index % opt_key % ex.what();
boost::nowide::cerr << "filament " << filament_index << ": option " << opt_key << " could not be applied: " << ex.what() << std::endl;
record_exit_reson(outfile_dir, CLI_CONFIG_FILE_ERROR, 0, cli_errors[CLI_CONFIG_FILE_ERROR], sliced_info);
flush_and_exit(CLI_CONFIG_FILE_ERROR);
}
opt_vec_dst->set_with_restore_2(opt_vec_src, temp_variant_indice, old_start_indice[filament_index - 1], old_variant_count, true);
if (opt_key == "filament_extruder_variant")
new_variant_counts[filament_index - 1] = opt_vec_src->size();
@@ -4168,10 +4139,6 @@ int CLI::run(int argc, char **argv)
BOOST_LOG_TRIVIAL(info) << boost::format("%1%, set disable_wipe_tower_after_mapping back to false due to wrapping detect")%__LINE__;
}
// Belt printers never get the classic wipe tower (see Print::has_wipe_tower()), so reserve no space for it.
const ConfigOptionBool* belt_printer_opt = m_print_config.option<ConfigOptionBool>("belt_printer");
const bool is_belt_printer = belt_printer_opt && belt_printer_opt->value;
auto timelapse_type_opt = m_print_config.option("timelapse_type");
bool is_smooth_timelapse = false;
if (enable_timelapse && timelapse_type_opt && (timelapse_type_opt->getInt() == TimelapseType::tlSmooth))
@@ -4409,11 +4376,11 @@ int CLI::run(int argc, char **argv)
}
};
auto check_plate_wipe_tower = [get_print_sequence, is_smooth_timelapse, is_belt_printer](Slic3r::GUI::PartPlate* plate, int plate_index, DynamicPrintConfig& print_config, plate_obj_size_info_t &plate_obj_size_info) {
auto check_plate_wipe_tower = [get_print_sequence, is_smooth_timelapse](Slic3r::GUI::PartPlate* plate, int plate_index, DynamicPrintConfig& print_config, plate_obj_size_info_t &plate_obj_size_info) {
plate_obj_size_info.obj_bbox= plate->get_objects_bounding_box();
BOOST_LOG_TRIVIAL(info) << boost::format("plate %1%, object bbox: min {%2%, %3%, %4%} - max {%5%, %6%, %7%}")
%(plate_index+1) %plate_obj_size_info.obj_bbox.min.x() % plate_obj_size_info.obj_bbox.min.y() % plate_obj_size_info.obj_bbox.min.z() %plate_obj_size_info.obj_bbox.max.x() % plate_obj_size_info.obj_bbox.max.y() % plate_obj_size_info.obj_bbox.max.z();
if (is_belt_printer || !print_config.has("wipe_tower_x")) {
if (!print_config.has("wipe_tower_x")) {
plate_obj_size_info.has_wipe_tower = false;
BOOST_LOG_TRIVIAL(info) << boost::format("can not found wipe_tower_x in config, set to no wipe tower");
return;
@@ -5283,7 +5250,7 @@ int CLI::run(int argc, char **argv)
}
}
if (!is_belt_printer && ((!arrange_cfg.is_seq_print && (assemble_plate.filaments_count > 1)) || (enable_wrapping_detect && !current_wrapping_exclude_area.empty())))
if ((!arrange_cfg.is_seq_print && (assemble_plate.filaments_count > 1))||(enable_wrapping_detect && !current_wrapping_exclude_area.empty()))
{
//prepare the wipe tower
int plate_count = partplate_list.get_plate_count();
@@ -5435,7 +5402,7 @@ int CLI::run(int argc, char **argv)
bool is_seq_print = false;
get_print_sequence(cur_plate, m_print_config, is_seq_print);
if (!is_belt_printer && !is_seq_print && (assemble_plate.filaments_count > 1) && !has_wipe_tower_position)
if (!is_seq_print && (assemble_plate.filaments_count > 1) && !has_wipe_tower_position)
{
//prepare the wipe tower
auto printer_structure_opt = m_print_config.option<ConfigOptionEnum<PrinterStructure>>("printer_structure");
@@ -5603,7 +5570,7 @@ int CLI::run(int argc, char **argv)
};
const int max_filament_count = plate_count > 0 ? *std::max_element(plate_filament_counts.begin(), plate_filament_counts.end()) : 0;
if (!is_belt_printer && plate_needs_wipe_tower(max_filament_count))
if (plate_needs_wipe_tower(max_filament_count))
{
//prepare the wipe tower
auto printer_structure_opt = m_print_config.option<ConfigOptionEnum<PrinterStructure>>("printer_structure");
@@ -5710,7 +5677,7 @@ int CLI::run(int argc, char **argv)
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": found single object mode");
}
if (!is_belt_printer && m_print_config.has("wipe_tower_x") && (is_smooth_timelapse || !arrange_cfg.is_seq_print || (selected.size() <= 1))) {
if (m_print_config.has("wipe_tower_x") && (is_smooth_timelapse || !arrange_cfg.is_seq_print || (selected.size() <= 1))) {
float x;
float y;
if (duplicate_count > 0) {
@@ -6335,7 +6302,7 @@ int CLI::run(int argc, char **argv)
// The stored (or default) tower position may not fit the tower these plates
// need, and no CLI placement site runs on a plain slice - mirror the GUI's
// reload clamp and fit every plate's tower into the printable area first.
if (!is_belt_printer && m_print_config.option<ConfigOptionBool>("enable_prime_tower", true)->value) {
if (m_print_config.option<ConfigOptionBool>("enable_prime_tower", true)->value) {
for (int index = 0; index < partplate_list.get_plate_count(); index++) {
if ((plate_to_slice != 0) && (plate_to_slice != (index + 1)))
continue;
@@ -521,16 +521,11 @@ int slice_all_printers(const std::string &vendor, const std::string &outdir)
const std::string filament_name = bundle.filaments.get_selected_preset_name();
const std::string what = "Printer \"" + printer + "\"";
const std::string file_base = sanitize_filename(vendor_name) + "__" + sanitize_filename(printer);
// A belt printer has no wipe tower (it purges into a prism object on the belt), so its
// filament change is the plain tool change set_extruder() emits rather than the tower's block.
const bool belt = bundle.printers.get_selected_preset().config.opt_bool("belt_printer");
const std::string marker = belt ? "\nT1\n" : "CP TOOLCHANGE START";
if (const std::string out = slice_selection(bundle, what, false, outdir, file_base); out.empty())
++failures;
else if (out.find(marker) == std::string::npos) {
// The filament change never fired, so change_filament_gcode was not exercised.
BOOST_LOG_TRIVIAL(error) << what << " sliced but the filament change never fired (no "
<< (belt ? "T1" : "CP TOOLCHANGE START") << ")";
else if (out.find("CP TOOLCHANGE START") == std::string::npos) {
// The filament change never rode the tower, so change_filament_gcode was not exercised.
BOOST_LOG_TRIVIAL(error) << what << " sliced but the filament change never fired (no CP TOOLCHANGE START)";
++failures;
}
cover(bundle.prints.get_selected_preset());
@@ -5,5 +5,10 @@
<!-- for dynamic loading of libraries without signature validation. Used for 3dconnection drivers.-->
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<!-- The Bambu network plug-in's code protector rewrites one of its own signed code pages
after loading. Under the hardened runtime macOS kills the process when that page is
paged back in; this lets it run, as Bambu Studio's signature does. -->
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
</dict>
</plist>
+9 -84
View File
@@ -300,15 +300,7 @@ Points get_shrink_bedpts(const DynamicPrintConfig* print_cfg, const ArrangeParam
template<class PConf>
void fill_config(PConf& pcfg, const ArrangeParams &params) {
if (params.is_belt) {
// Pack from the end of the belt that prints first, and keep the pile on the
// bed when it is larger than the room around that end.
pcfg.starting_point = !params.belt_reversed ? PConf::Alignment::BOTTOM_LEFT :
params.belt_axis == 1 ? PConf::Alignment::TOP_LEFT :
PConf::Alignment::BOTTOM_RIGHT;
pcfg.clamp_to_bin = true;
}
else if (params.is_seq_print) {
if (params.is_seq_print) {
// Start placing the items from the center of the print bed
pcfg.starting_point = PConf::Alignment::BOTTOM_LEFT;
}
@@ -453,50 +445,6 @@ protected:
return bindist;
}
// Belt printers pack from the end of the belt that prints first, and a corner
// packer's checks (pile inside the bin, pack origin) apply to them as well.
bool corner_packing() const { return params.is_belt || m_pconf.starting_point == PConfig::Alignment::BOTTOM_LEFT; }
static double at(const Box::PointType &pt, int i) { return double(i == 0 ? getX(pt) : getY(pt)); }
// Position along the belt in print order: increasing from the end that prints first.
double belt_pos(const Box::PointType &pt) const { return params.belt_reversed ? -at(pt, params.belt_axis) : at(pt, params.belt_axis); }
double belt_start(const Box &bb) const { return belt_pos(params.belt_reversed ? bb.maxCorner() : bb.minCorner()); }
double belt_end(const Box &bb) const { return belt_pos(params.belt_reversed ? bb.minCorner() : bb.maxCorner()); }
// The corner of the bin the belt pile grows from.
Box::PointType belt_origin() const
{
const Box bb = sl::boundingBox(m_bin);
auto o = bb.minCorner();
if (params.belt_reversed) {
if (params.belt_axis == 0) setX(o, getX(bb.maxCorner()));
else setY(o, getY(bb.maxCorner()));
}
return o;
}
// An item's far edge in print order is what it costs (so a row fills across the
// belt before the pile advances), with a slight pull toward the near lateral
// edge and the same penalty as the bottom-left heuristic for sitting outside
// the corner.
double dist_along_belt(const Box &ibb)
{
const Box bin = sl::boundingBox(m_bin);
const int l = 1 - params.belt_axis;
const double lat = at(ibb.minCorner(), l) - at(bin.minCorner(), l);
double d = belt_end(ibb) - belt_start(bin);
d += lat < 0 ? 10 * -lat : 0.1 * lat;
if (double behind = belt_start(ibb) - belt_start(bin); behind < 0)
d += 10 * -behind;
return norm(d);
}
double corner_bindist(const Box &ibb, const Slic3r::Point &origin_pack)
{
return params.is_belt ? dist_along_belt(ibb) : dist_for_BOTTOM_LEFT(ibb, origin_pack);
}
double dist_to_bin(const Box& ibb, const Slic3r::Point& origin_pack, typename Packer::PlacementConfig::Alignment starting_point_alignment)
{
double bindist = 0;
@@ -586,8 +534,8 @@ protected:
// The smalles distance from the arranged pile center:
double dist = norm(*(std::min_element(dists.begin(), dists.end())));
if (corner_packing()) {
double bindist = corner_bindist(ibb, origin_pack);
if (m_pconf.starting_point == PConfig::Alignment::BOTTOM_LEFT) {
double bindist = dist_for_BOTTOM_LEFT(ibb, origin_pack);
score = 0.2 * dist + 0.8 * bindist;
}
else {
@@ -644,8 +592,8 @@ protected:
break;
}
case LAST_BIG_ITEM: {
if (corner_packing()) {
score = corner_bindist(ibb, origin_pack);
if (m_pconf.starting_point == PConfig::Alignment::BOTTOM_LEFT) {
score = dist_for_BOTTOM_LEFT(ibb, origin_pack);
}
else {
if (m_pilebb.defined)
@@ -660,8 +608,8 @@ protected:
// already processed bigger items.
// No need to play around with the anchor points, the center will be
// just fine for small items
if (corner_packing())
score = corner_bindist(ibb, origin_pack);
if (m_pconf.starting_point == PConfig::Alignment::BOTTOM_LEFT)
score = dist_for_BOTTOM_LEFT(ibb, origin_pack);
else {
// Align mainly around existing items
score = 0.8 * norm(pl::distance(ibb.center(), bigbb.center()))+ 0.2*norm(pl::distance(ibb.center(), origin_pack));
@@ -762,28 +710,6 @@ protected:
score += 1 * (new_extruder_cnt-last_extruder_cnt);
}
// On a belt the parts print in belt order, so every colour change between
// parts is a filament change. Items arrive sorted by extruder and the pile
// grows from the leading end; keep each colour's run contiguous by charging
// an item for every packed item of another colour it does not fully follow,
// counting the tilted layers that reach belt_tilt_slope * height past that
// item's far edge.
if (params.is_belt && !params.is_seq_print) {
const std::set<int> item_colours(item.extrude_ids.begin(), item.extrude_ids.end());
const double item_start = belt_start(ibb);
for (Item &p : m_items) {
if (p.is_virt_object)
continue;
const std::set<int> p_colours(p.extrude_ids.begin(), p.extrude_ids.end());
const bool same_colour = std::includes(item_colours.begin(), item_colours.end(), p_colours.begin(), p_colours.end())
|| std::includes(p_colours.begin(), p_colours.end(), item_colours.begin(), item_colours.end());
if (same_colour)
continue;
if (item_start < belt_end(p.boundingBox()) + scaled(p.height * params.belt_tilt_slope))
score += 10.;
}
}
return std::make_tuple(score, fullbb);
}
@@ -860,8 +786,7 @@ public:
auto binbb = sl::boundingBox(m_bin);
auto starting_point = this->params.is_belt ? belt_origin() :
cfg.starting_point == PConfig::Alignment::BOTTOM_LEFT ? binbb.minCorner() : binbb.center();
auto starting_point = cfg.starting_point == PConfig::Alignment::BOTTOM_LEFT ? binbb.minCorner() : binbb.center();
// if we have wipe tower, items should be arranged around wipe tower
for (Item itm : items) {
if (itm.is_wipe_tower) {
@@ -1012,7 +937,7 @@ std::function<double(const Item &, const ItemGroup&)> AutoArranger<ExPolygon>::g
auto mp = m_merged_pile;
mp.emplace_back(itm.transformedShape());
auto chull = sl::convexHull(mp);
if (corner_packing())
if (m_pconf.starting_point == PConfig::Alignment::BOTTOM_LEFT)
{
if (!sl::isInside(chull, m_bin))
score += LARGE_COST_TO_REJECT;
-7
View File
@@ -146,13 +146,6 @@ struct ArrangeParams {
float nozzle_height = 0;
float printable_height = 256.0;
Vec2d align_center{ 0.5,0.5 };
// Belt printer: items print in the order they lie along the belt axis, from
// its low end unless belt_reversed, and a part's top prints
// belt_tilt_slope * height further along it than its base.
bool is_belt = false;
int belt_axis = 1; // 0 = X, 1 = Y
bool belt_reversed = false;
float belt_tilt_slope = 1.f; // cot(belt tilt angle), 0 when the belt is not tilted
ArrangePolygons excluded_regions; // regions cant't be used
ArrangePolygons nonprefered_regions; // regions can be used but not prefered
-554
View File
@@ -1,554 +0,0 @@
#include <limits>
#include "BeltBrim.hpp"
#include "ClipperUtils.hpp"
#include "Flow.hpp"
#include "Layer.hpp"
#include "Polygon.hpp"
#include "Print.hpp"
#include "ShortestPath.hpp"
#include "Support/BeltFloorContext.hpp"
#include "BoundingBox.hpp"
#include "ExPolygon.hpp"
#include "ExtrusionEntity.hpp"
#include "ExtrusionEntityCollection.hpp"
#include "Point.hpp"
#include "Polyline.hpp"
#include "PrintConfig.hpp"
#include "libslic3r.h"
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <utility>
#include <vector>
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;
}
ExPolygons belt_brim_clip_leading_edge(const ExPolygons &region, const BeltBrimFrame &frame, coordf_t u_cut)
{
if (region.empty())
return region;
BoundingBox keep_bb = get_extents(region);
keep_bb.offset(scale_(1.));
const bool low_side = frame.downhill_sign() < 0; // downhill is -u
const Polygon keep = band_box(keep_bb, frame.from_axis,
low_side ? unscale<double>(frame.from_axis == 0 ? keep_bb.min.x() : keep_bb.min.y()) : u_cut,
low_side ? u_cut : unscale<double>(frame.from_axis == 0 ? keep_bb.max.x() : keep_bb.max.y()));
return keep.empty() ? ExPolygons{} : intersection_ex(region, Polygons{ keep });
}
// Everything the per-band line generator needs, gathered once per object.
struct BeltBrimContext
{
BeltFloorContext ctx;
BeltBrimFrame frame;
ExPolygons region; // brim region, object-local slicing XY
BoundingBox region_bbox;
Flow brim_flow;
coord_t pitch_u = 0;
coord_t u_anchor = 0;
double in_plane_pitch = 0.; // mm
};
// Emit the cross-belt brim lines that belong to the band [print_z - height, print_z].
static void belt_brim_band_paths(const BeltBrimContext &bc,
coordf_t print_z,
coordf_t height,
const Polygons &obstacles,
ExtrusionEntityCollection &out,
ExPolygons &areas_out)
{
coordf_t u_lo = bc.ctx.cutoff_u(print_z - height);
coordf_t u_hi = bc.ctx.cutoff_u(print_z);
if (u_lo > u_hi)
std::swap(u_lo, u_hi);
// How wide this band is measured ON the belt, versus one nominal bead.
const double band_in_plane = (u_hi - u_lo) * bc.frame.u_stretch();
// Fraction of the layer height at which a line sits above the belt. Toward the
// downhill edge, so the sheet is reasonably thick while the nozzle stays clear of
// the belt itself.
static constexpr double BAND_CLEARANCE_FRACTION = 0.75;
std::vector<coord_t> us;
double uniform_clearance = 0.; // 0 => derive per line from its own position
double line_pitch = bc.in_plane_pitch;
// One line also serves a band up to half a bead wider than the nominal pitch (a 0.3 mm
// first layer at 45 degrees): its flow is matched to the band, so the bead is that much
// wider. Two lattice lines in such a band would land almost on top of each other.
if (band_in_plane <= 1.5 * bc.in_plane_pitch + EPSILON) {
// Steep belt, which is the normal case: the band is narrower than one bead, so
// exactly one line fits. Place it at a FIXED fraction of the band rather than
// on a nominal-spacing lattice. On a lattice each line lands at an arbitrary
// point in its band, the clearance sweeps [0, height] from band to band, and the
// bead width therefore varies by 2x - visible as ragged, uneven brim lines.
// Anchoring to the band makes the clearance identical everywhere, so every bead
// is the same width.
//
// The spacing is then whatever the bands give (height / sin(tilt) on the belt)
// rather than the nominal bead spacing, so the flow below is matched to THAT
// pitch. Matched flow at the real pitch is what keeps the sheet uniform and
// gap-free; using nominal flow at band spacing would over-feed it.
us.push_back(scale_(bc.ctx.cutoff_u(print_z - BAND_CLEARANCE_FRACTION * height)));
uniform_clearance = BAND_CLEARANCE_FRACTION * height;
line_pitch = band_in_plane;
} else {
// Shallow belt: the band is wider than a bead, so it takes several lines and they
// have to sit on the nominal lattice. Their clearances then differ, and so do
// their widths - unavoidable here, but shallow belts are the rare case.
us = belt_brim_line_positions(scale_(u_lo), scale_(u_hi), bc.pitch_u, bc.u_anchor);
}
if (us.empty())
return;
const Polygons region_polys = to_polygons(bc.region);
// One lattice line at a time: the clearance - and therefore the extrusion
// volume - is a property of the line's u, so the pieces of different lines
// must not be pooled before the flow is resolved.
// Overshoot the region so the clip, not the line's ends, decides the extent.
const coord_t margin = coord_t(SCALED_EPSILON) + 1;
coord_t u_prev = std::numeric_limits<coord_t>::min();
for (coord_t u : us) {
// Nozzle-to-belt clearance for this line. Constant along the line, because the
// belt height depends only on the shear-axis coordinate. Band-anchored lines
// share one clearance by construction; lattice lines (shallow belts, or a first
// layer thick enough that the band is wider than a bead) each get their own.
//
// A lattice line can fall where the belt is only a hair below the band's print_z.
// The bead there would be laid scraping the belt while its flow is sized for a
// taller cell, so it is moved uphill to the same fraction of the band the
// single-line case uses. (The clearance is along slice Z; the real gap under the
// nozzle is clearance x cos(tilt), 0.53 h at 45 degrees for the 0.75 fraction.)
double clearance = uniform_clearance;
if (clearance <= 0.) {
const Point probe = bc.frame.from_axis == 0 ? Point(u, 0) : Point(0, u);
clearance = print_z - bc.ctx.floor_print_z(probe);
if (clearance < BAND_CLEARANCE_FRACTION * height) {
clearance = BAND_CLEARANCE_FRACTION * height;
u = scale_(bc.ctx.cutoff_u(print_z - clearance));
}
clearance = std::min(clearance, height);
}
// A line moved uphill can land on, or almost on, its neighbour; two beads closer
// than half a pitch would be laid into the same cell.
if (u_prev != std::numeric_limits<coord_t>::min() && std::abs(u - u_prev) < bc.pitch_u / 2)
continue;
u_prev = u;
Polyline line;
if (bc.frame.from_axis == 0)
line.points = { Point(u, coord_t(bc.region_bbox.min.y() - margin)),
Point(u, coord_t(bc.region_bbox.max.y() + margin)) };
else
line.points = { Point(coord_t(bc.region_bbox.min.x() - margin), u),
Point(coord_t(bc.region_bbox.max.x() + margin), u) };
Polylines pieces = intersection_pl(Polylines{ line }, region_polys);
if (! obstacles.empty())
pieces = diff_pl(pieces, obstacles);
if (pieces.empty())
continue;
// with_cross_section, not with_height: it reaches the prescribed volume while
// KEEPING the extrusion spacing, so the bead is sized to fill exactly one
// pitch x clearance cell of the sheet.
const Flow f = bc.brim_flow.with_cross_section(float(line_pitch * clearance));
// Footprint of these beads, for the first-layer convex hull and bbox.
for (const Polygon &p : offset(pieces, 0.5f * float(f.scaled_width())))
areas_out.emplace_back(ExPolygon(p));
extrusion_entities_append_paths(out.entities, chain_polylines(std::move(pieces)),
erBrim, f.mm3_per_mm(), f.width(), float(clearance));
}
}
// Union of everything extruded at `print_z` that the brim must keep clear of, expressed
// in `self`'s local slicing frame. Includes `self` itself: its slice at this Z can
// overhang outside the belt footprint and land in the brim ring, which the flattened
// brim_object_gap - a belt-plane separation - does not cover.
//
// THREADING: this runs inside posSupportMaterial, which Print::process() executes for all
// objects in a tbb::parallel_for (Print.cpp). Object slices are finished by then and safe
// to read across objects, but SUPPORT layers are not: another object's thread may be
// inside clear_support_layers() - which deletes the SupportLayer pointers - right now, so
// touching a foreign object's support_layers() here is a use-after-free. Only this
// object's own supports are consulted; they are complete, because make_belt_brim() runs at
// the tail of this object's own generate_support_material(). The cost is that the brim
// does not dodge a *different* object's support at the same Z, which needs the objects to
// overlap in the belt direction in the first place.
// `region_bbox` bounds the brim; anything outside it cannot clip a brim line, so whole
// objects are skipped without materialising their polygons. On a typical plate the
// objects do not overlap and every foreign object drops out here, which matters because
// this runs once per band - hundreds of times per object.
static Polygons belt_brim_obstacles(const Print &print, const PrintObject &self,
const BoundingBox &region_bbox, coordf_t print_z, coordf_t tol)
{
const Point shift_self = self.instances().empty() ? Point(0, 0)
: self.instances().front().shift_without_plate_offset();
Polygons out;
for (const PrintObject *o : print.objects()) {
const bool is_self = (o == &self);
for (const PrintInstance &inst : o->instances()) {
const Point delta = inst.shift_without_plate_offset() - shift_self;
if (const Layer *l = o->get_layer_at_printz(print_z, tol)) {
BoundingBox lb = get_extents(l->lslices);
lb.translate(delta.x(), delta.y());
if (lb.overlap(region_bbox)) {
Polygons ps = to_polygons(l->lslices);
for (Polygon &p : ps)
p.translate(delta);
polygons_append(out, std::move(ps));
}
}
if (! is_self)
continue;
if (const SupportLayer *sl = o->get_support_layer_at_printz(print_z, tol)) {
Polygons ps = sl->support_fills.polygons_covered_by_spacing();
for (Polygon &p : ps)
p.translate(delta);
polygons_append(out, std::move(ps));
}
}
}
if (out.size() < 2)
return out; // union_() of 0 or 1 polygons is pure overhead
return union_(out);
}
void make_belt_brim(PrintObject &object)
{
object.clear_belt_brim();
if (! object.has_belt_brim())
return;
const Print &print = *object.print();
BeltBrimContext bc;
if (! bc.ctx.init(object.slicing_parameters(), print.config()))
return;
bc.frame = BeltBrimFrame{ bc.ctx.shear_factor(), bc.ctx.from_axis() };
const size_t nlayers = object.layers().size();
if (nlayers == 0)
return;
// 1. Belt footprint: the union of each layer's slice clipped to that layer's
// own contact band. This is the object's bottom face, which on a belt is
// spread over every layer instead of sitting in layer 0.
ExPolygons footprint_acc;
for (size_t i = 0; i < nlayers; ++ i) {
const Layer &layer = *object.layers()[i];
if (layer.lslices.empty())
continue;
// print_z - height, not the previous layer's print_z: variable layer
// heights make the latter wrong.
coordf_t u_lo = bc.ctx.cutoff_u(layer.print_z - layer.height);
coordf_t u_hi = bc.ctx.cutoff_u(layer.print_z);
if (u_lo > u_hi)
std::swap(u_lo, u_hi);
BoundingBox bb = get_extents(layer.lslices);
bb.offset(scale_(1.));
const Polygon band = band_box(bb, bc.frame.from_axis, u_lo, u_hi);
if (band.empty())
continue;
expolygons_append(footprint_acc, intersection_ex(layer.lslices, Polygons{ band }));
}
const ExPolygons footprint = union_ex(footprint_acc);
if (footprint.empty())
return;
// 2. Brim region, offset in the flattened (true on-belt) metric.
const PrintObjectConfig &cfg = object.config();
bc.brim_flow = print.brim_flow();
const double flow_w = bc.brim_flow.scaled_spacing() * SCALING_FACTOR;
// Quantize to an even number of lines, as the plate brim does.
const coord_t width = scale_(std::floor(cfg.brim_width.value / flow_w / 2) * flow_w * 2);
const coord_t leading = scale_(cfg.leading_brim_length.value);
const coord_t lateral = scale_(cfg.extra_brim_width.value);
const coord_t gap = scale_(cfg.brim_object_gap.value);
// Belt printers collapse Auto / Mouse ear / Painted to outer-only: the auto width
// heuristic and flat ear discs have no meaning on a tilted plane. Leading-edge-only
// is an outer brim too; it is narrowed down to the first contact below.
const BrimType bt = cfg.brim_type.value;
const bool has_outer = bt == btOuterOnly || bt == btOuterAndInner
|| bt == btAutoBrim || bt == btEar || bt == btPainted
|| bt == btLeadingEdgeOnly;
const bool has_inner = bt == btInnerOnly || bt == btOuterAndInner;
bc.region = belt_unflatten(
belt_brim_region(belt_flatten(footprint, bc.frame), has_outer, has_inner,
width, gap, leading, lateral, bc.frame),
bc.frame);
if (bt == btLeadingEdgeOnly && ! bc.region.empty())
// The cut is the uphill edge of the first layer's contact band: everything
// past it belongs to later contacts.
bc.region = belt_brim_clip_leading_edge(bc.region, bc.frame,
bc.ctx.cutoff_u(object.layers().front()->print_z));
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
-178
View File
@@ -1,178 +0,0 @@
#ifndef slic3r_BeltBrim_hpp_
#define slic3r_BeltBrim_hpp_
#include "ExPolygon.hpp"
#include "ExtrusionEntityCollection.hpp"
#include "Point.hpp"
#include "Polyline.hpp"
#include "libslic3r.h"
#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);
// "Leading edge only": keep the part of a brim region (unflattened, slicing XY)
// 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. `u_cut`
// is the uphill edge of the first layer's contact band along `frame.from_axis`,
// in mm (BeltFloorContext::cutoff_u of the first layer); downhill is the side
// `frame.downhill_sign()` points to.
ExPolygons belt_brim_clip_leading_edge(const ExPolygons &region, const BeltBrimFrame &frame, coordf_t u_cut);
// 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_
-73
View File
@@ -1,73 +0,0 @@
#include "BeltGCode.hpp"
#include "GCodeWriter.hpp"
#include "GCode/BeltKinematics.hpp"
#include "BeltTransform.hpp"
#include "Print.hpp"
#include "Point.hpp"
#include "PrintConfig.hpp"
#include "libslic3r.h"
#include <cstdlib>
namespace Slic3r {
void BeltGCode::init_belt_writer(Print &print)
{
// Axis remap and build volume max are set by base GCode after init_belt_writer
// returns; set_kinematics() replays them, so install order does not matter.
install_belt_kinematics(m_writer, print.config());
m_writer.set_force_normal_lift(true);
}
void BeltGCode::write_belt_header(GCodeOutputStream &file, const Print &print)
{
const auto &full_cfg = print.full_print_config();
// Slicing rotation: the belt tilt (axis + angle) and the single source of truth
// for the physical tilt the G-code viewer uses to enable belt view.
file.write_format("; belt_slice_rotation = %s\n", full_cfg.opt_serialize("belt_slice_rotation").c_str());
file.write_format("; belt_slice_rotation_angle = %.1f\n", print.config().belt_slice_rotation_angle.value);
file.write_format("; belt_slice_rotation_global = %d\n", print.config().belt_slice_rotation_global.value ? 1 : 0);
// Pre-slice remap configs
file.write_format("; preslice_remap_x = %s\n", full_cfg.opt_serialize("preslice_remap_x").c_str());
file.write_format("; preslice_remap_y = %s\n", full_cfg.opt_serialize("preslice_remap_y").c_str());
file.write_format("; preslice_remap_z = %s\n", full_cfg.opt_serialize("preslice_remap_z").c_str());
file.write_format("; preslice_remap_global = %d\n", print.config().preslice_remap_global.value ? 1 : 0);
file.write_format("; belt_preslice_global = %d\n", print.config().belt_preslice_global.value ? 1 : 0);
// Machine-frame transform: shear (tan) + scale (1/cos) derived from the belt
// tilt angle (or belt_frame_tilt_angle when decoupled).
file.write_format("; belt_frame_tilt_decouple = %d\n", print.config().belt_frame_tilt_decouple.value ? 1 : 0);
file.write_format("; belt_frame_tilt_angle = %.1f\n", print.config().belt_frame_tilt_angle.value);
}
void BeltGCode::on_set_origin(const PrintObject * /*obj*/, const Point & /*inst_shift*/)
{
// Global pre-slice mode: adjust origin using computed correction.
// Transform the origin through the belt pipeline so that
// back_transform(T * origin) = origin (correct machine position).
//
// Flags that trigger this path:
// belt_preslice_global — full pipeline (rotation * remap) is global
// preslice_remap_global — only the pre-slice remap is global
// belt_slice_rotation_global — slicing rotation treated as global (matches
// the per-instance Z-offset added in PrintObjectSlice.cpp)
// The XY origin adjustment uses the FULL forward transform, because the
// back_transform applied during G-code emission is always the inverse of
// the full pipeline.
bool use_global = m_config.belt_preslice_global.value
|| (m_config.preslice_remap_global.value
&& BeltTransformPipeline::has_preslice_remap(m_config))
|| (m_config.belt_slice_rotation_global.value
&& m_config.belt_slice_rotation.value != BeltRotationAxis::None
&& std::abs(m_config.belt_slice_rotation_angle.value) > EPSILON);
if (!use_global)
return;
// Adjust origin: transform through belt forward pipeline so that
// the back-transform correctly recovers model-space positions.
Transform3d T = BeltTransformPipeline::build_forward_transform(m_config);
Vec2d cur_origin = this->origin();
Vec3d origin3d(cur_origin.x(), cur_origin.y(), 0.);
Vec3d adjusted = T.linear() * origin3d;
this->set_origin(Vec2d(adjusted.x(), adjusted.y()));
}
} // namespace Slic3r
-25
View File
@@ -1,25 +0,0 @@
#pragma once
#include "GCode.hpp"
#include "Point.hpp"
#include "Print.hpp"
namespace Slic3r {
// Belt-printer-specific GCode export.
//
// Inherits from GCode and overrides virtual hooks to:
// - Install a BeltKinematics on the GCodeWriter
// - Write belt configuration to the G-code header
// - Adjust the origin for global pre-slice transforms when switching instances
// (Arc fitting is disabled for belt printers by BeltKinematics::supports_arc_moves(),
// which the base GCode::should_disable_arc_fitting() consults -- no override needed.)
class BeltGCode : public GCode
{
protected:
void init_belt_writer(Print &print) override;
void write_belt_header(GCodeOutputStream &file, const Print &print) override;
void on_set_origin(const PrintObject *obj, const Point &inst_shift) override;
};
} // namespace Slic3r
-433
View File
@@ -1,433 +0,0 @@
// 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 "BeltBrim.hpp"
#include "PrintBase.hpp"
#include <algorithm>
#include <cmath>
#include <limits>
#include <boost/log/trivial.hpp>
#include <cassert>
#include <cstddef>
#include <functional>
#include <utility>
#include <vector>
namespace Slic3r {
// Belt purge prism: purging after filament changes is routed into a sliced
// prism object via the flush-into-objects machinery instead of a wipe tower.
bool Print::has_belt_purge_tower() const
{
// Its own purge-tower "type", gated by the belt-only enable_belt_purge_tower
// option (not the classic enable_prime_tower).
if (!(m_config.belt_printer.value
&& m_config.enable_belt_purge_tower.value
&& !m_config.spiral_mode.value
&& m_config.filament_diameter.values.size() > 1))
return false;
return std::any_of(m_objects.begin(), m_objects.end(), [](const PrintObject *object) {
return object->config().belt_purge_tower_object.value;
});
}
// Belt mode: align ALL objects on the plate (the printed objects AND the purge
// prism) onto one common layer grid, so the prism can absorb every toolchange.
//
// After belt slicing each object's layer print_z carries a per-object global z
// offset (mesh-vertex-scan belt_z_shift + instance-Y-dependent terms), so
// objects at different belt-Y positions get layer grids with DIFFERENT residues
// (mod layer height). Purge marking looks absorbers up with
// get_layer_at_printz(lt.print_z, EPSILON), so a toolchange on object B only
// absorbs into the prism if the prism has a layer at B's print_z. Snapping only
// the prism to one object therefore worked for a single (assembled) multi-color
// object but failed with multiple separate objects — the prism could follow only
// one grid, and toolchanges on the others went unabsorbed ("multiple layer
// grids" warning).
//
// Fix: pick one reference grid (the tallest object) and shift every object onto
// it. Each shift is at most half a layer height — a sub-100µm move along the
// belt, the very same mechanism the per-object global_z_offset already uses, and
// it keeps each object internally consistent (belt_shift_layer_grid moves the
// object's layers, its support layers, and its belt floor together). Equal layer
// height across objects is enforced by Print::validate(), so once residues match
// every object steps on the same lattice {ref_offset + k*h} and every toolchange
// layer coincides with a prism layer.
void Print::_align_belt_purge_layers()
{
PrintObject *prism = nullptr;
for (PrintObject *po : m_objects)
if (po->config().belt_purge_tower_object.value && !po->layers().empty()) {
prism = po;
break;
}
if (prism == nullptr || prism->layers().empty())
return;
const double h = prism->config().layer_height.value;
if (h <= EPSILON)
return;
// Grid residue of an object's layer grid: identical for all of an object's
// layers above the first since they step by h.
auto grid_offset = [h](const PrintObject *po) -> double {
if (po->layers().empty())
return 0.;
const double z = po->layers().front()->print_z;
return z - std::floor(z / h) * h; // in [0, h)
};
// Reference grid: the tallest non-prism object (proxy for the object with
// the most toolchange layers — minimizes how far the rest must move).
const PrintObject *ref = nullptr;
double ref_top = -std::numeric_limits<double>::max();
for (const PrintObject *po : m_objects) {
if (po->config().belt_purge_tower_object.value || po->layers().empty())
continue;
const double top = po->layers().back()->print_z;
if (top > ref_top) {
ref_top = top;
ref = po;
}
}
if (ref == nullptr)
return;
const double ref_offset = grid_offset(ref);
// Snap every object (printed objects AND the prism) onto the reference grid.
for (PrintObject *po : m_objects) {
if (po->layers().empty())
continue;
double delta = ref_offset - grid_offset(po);
if (delta > 0.5 * h)
delta -= h;
else if (delta <= -0.5 * h)
delta += h;
po->belt_shift_layer_grid(delta); // no-op for the reference object (delta ~ 0)
}
}
// Belt mode replacement for _make_wipe_tower(): plan filament-change purging
// into the belt purge prism (and any other flush_into_* object) using the
// flush-into-objects machinery, without generating classic wipe tower G-code.
// The toolchange itself is emitted by GCode::set_extruder() via the
// change_filament_gcode macro; the overrides marked here make the new
// filament's first extrusions land in the purge prism.
void Print::_plan_belt_purge()
{
m_wipe_tower_data.clear();
// psWipeTower may be invalidated without posSlice (for example after a
// filament-map or tool-ordering change). Restore a prism shortened by the
// previous plan so a newly higher toolchange can use its original layers.
for (PrintObject *po : m_objects)
if (po->config().belt_purge_tower_object.value)
po->belt_undo_purge_plan();
// Must run before ToolOrdering is built: LayerTools merge per-object layer
// print_z values, and the prism only absorbs purge where its (snapped)
// layers coincide with the toolchange layers.
this->_align_belt_purge_layers();
const unsigned int number_of_extruders = (unsigned int) m_config.filament_colour.values.size();
// No initial priming extrusions: there is no tower to prime on.
m_wipe_tower_data.tool_ordering = ToolOrdering(*this, (unsigned int) -1, false);
m_wipe_tower_data.tool_ordering.sort_and_build_data(*this, (unsigned int) -1, false);
if (m_wipe_tower_data.tool_ordering.empty() || m_wipe_tower_data.tool_ordering.last_extruder() == unsigned(-1))
throw Slic3r::SlicingError("The print is empty. The model is not printable with current print settings.");
// Is there any filament change at all? Not ToolOrdering::has_wipe_tower(): that reads the
// FIRST layer's flag, and on a belt the first layer may be a brim apron band, which carries
// neither object nor support and so never gets the flag even when the print changes filament.
{
bool any_change = false;
unsigned int cur = m_wipe_tower_data.tool_ordering.first_extruder();
for (const auto &lt : m_wipe_tower_data.tool_ordering.layer_tools())
for (const unsigned int e : lt.extruders)
if (e != cur) { any_change = true; cur = e; }
if (! any_change)
return;
}
this->throw_if_canceled();
// Flush volumes per filament pair, mirroring the generic wipe tower path:
// full flush matrix for single extruder multi material with purging enabled,
// plain prime volume otherwise.
std::vector<float> flush_matrix(cast<float>(
get_flush_volumes_matrix(m_config.flush_volumes_matrix.values, 0, m_config.nozzle_diameter.values.size())));
std::vector<std::vector<float>> wipe_volumes;
for (unsigned int i = 0; i < number_of_extruders; ++i)
wipe_volumes.push_back(std::vector<float>(flush_matrix.begin() + i * number_of_extruders,
flush_matrix.begin() + (i + 1) * number_of_extruders));
const bool use_flush_matrix = m_config.purge_in_prime_tower && m_config.single_extruder_multi_material;
const float flush_multiplier = (float) m_config.flush_multiplier.get_at(0);
// Cancel the purge prism early: pre-scan the tool ordering for the highest
// print_z that actually has a toolchange, then drop the prism's layers above
// it so the tower stops at the last color swap (saves filament/time). This
// MUST happen before the marking loop below: ensure_perimeters_infills_order
// force-overrides the prism's extrusions on every layer (it is a dedicated
// flush object), so truncating afterwards would leave dangling overrides
// pointing into deleted layers.
{
// The tool ordering covers the WHOLE print, and the prism is a printed
// object in it. Left unbounded, the scan below sees the prism's own
// toolchanges on layers above every model object -- the prism runs past
// them by design (ramp/height compensation at the tilted ends) -- so
// last_tc_z lands at the prism's own top and the truncation cancels
// nothing. The tower ends up justifying its own existence.
//
// Nothing above the tallest printed object can require a color change,
// so bound the scan there. On MCTEST5 that is 197 toolchanges spanning
// z=154.00..193.20 with the tallest object topping out at 153.80, i.e.
// 39.4 mm of tower that no swap ever needed.
// Support layers count too: on a belt they can extend above the object's
// own top, and a toolchange there is a real one.
double obj_top_z = -1.;
for (const PrintObject *po : m_objects) {
if (po->config().belt_purge_tower_object.value)
continue;
if (!po->layers().empty())
obj_top_z = std::max(obj_top_z, po->layers().back()->print_z);
if (!po->support_layers().empty())
obj_top_z = std::max(obj_top_z, po->support_layers().back()->print_z);
}
double last_tc_z = -1.;
unsigned int cur_ext = m_wipe_tower_data.tool_ordering.first_extruder();
for (const auto &lt : m_wipe_tower_data.tool_ordering.layer_tools()) {
// layer_tools() is ordered by print_z ascending.
if (obj_top_z >= 0. && lt.print_z > obj_top_z + EPSILON)
break;
for (const unsigned int e : lt.extruders)
if (e != cur_ext) { last_tc_z = lt.print_z; cur_ext = e; }
}
// Deliberately NOT cancelling the prism outright when no object toolchange
// exists: belt_truncate_layers_above(0.) empties m_layers, and an object
// with zero layers is not something the rest of the pipeline expects. The
// GUI already declines to create a prism unless more than one filament is
// in use, so this case is a stale prism, not a hot path -- leave it whole
// rather than risk a zero-layer object.
if (last_tc_z >= 0.)
for (PrintObject *po : m_objects)
if (po->config().belt_purge_tower_object.value && !po->layers().empty()) {
po->belt_truncate_layers_above(last_tc_z);
break;
}
}
// The prism only absorbs purge at toolchange layers whose print_z coincides
// with one of its own layers.
PrintObject *prism_po = nullptr;
for (PrintObject *po : m_objects)
if (po->config().belt_purge_tower_object.value && !po->layers().empty()) { prism_po = po; break; }
float total_leftover = 0.f;
float worst_layer_leftover = 0.f;
double worst_layer_z = 0.;
unsigned int current_extruder_id = m_wipe_tower_data.tool_ordering.first_extruder();
for (auto &layer_tools : m_wipe_tower_data.tool_ordering.layer_tools()) {
float layer_leftover = 0.f;
for (const unsigned int extruder_id : layer_tools.extruders) {
if (extruder_id == current_extruder_id)
continue;
float volume_to_wipe = use_flush_matrix ?
wipe_volumes[current_extruder_id][extruder_id] * flush_multiplier :
(float) m_config.prime_volume;
float leftover = layer_tools.wiping_extrusions().mark_wiping_extrusions(*this, current_extruder_id, extruder_id,
volume_to_wipe);
layer_leftover += leftover;
current_extruder_id = extruder_id;
}
// Plastic saving: drop the prism's fills that no toolchange on this layer
// claimed. At this point the prism's OVERRIDDEN fills are exactly the
// purge; the rest would print as solid infill in the prism's own filament
// for nothing -- which is the whole prism on a layer with no toolchange
// (141 of 692 layers on MCTEST5 before the truncation fix). Perimeters are
// left alone so the bar keeps a continuous wall along the belt.
//
// Non-destructive: the entities are stashed with their positions and put
// back by belt_restore_dropped_fills() at the top of the next plan. An
// earlier version deleted them outright, which broke replanning when a
// later tool ordering needed what this one had not claimed -- that is why
// it was removed rather than kept.
if (prism_po != nullptr) {
const auto &we = layer_tools.wiping_extrusions();
prism_po->belt_drop_unclaimed_fills(
prism_po->get_layer_at_printz(layer_tools.print_z, EPSILON),
[&we, prism_po](const ExtrusionEntity *e) { return we.is_entity_overridden(e, prism_po, 0); });
}
layer_tools.wiping_extrusions().ensure_perimeters_infills_order(*this);
if (layer_leftover > 0.f) {
total_leftover += layer_leftover;
if (layer_leftover > worst_layer_leftover) {
worst_layer_leftover = layer_leftover;
worst_layer_z = layer_tools.print_z;
}
}
this->throw_if_canceled();
}
if (total_leftover > 1.f) {
this->active_step_add_warning(
PrintStateBase::WarningLevel::CRITICAL,
Slic3r::format(_u8L("The belt purge tower cannot absorb the full purge volume: %1% mm³ in total could not "
"be purged (worst layer: %2% mm³ at height %3%). The print may show color bleeding. "
"Increase the belt purge tower width, or reduce flushing volumes."),
int(std::ceil(total_leftover)), int(std::ceil(worst_layer_leftover)),
Slic3r::float_to_string_decimal_point(worst_layer_z, 2)));
}
}
// Belt mode: shift the sliced layer grid by delta. Mirrors the global_z_offset
// application in slice() — layer print_z and belt_floor_z_shift move together
// so belt floor clipping stays consistent with the shifted grid. Used by
// Print::_align_belt_purge_layers() to snap the purge prism onto the printed
// objects' layer grid; |delta| <= half a layer height, i.e. a sub-layer shift
// of the prism along the belt.
void PrintObject::belt_shift_layer_grid(double delta)
{
if (std::abs(delta) < EPSILON)
return;
for (Layer *layer : m_layers)
layer->print_z += delta;
for (SupportLayer *layer : m_support_layers)
layer->print_z += delta;
// The brim's apron bands below the first layer carry their own print_z.
for (BeltBrimBand &band : m_belt_brim_prologue)
band.print_z += delta;
m_slicing_params.belt_floor_z_shift += delta;
// The grid stays shifted across a support-only or brim-only change (posSlice does
// not rerun), so everything slice() derived from it has to follow: the cached floor
// that update_slicing_parameters() restores, and the global offset the organic
// support layers and the adaptive infill octree are placed with. Left alone, the
// next alignment finds a delta of 0 and the floor and the supports sit up to half
// a layer off the grid, unlike a fresh slice.
if (m_belt_floor_z_shift_cache_valid)
m_belt_floor_z_shift_cached += delta;
m_belt_global_z_offset += delta;
}
// Belt mode: drop layers strictly above z (used to cancel the purge prism early
// once there are no more toolchanges above z, so the tower stops at the last
// color swap instead of wasting filament up the rest of the belt). Each layer's
// cross-section is already sliced, so removing upper layers does not affect the
// last toolchange's coverage. Deletes the Layer objects and clears the new top
// layer's upper-layer link. Returns the number of layers removed.
size_t PrintObject::belt_truncate_layers_above(coordf_t z)
{
// A repeated plan always starts from the restored full layer set.
assert(m_belt_truncated_layers.empty());
size_t keep = m_layers.size();
while (keep > 0 && m_layers[keep - 1]->print_z > z + EPSILON)
--keep;
if (keep >= m_layers.size())
return 0;
const size_t removed = m_layers.size() - keep;
m_belt_truncated_layers.assign(m_layers.begin() + keep, m_layers.end());
m_layers.resize(keep);
if (!m_layers.empty())
m_layers.back()->upper_layer = nullptr;
return removed;
}
// Plastic saving on the purge prism: keep only the fills a toolchange claimed.
//
// Called per layer from _plan_belt_purge(), after the real-purge marking and
// BEFORE ensure_perimeters_infills_order() -- that pass force-overrides every
// remaining fill on the prism (it is a dedicated flush object), so afterwards
// everything looks claimed and nothing could be distinguished.
size_t PrintObject::belt_drop_unclaimed_fills(Layer *layer, const std::function<bool(const ExtrusionEntity*)> &claimed)
{
if (layer == nullptr)
return 0;
size_t dropped = 0;
for (size_t ri = 0; ri < layer->regions().size(); ++ri) {
LayerRegion *lr = layer->get_region(ri);
auto &ents = lr->fills.entities;
ExtrusionEntitiesPtr keep;
keep.reserve(ents.size());
for (size_t i = 0; i < ents.size(); ++i) {
if (claimed(ents[i])) {
keep.emplace_back(ents[i]);
} else {
// Stash with its original index so the restore is exact.
m_belt_dropped_fills.push_back(BeltDroppedFill{ layer, ri, i, ents[i] });
++dropped;
}
}
ents = std::move(keep);
}
return dropped;
}
void PrintObject::belt_restore_dropped_fills()
{
if (m_belt_dropped_fills.empty())
return;
// Ascending index per (layer, region): inserting in that order lands every
// entity back at its original position, because each insertion shifts only
// the entries after it, which are themselves still to be inserted.
std::stable_sort(m_belt_dropped_fills.begin(), m_belt_dropped_fills.end(),
[](const BeltDroppedFill &a, const BeltDroppedFill &b) {
if (a.layer != b.layer) return a.layer < b.layer;
if (a.region_idx != b.region_idx) return a.region_idx < b.region_idx;
return a.index < b.index;
});
for (const BeltDroppedFill &d : m_belt_dropped_fills) {
auto &ents = d.layer->get_region(d.region_idx)->fills.entities;
ents.insert(ents.begin() + std::min(d.index, ents.size()), d.entity);
}
m_belt_dropped_fills.clear();
}
void PrintObject::belt_restore_truncated_layers()
{
if (m_belt_truncated_layers.empty())
return;
m_layers.insert(m_layers.end(), m_belt_truncated_layers.begin(), m_belt_truncated_layers.end());
m_belt_truncated_layers.clear();
for (size_t i = 0; i < m_layers.size(); ++i) {
m_layers[i]->lower_layer = i == 0 ? nullptr : m_layers[i - 1];
m_layers[i]->upper_layer = i + 1 < m_layers.size() ? m_layers[i + 1] : nullptr;
}
}
} // namespace Slic3r
-70
View File
@@ -1,70 +0,0 @@
#include "BeltSliceStrategy.hpp"
#include "Model.hpp"
#include "BeltTransform.hpp"
#include "Point.hpp"
#include "PrintConfig.hpp"
#include <limits>
#include <algorithm>
namespace Slic3r {
void BeltSliceStrategy::apply_preslice_transforms(Transform3d &trafo,
const PrintConfig &config,
const ModelVolumePtrs &model_volumes,
double *out_belt_min_z)
{
// 1. Standalone pre-slice axis remap (works without belt mode).
const bool has_remap = BeltTransformPipeline::has_preslice_remap(config);
if (has_remap)
trafo = BeltTransformPipeline::build_preslice_remap(config) * trafo;
// 2. Belt rotation — the sole mesh-side belt transform (matching
// BeltTransformPipeline::build_forward_transform). Only active in
// belt-printer mode.
bool has_rotation = false;
if (config.belt_printer.value) {
const Matrix3d rot = BeltTransformPipeline::build_rotation_matrix(config, &has_rotation);
if (has_rotation) {
Transform3d belt_xform = Transform3d::Identity();
belt_xform.linear() = rot;
trafo = belt_xform * trafo;
}
}
if (!has_remap && !has_rotation)
return;
// 3. Z-shift — detect if the mesh clips below the build plate after the
// transforms and lift it. Each mesh vertex must be brought into object space
// via mv->get_matrix() before applying the full trafo (which is in object
// space). Missing this on assemblies (where per-volume get_matrix() positions
// each volume within the object) would compute min_z against mesh-local vertex
// coordinates rather than object-space coordinates, so volumes translated along
// the slicer's Z axis would be silently excluded from the bound check.
double min_z = std::numeric_limits<double>::max();
for (const ModelVolume *mv : model_volumes) {
if (!mv->is_model_part()) continue;
Transform3d vol_trafo = trafo * mv->get_matrix();
const auto &its = mv->mesh().its;
for (const stl_vertex &v : its.vertices) {
Vec3d vm = v.cast<double>();
Vec3d pt = vol_trafo * vm;
min_z = std::min(min_z, pt.z());
}
}
const double z_shift_val = (min_z < 0. && min_z != std::numeric_limits<double>::max()) ? -min_z : 0.;
if (z_shift_val > 0.) {
Transform3d z_shift = Transform3d::Identity();
z_shift.matrix()(2, 3) = z_shift_val;
trafo = z_shift * trafo;
}
// out_belt_min_z is only meaningful in belt mode; the standalone-remap path
// never reported it.
if (out_belt_min_z && config.belt_printer.value) {
*out_belt_min_z = (min_z != std::numeric_limits<double>::max()) ? min_z : 0.;
}
}
} // namespace Slic3r
-36
View File
@@ -1,36 +0,0 @@
#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
-230
View File
@@ -1,230 +0,0 @@
#include "BeltTransform.hpp"
#include "Model.hpp"
#include "BoundingBox.hpp"
#include "Config.hpp"
#include "Geometry.hpp"
#include "Point.hpp"
#include "PrintConfig.hpp"
#include "libslic3r.h"
#include <limits>
#include <algorithm>
#include <cmath>
#include <cstdlib>
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)
{
if (!has_preslice_remap(config))
return bb; // Identity remap, or belt mode off.
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);
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
-168
View File
@@ -1,168 +0,0 @@
#pragma once
#include "libslic3r.h"
#include "Point.hpp"
#include "BoundingBox.hpp"
#include "PrintConfig.hpp"
#include "Geometry.hpp"
#include "Config.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 --------------------------------------------------
// Whether the axis remaps (preslice_remap_* and gcode_remap_*) apply at all.
// The remap fields are only offered in the belt printer group, so a value
// left in a profile must not change a non-belt print: with belt mode off every
// belt-only key is a no-op. This is the one place to widen if a non-belt use
// ever needs them.
static bool axis_remap_enabled(const PrintConfig &config) { return config.belt_printer.value; }
static bool axis_remap_enabled(const DynamicPrintConfig &config)
{
auto *opt = config.option<ConfigOptionBool>("belt_printer");
return opt != nullptr && opt->value;
}
static bool has_preslice_remap(const PrintConfig &config)
{
return axis_remap_enabled(config) &&
(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)
{
if (! axis_remap_enabled(config))
return false;
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
+1 -14
View File
@@ -474,9 +474,7 @@ static ExPolygons outer_inner_brim_area(const Print& print,
const bool use_brim_ears = object->config().brim_type == btPainted;
const bool use_inner_brim_ears = (use_auto_brim_ears || use_brim_ears) && !object->config().brim_ears_outer_only.value;
const bool has_inner_brim = brim_type == btInnerOnly || brim_type == btOuterAndInner || use_inner_brim_ears;
// 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;
const bool has_outer_brim = brim_type == btOuterOnly || brim_type == btOuterAndInner || brim_type == btAutoBrim || use_auto_brim_ears || use_brim_ears;
coord_t ear_detection_length = scale_(object->config().brim_ears_detection_length.value);
coordf_t brim_ears_max_angle = object->config().brim_ears_max_angle.value;
//ORCA: Select brim base slices from EFC-compensated outline when enabled.
@@ -891,17 +889,6 @@ void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_
std::vector<unsigned int>& printExtruders,
std::map<ObjectInstanceID, ExPolygons>* objectBrimAreasByInstanceOut)
{
// Belt printers never use the flat plate brim.
//
// With a tilted belt the brim has to be laid onto the belt plane over many layers,
// which BeltBrim.cpp does during posSupportMaterial. With an untilted belt this
// could in principle fall through and produce an ordinary brim, but it would never
// reach the G-code: the plate brim is emitted out of skirt_brim_groups(), which
// _make_skirt() builds, and that returns early for every belt printer. Running the
// generator anyway would just burn time on geometry nobody prints.
if (print.config().belt_printer.value)
return;
std::map<ObjectInstanceID, ExPolygons> brimAreaMap;
Flow flow = print.brim_flow();
ExPolygons islands_area_ex = outer_inner_brim_area(print,
+1 -1
View File
@@ -84,7 +84,7 @@ public:
indexed_triangle_set bounding_mesh(bool scale=true) const;
// Center of the print bed, unscaled.
Vec2d bed_center() const { return get_extents(m_bed_shape).center(); }
Vec2d bed_center() const { return to_2d(m_bboxf.center()); }
// Convex hull of polygon(), scaled.
const Polygon& convex_hull() const { return m_convex_hull; }
// Smallest enclosing circle of polygon(), scaled.
-21
View File
@@ -85,17 +85,6 @@ set(lisbslic3r_sources
BoundingBox.hpp
BridgeDetector.cpp
BridgeDetector.hpp
BeltBrim.cpp
BeltBrim.hpp
BeltGCode.cpp
BeltGCode.hpp
BeltPurge.cpp
BeltSliceStrategy.cpp
BeltSliceStrategy.hpp
BeltTransform.cpp
BeltTransform.hpp
FirstLayerPlane.cpp
FirstLayerPlane.hpp
Brim.cpp
BrimEarsPoint.hpp
Brim.hpp
@@ -243,14 +232,6 @@ set(lisbslic3r_sources
GCode/AdaptivePAProcessor.hpp
GCode/AvoidCrossingPerimeters.cpp
GCode/AvoidCrossingPerimeters.hpp
GCode/BeltBackTransform.cpp
GCode/BeltBackTransform.hpp
GCode/MachineFrameTransform.cpp
GCode/MachineFrameTransform.hpp
GCode/BeltKinematics.cpp
GCode/BeltKinematics.hpp
GCode/MachineKinematics.cpp
GCode/MachineKinematics.hpp
GCode/ConflictChecker.cpp
GCode/ConflictChecker.hpp
GCode/CoolingBuffer.cpp
@@ -475,8 +456,6 @@ set(lisbslic3r_sources
SlicingAdaptive.hpp
Slicing.cpp
Slicing.hpp
Support/BeltFloorContext.cpp
Support/BeltFloorContext.hpp
Support/SupportCommon.cpp
Support/SupportCommon.hpp
Support/SupportLayer.hpp
-5
View File
@@ -403,11 +403,6 @@ inline void translate(ExPolygons &expolys, const Point &p) {
expoly.translate(p);
}
inline void translate(Polygons &polys, const Point &p) {
for (Polygon &poly : polys)
poly.translate(p);
}
inline void polygons_append(Polygons &dst, const ExPolygon &src)
{
dst.reserve(dst.size() + src.holes.size() + 1);
-229
View File
@@ -1,229 +0,0 @@
#include "FirstLayerPlane.hpp"
#include "BeltTransform.hpp"
#include "BoundingBox.hpp"
#include "Point.hpp"
#include "PrintConfig.hpp"
#include "libslic3r.h"
#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) {
// BeltKinematics 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
-76
View File
@@ -1,76 +0,0 @@
#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_
+12 -31
View File
@@ -1041,10 +1041,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
void _stop_object_xml_parser(const std::string& msg = std::string())
{
assert(! obj_parse_error);
assert(obj_parse_error_message.empty());
assert(object_xml_parser != nullptr);
obj_parse_error = true;
if (! msg.empty() || obj_parse_error_message.empty()) // a handler may have set the message already
obj_parse_error_message = msg;
obj_parse_error_message = msg;
XML_StopParser(object_xml_parser, false);
}
@@ -3901,18 +3901,11 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
{
// appends the vertex coordinates
// missing values are set equal to ZERO
if (m_curr_object) {
const Vec3f v(m_unit_factor * bbs_get_attribute_value_float(attributes, num_attributes, X_ATTR),
m_unit_factor * bbs_get_attribute_value_float(attributes, num_attributes, Y_ATTR),
m_unit_factor * bbs_get_attribute_value_float(attributes, num_attributes, Z_ATTR));
// A non-finite coordinate ("nan", "inf") used to be accepted and crashed
// qhull in ModelVolume's convex hull while the file was still loading. Refuse the file.
if (! v.allFinite()) {
_stop_xml_parser("Invalid vertex coordinate: not a finite number");
return true; // the parser is stopped; returning false would overwrite the message
}
m_curr_object->geometry.vertices.emplace_back(v);
}
if (m_curr_object)
m_curr_object->geometry.vertices.emplace_back(
m_unit_factor * bbs_get_attribute_value_float(attributes, num_attributes, X_ATTR),
m_unit_factor * bbs_get_attribute_value_float(attributes, num_attributes, Y_ATTR),
m_unit_factor * bbs_get_attribute_value_float(attributes, num_attributes, Z_ATTR));
return true;
}
@@ -5202,11 +5195,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
}
}
for (const Vec3f &v : sub_object->geometry.vertices)
if (! v.allFinite()) { // Qhull cannot take a NaN vertex
add_error("invalid (non-finite) vertex in object " + std::to_string(sub_object->id));
return false;
}
its.vertices.assign(sub_object->geometry.vertices.begin(), sub_object->geometry.vertices.end());
// BBS
@@ -5720,18 +5708,11 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
{
// appends the vertex coordinates
// missing values are set equal to ZERO
if (current_object) {
const Vec3f v(object_unit_factor * bbs_get_attribute_value_float(attributes, num_attributes, X_ATTR),
object_unit_factor * bbs_get_attribute_value_float(attributes, num_attributes, Y_ATTR),
object_unit_factor * bbs_get_attribute_value_float(attributes, num_attributes, Z_ATTR));
// See _BBS_3MF_Importer::_handle_start_vertex: a non-finite coordinate
// crashed qhull while the file loaded. The dispatcher stops this parser on `false`.
if (! v.allFinite()) {
obj_parse_error_message = "Invalid vertex coordinate: not a finite number";
return false;
}
current_object->geometry.vertices.emplace_back(v);
}
if (current_object)
current_object->geometry.vertices.emplace_back(
object_unit_factor * bbs_get_attribute_value_float(attributes, num_attributes, X_ATTR),
object_unit_factor * bbs_get_attribute_value_float(attributes, num_attributes, Y_ATTR),
object_unit_factor * bbs_get_attribute_value_float(attributes, num_attributes, Z_ATTR));
return true;
}
+35 -623
View File
File diff suppressed because it is too large Load Diff
+7 -163
View File
@@ -7,8 +7,6 @@
#include "Print.hpp"
#include "libslic3r.h"
#include "GCodeWriter.hpp"
#include "GCode/BeltKinematics.hpp"
#include "FirstLayerPlane.hpp"
#include "Layer.hpp"
#include "Point.hpp"
#include "PlaceholderParser.hpp"
@@ -42,16 +40,13 @@
#include <memory>
#include <map>
#include <unordered_map>
#include <optional>
#include <set>
#include <string>
#include <cfloat>
#include <vector>
#include <utility>
#include <cmath>
#include "BoundingBox.hpp"
#include "Polyline.hpp"
#include "BeltBrim.hpp"
namespace Slic3r { class ExtrusionEntityCollection; }
@@ -254,9 +249,8 @@ public:
m_toolchange_count(0),
m_nominal_z(0.)
{}
virtual ~GCode() = default;
~GCode() = default;
public:
// throws std::runtime_exception on error,
// throws CanceledException through print->throw_if_canceled().
void do_export(Print* print, const char* path, GCodeProcessorResult* result = nullptr, ThumbnailsGeneratorCallback thumbnail_cb = nullptr);
@@ -348,13 +342,6 @@ public:
const Layer* object_layer;
const SupportLayer* support_layer;
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
{
if (object_layer != nullptr)
@@ -384,25 +371,11 @@ public:
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;
}
};
// 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:
private:
class GCodeOutputStream {
public:
GCodeOutputStream(FILE *f, GCodeProcessor &processor) : f(f), m_processor(processor) {}
@@ -430,21 +403,9 @@ protected:
FILE *f = nullptr;
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) {}
virtual void write_belt_header(GCodeOutputStream &file, const Print &print) {}
virtual void on_set_origin(const PrintObject *obj, const Point &inst_shift) {}
// Arc fitting is suppressed whenever the writer's machine mapping cannot
// represent a G2/G3 arc. Belt printers get this through BeltKinematics
// rather than through an override of their own.
virtual bool should_disable_arc_fitting() const
{ return ! m_writer.kinematics().supports_arc_moves(); }
void _do_export(Print &print, GCodeOutputStream &file, ThumbnailsGeneratorCallback thumbnail_cb);
static std::vector<LayerToPrint> collect_layers_to_print(const PrintObject &object, bool skip_empty_first_layer = false);
static std::vector<LayerToPrint> collect_layers_to_print(const PrintObject &object);
static std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> collect_layers_to_print(const Print &print);
std::string generate_skirt(const Print &print,
@@ -464,29 +425,7 @@ protected:
std::string generate_object_brim(const Print &print,
const PrintObject &object,
size_t instance_id,
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 whose brim filament is extruder_id
// (0-based). 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,
const unsigned int extruder_id);
bool first_layer);
LayerResult process_layer(
const Print &print,
@@ -690,21 +629,9 @@ protected:
};
// Cache the per-filament island tour to avoid recomputing while the layer's island layout is
// unchanged. Key: filament_id. Value: the nodes the tour was computed from, the per-instance
// island layout (count and whether the trailing catch-all island has anything to print), and
// the resulting visits.
// The layout is part of the key. Nodes only cover the chainable islands, so two
// layers with the same centroids but a different number of islands (thin walls, negative
// volumes come and go) matched the cache and the visit's catch-all index -- islands.size() - 1
// of the OLD layer -- ran past the new layer's islands (found by fuzzing: segfault in
// extrude_perimeters on multi-part objects).
struct IslandOrderCacheEntry
{
std::vector<IslandOrderNode> nodes;
std::vector<std::pair<size_t, bool>> layout;
std::vector<InstanceVisit> visits;
};
std::map<unsigned int, IslandOrderCacheEntry> m_ordering_cache;
// unchanged. Key: filament_id. Value: {nodes the tour was computed from, resulting visits}.
std::map<unsigned int, std::pair<std::vector<IslandOrderNode>, std::vector<InstanceVisit>>>
m_ordering_cache;
ExtrusionQualityEstimator m_extrusion_quality_estimator;
@@ -841,12 +768,6 @@ protected:
std::unique_ptr<CoolingBuffer> m_cooling_buffer;
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;
// Plate origin, kept so a writer replaced during export can be given it again.
std::unique_ptr<PressureEqualizer> m_pressure_equalizer;
@@ -902,25 +823,6 @@ protected:
mutable ConfigIndexCache m_filament_index_cache;
mutable ConfigIndexCache m_nozzle_index_cache;
// 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;
// Belt brim only. Brim and coincident apron bands are emitted before m_layer
// is switched to their object, so belt_height_above_floor() would otherwise
// read the previously visited object's belt description -- making a brim's
// classification depend on plate visiting order. Those paths publish the
// owner here for the duration of the emission. Never left set.
const PrintObject *m_belt_floor_object{nullptr};
struct BeltFloorObjectGuard {
const PrintObject *&slot;
BeltFloorObjectGuard(const PrintObject *&s, const PrintObject *o) : slot(s) { slot = o; }
~BeltFloorObjectGuard() { slot = nullptr; }
};
// The last extrusion segment was inside the belt's first-layer fan band (see _extrude()).
bool m_belt_in_band{false};
std::set<unsigned int> m_initial_layer_extruders;
std::vector<std::vector<unsigned int>> m_sorted_layer_filaments;
// BBS
@@ -938,64 +840,6 @@ protected:
// On the first printing layer. This flag triggers first layer speeds.
//BBS
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 {
// Belt printers: measure height above the belt surface itself, in the
// slicing frame. See belt_height_above_floor() for why this does not go
// through FirstLayerPlane.
double h;
if (this->belt_height_above_floor(point_slicing_mm, h))
return h <= m_config.initial_layer_print_height.value + EPSILON;
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 {
double h;
if (this->belt_height_above_floor(point_slicing_mm, h)) {
const double lh = this->first_layer_band_mm();
return h <= 0. ? 0 : int(std::floor(h / lh));
}
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();
}
// Band thickness for the *effective layer index* only. FirstLayerPlane keeps
// two separate thresholds and so must this path: is_first_layer() tests
// against initial_layer_print_height, while effective_layer_index() counts
// bands of first_layer_plane_thickness. Conflating them would apply
// first-layer treatment through a whole 1mm band on a 0.2mm first layer.
double first_layer_band_mm() const {
double band = m_config.first_layer_plane_thickness.value;
if (band <= 0.) band = m_config.initial_layer_print_height.value;
return band > 0. ? band : 0.2;
}
// Height of a slicing-frame point above the belt surface, or false when this
// is not a belt print.
//
// The belt surface is known exactly in the slicing frame from the slicing
// parameters (belt_floor_shear_factor / _from_axis / _z_shift) -- the same
// description the support generator uses. FirstLayerPlane instead derives its
// plane by composing gcode_remap_* with the g-code back-transform, so its
// answer changes with the machine's *output* axis convention: on a printer
// with a non-identity remap it reported ~86mm of clearance for geometry
// sitting directly on the belt, and no extrusion was ever classified as
// first-layer. Measuring against the belt itself is independent of every
// remap and back-transform.
bool belt_height_above_floor(const Vec3d &point_slicing_mm, double &height_mm) const;
// 1 / 0 / -1: the object layer is entirely past the first-layer band above the
// belt / reaches into it / the belt surface is not known for it.
int belt_layer_past_first_layer_band(const Layer *object_layer) const;
int layer_id() const {
if (m_layer == nullptr)
return -1;
-42
View File
@@ -1,42 +0,0 @@
#include "BeltBackTransform.hpp"
#include "../BeltTransform.hpp"
#include "../Point.hpp"
#include "../PrintConfig.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
-45
View File
@@ -1,45 +0,0 @@
#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
// BeltKinematics::to_machine() 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_
-42
View File
@@ -1,42 +0,0 @@
#include "BeltKinematics.hpp"
#include "../BeltTransform.hpp"
#include "../PrintConfig.hpp"
#include "../GCodeWriter.hpp"
#include "../Point.hpp"
#include <memory>
namespace Slic3r {
BeltKinematics::BeltKinematics(const PrintConfig &config, bool world_coordinates)
: m_world_coordinates(world_coordinates)
{
m_back_active = m_back_transform.init_from_config(config);
m_machine_frame.init_from_config(config);
if (m_back_active)
// BeltBackTransform stores the inverse of this; keep the forward so
// to_logical() can reverse the whole chain.
m_back_forward = BeltTransformPipeline::build_forward_transform(config);
}
Vec3d BeltKinematics::to_machine(const Vec3d &p) const
{
const Vec3d after_back = m_world_coordinates ? p : m_back_transform.apply(p);
const Vec3d after_remap = this->apply_axis_remap(after_back);
return m_machine_frame.apply(after_remap);
}
Vec3d BeltKinematics::to_logical(const Vec3d &machine) const
{
const Vec3d before_frame = m_machine_frame.apply_inverse(machine);
const Vec3d before_remap = this->apply_axis_remap_inverse(before_frame);
if (m_world_coordinates || ! m_back_active)
return before_remap;
return m_back_forward * before_remap;
}
void install_belt_kinematics(GCodeWriter &writer, const PrintConfig &config, bool world_coordinates)
{
writer.set_kinematics(std::make_unique<BeltKinematics>(config, world_coordinates));
}
} // namespace Slic3r
-70
View File
@@ -1,70 +0,0 @@
#ifndef slic3r_BeltKinematics_hpp_
#define slic3r_BeltKinematics_hpp_
#include "MachineKinematics.hpp"
#include "BeltBackTransform.hpp"
#include "MachineFrameTransform.hpp"
#include "../Point.hpp"
namespace Slic3r {
class PrintConfig;
class GCodeWriter;
// Belt-printer machine frame.
//
// Forward order, as applied per emitted point:
// machine = MachineFrameTransform( axis_remap( BeltBackTransform( logical ) ) )
//
// i.e. the slicer->world back-transform runs FIRST and the machine-frame
// shear/scale LAST, so the latter acts as a global linear transform on the
// already-placed coordinates.
//
// world_coordinates mode (the PA line / PA pattern calibration generators)
// treats the incoming point as already relative to the belt surface -- X across,
// Y along the belt, Z above it -- and therefore skips the back-transform while
// keeping the remap and the machine frame. It is a different coordinate map, not
// a writer mode, which is why it is fixed at construction.
class BeltKinematics : public CartesianKinematics
{
public:
explicit BeltKinematics(const PrintConfig &config, bool world_coordinates = false);
Vec3d to_machine(const Vec3d &p) const override;
Vec3d to_logical(const Vec3d &machine) const override;
// Machine -> build-volume frame. Only the machine-frame shear/scale is undone,
// matching what GCodeProcessor's bounds validation wants. This is deliberately
// NOT to_logical().
Vec3d to_build_volume(const Vec3d &machine) const override
{ return m_machine_frame.apply_inverse(machine); }
// A belt writer has always emitted full XYZ on every move, whether or not any
// individual stage reports itself active. Making this conditional would change
// emitted G-code for an identity-transform belt configuration.
bool must_emit_all_axes() const override { return true; }
bool suppress_lift_at_unknown_position() const override { return true; }
// The machine frame shears and scales, so a circle is an ellipse in machine
// coordinates and G2/G3 cannot describe it.
bool supports_arc_moves() const override { return false; }
bool world_coordinates() const { return m_world_coordinates; }
private:
BeltBackTransform m_back_transform;
MachineFrameTransform m_machine_frame;
// Forward of what m_back_transform inverts, kept so to_logical() can undo it.
Transform3d m_back_forward { Transform3d::Identity() };
bool m_back_active { false };
bool m_world_coordinates { false };
};
// Install a belt machine frame on any GCodeWriter. Any axis remap and build
// volume already configured on the writer are carried over, so this may be
// called before or after those setters. Re-calling it with a different
// world_coordinates value swaps the map (used around the PA line generator).
void install_belt_kinematics(GCodeWriter &writer, const PrintConfig &config,
bool world_coordinates = false);
} // namespace Slic3r
#endif // slic3r_BeltKinematics_hpp_
+2 -31
View File
@@ -18,7 +18,6 @@
#include <iostream>
#include <float.h>
#include <string>
#include <string_view>
#include <system_error>
#include <unordered_map>
#include <vector>
@@ -47,12 +46,10 @@ CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_config(gcodegen.config()), m_g
m_num_extruders = std::max(ex.id() + 1, m_num_extruders);
m_extruder_ids.emplace_back(ex.id());
}
}
void CoolingBuffer::reset(const Vec3d &position)
{
m_belt_band_active = false;
// BBS: add I and J axis to store center of arc
m_current_pos.assign(7, 0.f);
m_current_pos[0] = float(position.x());
@@ -92,9 +89,6 @@ struct CoolingLine
// ORCA: Add support for ironing fan speed control
TYPE_IRONING_FAN_START = 1 << 19,
TYPE_IRONING_FAN_END = 1 << 20,
// Belt printers: extrusions within the first-layer band above the belt.
TYPE_BELT_BAND_START = 1 << 21,
TYPE_BELT_BAND_END = 1 << 22,
};
CoolingLine(unsigned int type, size_t line_start, size_t line_end) :
@@ -555,10 +549,6 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
line.type = CoolingLine::TYPE_IRONING_FAN_START;
} else if (boost::starts_with(sline, ";_IRONING_FAN_END")) { // ORCA: Add support for ironing fan speed control
line.type = CoolingLine::TYPE_IRONING_FAN_END;
} else if (boost::starts_with(sline, ";_BELT_BAND_START")) {
line.type = CoolingLine::TYPE_BELT_BAND_START;
} else if (boost::starts_with(sline, ";_BELT_BAND_END")) {
line.type = CoolingLine::TYPE_BELT_BAND_END;
} else if (boost::starts_with(sline, "G4 ")) {
// Parse the wait time.
line.type = CoolingLine::TYPE_G4;
@@ -901,9 +891,7 @@ std::string CoolingBuffer::apply_layer_cooldown(
{CoolingLine::TYPE_SUPPORT_INTERFACE_FAN_START, false},
{CoolingLine::TYPE_IRONING_FAN_START, false}, // ORCA: Add support for ironing fan speed control
{CoolingLine::TYPE_FORCE_RESUME_FAN, false}};
// Belt printers: a band still open from the previous layer has to take the fan back from
// the layer-level speed issued just above.
bool need_set_fan = m_belt_band_active;
bool need_set_fan = false;
for (const CoolingLine *line : lines) {
const char *line_start = gcode.c_str() + line->line_start;
@@ -917,8 +905,6 @@ std::string CoolingBuffer::apply_layer_cooldown(
if (new_extruder != m_current_extruder) {
m_current_extruder = new_extruder;
change_extruder_set_fan(true);
if (m_belt_band_active)
need_set_fan = true;
}
}
new_gcode.append(line_start, line_end - line_start);
@@ -971,13 +957,6 @@ std::string CoolingBuffer::apply_layer_cooldown(
if (m_additional_fan_speed != -1 && m_config.auxiliary_fan.value)
new_gcode += GCodeWriter::set_additional_fan(m_additional_fan_speed);
}
else if (line->type & CoolingLine::TYPE_BELT_BAND_START) {
m_belt_band_active = true;
need_set_fan = true;
} else if (line->type & CoolingLine::TYPE_BELT_BAND_END) {
m_belt_band_active = false;
need_set_fan = true;
}
else if (line->type & CoolingLine::TYPE_EXTRUDE_END) {
// Just remove this comment.
} else if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE | CoolingLine::TYPE_HAS_F)) {
@@ -1070,15 +1049,7 @@ std::string CoolingBuffer::apply_layer_cooldown(
m_current_fan_speed = speed;
}
};
if (m_belt_band_active) {
// Belt printers: a tilted layer runs from the belt to the top of the part, so
// "the first layers" are a band along the belt rather than the first slicing
// layers. Extrusions GCode::_extrude() marks as inside that band print with the
// fan off, whatever overhang, bridge or resume request is pending, as the first
// layers of a flat bed do. Leaving the band falls through to the branches below.
set_fan(0);
fan_speed_change_requests[CoolingLine::TYPE_FORCE_RESUME_FAN] = false;
} else if (fan_speed_change_requests[CoolingLine::TYPE_OVERHANG_FAN_START]){
if (fan_speed_change_requests[CoolingLine::TYPE_OVERHANG_FAN_START]){
set_fan(overhang_fan_speed);
} else if (fan_speed_change_requests[CoolingLine::TYPE_INTERNAL_BRIDGE_FAN_START]){ // ORCA: Add support for separate internal bridge fan speed control
set_fan(internal_bridge_fan_speed);
+1 -4
View File
@@ -21,7 +21,7 @@ struct PerExtruderAdjustments;
//
// 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.
// 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.
//
class CoolingBuffer {
@@ -63,9 +63,6 @@ private:
unsigned int m_current_nozzle;
//BBS: current fan speed
int m_current_fan_speed;
// Belt printers: the extrusion being processed lies in the first-layer band above the
// belt (between a ";_BELT_BAND_START" and a ";_BELT_BAND_END"). Kept across layers.
bool m_belt_band_active = false;
};
}
+19 -158
View File
@@ -2638,12 +2638,6 @@ void GCodeProcessorResult::reset() {
long_retraction_when_cut = false;
timelapse_warning_code = 0;
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();
filaments_count = 0;
backtrace_enabled = false;
@@ -2880,32 +2874,6 @@ bool GCodeProcessor::check_multi_extruder_gcode_valid(const int
return ps;
};
// Belt-printer post-gcode shear/scale/post_remap is applied as the final
// step of BeltKinematics::to_machine, so MoveVertex.position is
// in the printer's machine frame. Undo it here so the XY area check
// operates in the build-volume frame that printable_area is defined in
// (the height checks below are skipped on belt printers). 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
{
Points pos;
@@ -2917,20 +2885,26 @@ bool GCodeProcessor::check_multi_extruder_gcode_valid(const int
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
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) {
gcode_path_pos[move.object_label_id][int(move.extruder_id)].pos_custom.emplace_back(to_2d(cp));
/*if (move.is_arc_move_with_interpolation_points()) {
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 =
std::max(gcode_path_pos[move.object_label_id][int(move.extruder_id)].max_print_z_custom, z_for_height);
std::max(gcode_path_pos[move.object_label_id][int(move.extruder_id)].max_print_z_custom, move.print_z);
} else {
gcode_path_pos[move.object_label_id][int(move.extruder_id)].pos.emplace_back(to_2d(cp));
/*if (move.is_arc_move_with_interpolation_points()) {
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,
z_for_height);
move.print_z);
}
}
}
@@ -2965,12 +2939,7 @@ bool GCodeProcessor::check_multi_extruder_gcode_valid(const int
valid = false;
}
}
// Belt printers: the Z recorded here grows with belt travel (machine Z with the
// frame transform, the slicing-frame Z without it), while printable_height is the
// clearance above the belt; the two are not comparable, so the over-height check
// is skipped, as the preview's ToolHeightOutside warning already is.
// Print::validate() checks the object's height against the clearance.
if ( !m_belt_printer && iter->second.max_print_z > plate_printable_height ) { //over height
if ( iter->second.max_print_z > plate_printable_height ) { //over height
m_result.gcode_check_result.error_code |= (1 << 3);
std::pair<int, int> filament_to_object_id;
filament_to_object_id.first = iter->first;
@@ -3011,7 +2980,7 @@ bool GCodeProcessor::check_multi_extruder_gcode_valid(const int
}
// check printable height
if (!m_belt_printer && (extruder_id < printable_heights.size()) && (iter->second.max_print_z > printable_heights[extruder_id])) {
if ((extruder_id < printable_heights.size()) && (iter->second.max_print_z > printable_heights[extruder_id])) {
m_result.gcode_check_result.error_code |= (1 << 1);
std::pair<int, int> filament_to_object_id;
filament_to_object_id.first = iter->first;
@@ -3177,13 +3146,6 @@ void GCodeProcessor::apply_config(const PrintConfig& config)
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();
m_belt_printer = config.belt_printer.value;
auto filament_maps = config.option<ConfigOptionInts>("filament_map");
if (filament_maps != nullptr) {
m_filament_maps = filament_maps->values;
@@ -3214,33 +3176,6 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config)
{
m_parser.apply_config(config);
// Belt printer: remember the file's belt keys for export_config_for_render(). The
// config block lists belt_printer for every printer, so a non-belt file loaded while
// a belt printer is selected switches the preview's belt view off, and a belt file
// loaded on another printer brings its own tilt, remaps and bed along.
m_belt_render_config.clear();
{
const auto *belt = config.option<ConfigOptionBool>("belt_printer");
if (belt != nullptr) {
static const char *belt_keys[] = {
"belt_printer", "belt_slice_rotation", "belt_slice_rotation_angle", "belt_slice_rotation_global",
"belt_preslice_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",
};
for (const char *key : belt_keys)
if (const ConfigOption *opt = config.option(key); opt != nullptr)
m_belt_render_config.set_key_value(key, opt->clone());
// The Rev remaps mirror inside the build volume, so the designed view needs
// the bed the file was sliced for. Only a belt file may override it.
static const char *bed_keys[] = { "printable_area", "printable_height" };
if (belt->value)
for (const char *key : bed_keys)
if (const ConfigOption *opt = config.option(key); opt != nullptr)
m_belt_render_config.set_key_value(key, opt->clone());
}
}
//BBS
const ConfigOptionFloatsNullable* nozzle_volume = config.option<ConfigOptionFloatsNullable>("nozzle_volume");
if (nozzle_volume != nullptr) {
@@ -3724,7 +3659,6 @@ void GCodeProcessor::reset()
m_zero_layer_height = 0.0f;
m_first_layer_height = 0.0f;
m_processing_start_custom_gcode = false;
m_in_config_block = false;
m_g1_line_id = 0;
m_layer_id = 0;
m_cp_color.reset();
@@ -3765,7 +3699,6 @@ DynamicConfig GCodeProcessor::export_config_for_render() const
config.set_key_value("filament_is_support", new ConfigOptionBools(m_parser.get_config().filament_is_support.values));
config.set_key_value("filament_type", new ConfigOptionStrings(m_parser.get_config().filament_type.values));
config.set_key_value("filament_map", new ConfigOptionInts(m_parser.get_config().filament_map.values));
config.apply(m_belt_render_config);
return config;
}
@@ -4331,55 +4264,6 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers
return;
}
if (boost::starts_with(comment, " CONFIG_BLOCK_START")) {
m_in_config_block = true;
return;
}
if (boost::starts_with(comment, " CONFIG_BLOCK_END")) {
m_in_config_block = false;
return;
}
// Belt printer: derive the physical tilt magnitude from the slicing-rotation
// angle header comment (used to enable the preview's belt view). Only the belt
// header carries it outside the config block; the config block lists the key
// for every printer, belt or not.
if (!m_in_config_block && 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(20)))); return;
}
if (boost::starts_with(comment, " preslice_remap_y = ")) {
m_result.preslice_remap_y = parse_remap_axis(trim(std::string(comment.substr(20)))); return;
}
if (boost::starts_with(comment, " preslice_remap_z = ")) {
m_result.preslice_remap_z = parse_remap_axis(trim(std::string(comment.substr(20)))); return;
}
}
// wipe start tag
if (boost::starts_with(comment, reserved_tag(ETags::Wipe_Start))) {
m_wiping = true;
@@ -6276,13 +6160,6 @@ void GCodeProcessor::process_G92(const GCodeReader::GCodeLine& line)
if (line.has_z()) {
m_origin[Z] = m_end_position[Z] - line.z() * lengths_scale_factor;
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()) {
@@ -7261,22 +7138,6 @@ void GCodeProcessor::store_move_vertex(EMoveType type, EMovePathType path_type,
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 the belt kinematics 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_last_line_id,
type,
@@ -7284,7 +7145,7 @@ void GCodeProcessor::store_move_vertex(EMoveType type, EMovePathType path_type,
static_cast<unsigned char>(filament_id),
m_cp_color.current,
//BBS: add plate's offset to the rendering vertices
Vec3f(m_end_position[X] + m_x_offset, m_end_position[Y] + m_y_offset, store_z) + m_extruder_offsets[filament_id],
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],
static_cast<float>(m_end_position[E] - m_start_position[E]),
m_feedrate,
0.0f, // actual feedrate
-35
View File
@@ -12,7 +12,6 @@
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/CustomGCode.hpp"
#include "libslic3r/MultiNozzleUtils.hpp"
#include "libslic3r/GCode/MachineFrameTransform.hpp"
#include <cstddef>
#include <cassert>
@@ -291,22 +290,6 @@ class Print;
bool support_traditional_timelapse{true};
float printable_height;
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;
size_t filaments_count;
bool backtrace_enabled;
@@ -402,12 +385,6 @@ class Print;
// Keep the SKIPPABLE per-type time on a copied result.
skippable_part_time = std::forward<Other>(other).skippable_part_time;
initial_layer_time = std::forward<Other>(other).initial_layer_time;
belt_tilt_angle = std::forward<Other>(other).belt_tilt_angle;
belt_z_origin = std::forward<Other>(other).belt_z_origin;
machine_frame_transform_active = std::forward<Other>(other).machine_frame_transform_active;
preslice_remap_x = std::forward<Other>(other).preslice_remap_x;
preslice_remap_y = std::forward<Other>(other).preslice_remap_y;
preslice_remap_z = std::forward<Other>(other).preslice_remap_z;
#if ENABLE_GCODE_VIEWER_STATISTICS
time = std::forward<Other>(other).time;
#endif
@@ -1105,10 +1082,6 @@ class Print;
private:
CommandProcessor m_command_processor;
GCodeReader m_parser;
// Belt printer: the belt keys of the loaded file's config block (plus the bed they
// are relative to), handed to the preview through export_config_for_render() so the
// belt view and its back-transform follow the file, not the selected printer.
DynamicConfig m_belt_render_config;
EUnits m_units;
EPositioningType m_global_positioning_type;
EPositioningType m_e_local_positioning_type;
@@ -1184,13 +1157,6 @@ class Print;
double m_x_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;
bool m_belt_printer{ false };
unsigned int m_line_id;
unsigned int m_last_line_id;
float m_feedrate; // mm/s
@@ -1220,7 +1186,6 @@ class Print;
float m_first_layer_height; // mm
float m_zero_layer_height; // mm
bool m_processing_start_custom_gcode;
bool m_in_config_block;
unsigned int m_g1_line_id;
unsigned int m_layer_id;
CpColor m_cp_color;
@@ -1,89 +0,0 @@
#include "MachineFrameTransform.hpp"
#include "../Geometry.hpp"
#include "../Point.hpp"
#include "../PrintConfig.hpp"
#include "../libslic3r.h"
#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
@@ -1,54 +0,0 @@
#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 BeltKinematics::to_machine 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_
-48
View File
@@ -1,48 +0,0 @@
#include "MachineKinematics.hpp"
#include "../Point.hpp"
namespace Slic3r {
// Moved verbatim from GCodeWriter::apply_axis_remap().
Vec3d CartesianKinematics::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) };
}
// Inverse of the above. Output axis i is fed by source axis (r_i % 3); walking
// the three outputs therefore fills every source component exactly once, so long
// as the remap is a permutation (which set_axis_remap callers guarantee).
Vec3d CartesianKinematics::apply_axis_remap_inverse(const Vec3d &machine) const
{
if (!has_axis_remap())
return machine;
Vec3d out = Vec3d::Zero();
const int r[3] = { m_remap_x, m_remap_y, m_remap_z };
for (int i = 0; i < 3; ++i) {
const int axis = r[i] % 3;
if (r[i] < 3) out[axis] = machine[i];
else if (r[i] < 6) out[axis] = -machine[i];
else out[axis] = m_build_vol_max[axis] - machine[i];
}
return out;
}
Vec3d CartesianKinematics::to_machine(const Vec3d &p) const
{
return this->apply_axis_remap(p);
}
Vec3d CartesianKinematics::to_logical(const Vec3d &machine) const
{
return this->apply_axis_remap_inverse(machine);
}
} // namespace Slic3r
-106
View File
@@ -1,106 +0,0 @@
#ifndef slic3r_MachineKinematics_hpp_
#define slic3r_MachineKinematics_hpp_
#include "../Point.hpp"
namespace Slic3r {
// The frame contract for emitted movement.
//
// GCodeWriter produces points in the *logical placed* frame: plate offsets have
// already been subtracted, but no machine-specific mapping has been applied.
// A MachineKinematics turns that into the coordinates actually written to
// G-code, and answers the two structural questions the writer needs in order to
// decide which axis words it may omit.
//
// This is a seam for writer-generated movement only. Start/end/custom G-code,
// classic wipe-tower output and GCodeWriter::extrude_arc_to_xy() do NOT pass
// through it: they write machine coordinates directly.
class MachineKinematics
{
public:
virtual ~MachineKinematics() = default;
// Logical placed point -> emitted machine point.
virtual Vec3d to_machine(const Vec3d &p) const = 0;
// Inverse of to_machine(), back to the logical placed frame. Intended for
// consumers that must reconstruct model coordinates from emitted G-code
// (the G-code viewer's upright preview).
virtual Vec3d to_logical(const Vec3d &machine) const = 0;
// Machine point -> build-volume frame, for bounds validation only. This is
// deliberately NOT to_logical(): the build-volume check wants the physical
// frame the printable area is expressed in, not the model frame. Keeping
// them separate stops the two contracts from being confused.
virtual Vec3d to_build_volume(const Vec3d &machine) const = 0;
// True when a move must emit X, Y and Z because omitting a word would be
// wrong under this mapping. Deliberately not called "couples_axes": a pure
// axis permutation forces full emission without physically coupling axes.
virtual bool must_emit_all_axes() const = 0;
// True when a lift must be suppressed while the current position is unknown,
// because _travel_to_z() re-emits the logical X/Y through this mapping and an
// uninitialised position would map to a bogus machine point -- for a reverse
// mapping, the far corner of the bed.
virtual bool suppress_lift_at_unknown_position() const = 0;
// True when a G2/G3 arc in the logical XY plane is still the same arc in the
// machine frame. Arc moves emit only X, Y, I and J, so this asks a narrower
// question than must_emit_all_axes(): whether logical X and Y reach the
// machine unchanged. A mapping that only negates or reverses Z keeps its
// arcs; one that permutes X or Y moves the arc out of the plane that I/J
// describes, and a shear turns the circle into an ellipse G2/G3 cannot
// express at all.
virtual bool supports_arc_moves() const = 0;
// Configuration. GCodeWriter forwards its setters here so that the state
// lives with the strategy and a strategy installed before the setters run
// still receives it.
virtual void set_axis_remap(int rx, int ry, int rz) = 0;
virtual void set_build_volume_max(const Vec3d &max) = 0;
};
// Axis remap only -- the historical GCodeWriter behaviour, moved verbatim.
//
// The remap encodes, per output axis, which source axis feeds it and how:
// r < 3 : source axis r, unchanged
// r < 6 : source axis r-3, negated
// else : source axis r-6, reversed within the build volume
class CartesianKinematics : public MachineKinematics
{
public:
Vec3d to_machine(const Vec3d &p) const override;
Vec3d to_logical(const Vec3d &machine) const override;
Vec3d to_build_volume(const Vec3d &machine) const override { return machine; }
bool must_emit_all_axes() const override { return this->has_axis_remap(); }
bool suppress_lift_at_unknown_position() const override { return this->has_axis_remap(); }
// X and Y must reach the machine untouched. Because the remap is a
// permutation, pinning those two also pins Z to Z, so a mapping that only
// negates or reverses Z still supports arcs -- every word a G2/G3 emits is
// unchanged by it.
bool supports_arc_moves() const override { return m_remap_x == 0 && m_remap_y == 1; }
void set_axis_remap(int rx, int ry, int rz) override
{ m_remap_x = rx; m_remap_y = ry; m_remap_z = rz; }
void set_build_volume_max(const Vec3d &max) override { m_build_vol_max = max; }
bool has_axis_remap() const
{ return m_remap_x != 0 || m_remap_y != 1 || m_remap_z != 2; }
protected:
Vec3d apply_axis_remap(const Vec3d &pos) const;
Vec3d apply_axis_remap_inverse(const Vec3d &pos) const;
int m_remap_x { 0 };
int m_remap_y { 1 };
int m_remap_z { 2 };
Vec3d m_build_vol_max { Vec3d::Zero() };
};
} // namespace Slic3r
#endif // slic3r_MachineKinematics_hpp_
+2 -2
View File
@@ -706,7 +706,7 @@ void compute_global_occlusion(GlobalModelInfo &result, const PrintObject *po,
SeamPosition seam_position = spAligned) {
BOOST_LOG_TRIVIAL(debug)
<< "SeamPlacer: gather occlusion meshes: start";
auto obj_transform = po->trafo_sliced();
auto obj_transform = po->trafo_centered();
indexed_triangle_set triangle_set;
indexed_triangle_set negative_volumes_set;
//add all parts
@@ -796,7 +796,7 @@ void gather_enforcers_blockers(GlobalModelInfo &result, const PrintObject *po) {
BOOST_LOG_TRIVIAL(debug)
<< "SeamPlacer: build AABB trees for raycasting enforcers/blockers: start";
auto obj_transform = po->trafo_sliced();
auto obj_transform = po->trafo_centered();
for (const ModelVolume *mv : po->model_object()->volumes) {
// Collect painting only from model parts (what the gizmo edits) and negative volumes (the only way
+2 -70
View File
@@ -18,7 +18,6 @@
#include "Utils.hpp"
#include "format.hpp"
#include "I18N.hpp"
#include "../BeltBrim.hpp"
#include <boost/log/trivial.hpp>
#include <vector>
@@ -420,10 +419,6 @@ bool ToolOrdering::insert_wipe_tower_extruder()
{
if (!m_print_config_ptr || !m_print_config_ptr->enable_prime_tower)
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)
return false;
@@ -521,11 +516,6 @@ ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extrude
zs.emplace_back(layer->print_z);
for (auto layer : object.support_layers())
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);
}
@@ -570,10 +560,6 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool
zs.emplace_back(layer->print_z);
for (auto layer : object->support_layers())
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);
}
@@ -1008,44 +994,6 @@ 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) {
// Sort and remove duplicates
sort_remove_duplicates(layer.extruders);
@@ -1088,28 +1036,12 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_
}
//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. A belt printer never prints the classic
// prime tower (Print::has_wipe_tower()), 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 &lt : m_layer_tools)
lt.has_wipe_tower |= ((lt.has_object || lt.has_support) && (config.timelapse_type == TimelapseType::tlSmooth || lt.wipe_tower_partitions > 0))
|| (! belt_no_raft_gap && lt.print_z < object_bottom_z + EPSILON);
|| lt.print_z < object_bottom_z + EPSILON;
// Test for a raft, insert additional wipe tower layer to fill in the raft separation gap.
// 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) {
for (size_t i = 0; i + 1 < m_layer_tools.size(); ++ i) {
const LayerTools &lt = m_layer_tools[i];
const LayerTools &lt_next = m_layer_tools[i + 1];
if (lt.print_z < object_bottom_z + EPSILON && lt_next.print_z >= object_bottom_z + EPSILON) {
+6 -14
View File
@@ -84,17 +84,7 @@ public:
void set_layer_tools_ptr(const LayerTools* lt) { m_layer_tools = lt; }
// Returns true if entity is not printed with its usual extruder for a given
// copy -- i.e. it was claimed as a wiping/purge extrusion. Public because the
// belt purge prism uses it to tell which of its fills actually carry purge
// from the ones that are unclaimed waste (Print::_plan_belt_purge()).
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;
}
private:
int first_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const;
int last_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const;
@@ -104,6 +94,12 @@ private:
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);
// 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
// BBS
std::map<const PrintObject*, int> support_map;
@@ -179,10 +175,6 @@ public:
// 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.
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?
// Due to the support layers possibly interleaving the object layers,
// wipe tower will be disabled for some support only layers.
+18 -198
View File
@@ -1,7 +1,6 @@
#include "GCodeWriter.hpp"
#include "Config.hpp"
#include "Extruder.hpp"
#include "Geometry.hpp"
#include "I18N.hpp"
#include "Point.hpp"
#include "Polygon.hpp"
@@ -32,7 +31,6 @@
#include <sstream>
#include <stdexcept>
#include <math.h>
#include <memory>
#ifdef __APPLE__
#include <boost/spirit/include/karma.hpp>
@@ -45,57 +43,6 @@ namespace Slic3r {
bool GCodeWriter::full_gcode_comment = true;
// A lift emitted through _travel_to_z() re-emits the stored logical X/Y under a
// mapping that must emit every axis. While the position is unknown that X/Y is
// the uninitialised origin, which maps to a real but wrong machine point, so the
// lift has to be skipped rather than commanded.
bool GCodeWriter::must_skip_lift_now() const
{
return m_kinematics->suppress_lift_at_unknown_position() && ! this->is_current_position_clear();
}
bool GCodeWriter::point_on_first_layer(const Vec3d &point_logical) const
{
if (m_first_layer_point_test)
return m_first_layer_point_test(point_logical);
return m_is_first_layer;
}
void GCodeWriter::set_axis_remap(int rx, int ry, int rz)
{
m_remap_x = rx;
m_remap_y = ry;
m_remap_z = rz;
m_kinematics->set_axis_remap(rx, ry, rz);
}
void GCodeWriter::set_build_volume_max(const Vec3d &max)
{
m_build_vol_max = max;
m_kinematics->set_build_volume_max(max);
}
void GCodeWriter::set_kinematics(std::unique_ptr<MachineKinematics> kinematics)
{
assert(kinematics);
m_kinematics = std::move(kinematics);
// Replay whatever was configured on the previous strategy so callers may
// install the kinematics before or after set_axis_remap/set_build_volume_max.
m_kinematics->set_axis_remap(m_remap_x, m_remap_y, m_remap_z);
m_kinematics->set_build_volume_max(m_build_vol_max);
}
// Kept as the writer-facing name for "this move must emit every axis word".
bool GCodeWriter::has_axis_remap() const
{
return m_kinematics->must_emit_all_axes();
}
Vec3d GCodeWriter::apply_axis_remap(const Vec3d &pos) const
{
return m_kinematics->to_machine(pos);
}
bool GCodeWriter::supports_separate_travel_acceleration(GCodeFlavor flavor)
{
return (flavor == gcfRepetier || flavor == gcfMarlinFirmware || flavor == gcfRepRapFirmware);
@@ -849,14 +796,8 @@ 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 };
GCodeG1Formatter w;
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 = this->point_on_first_layer(Vec3d(point_on_plate.x(), point_on_plate.y(), m_pos.z()))
w.emit_xy(point_on_plate);
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);
w.emit_f(speed * 60.0);
//BBS
@@ -869,8 +810,6 @@ it will not perform subsequent lifts, even if Z was raised manually
(i.e. with travel_to_z()) and thus _lifted was reduced. */
std::string GCodeWriter::lazy_lift(LiftType lift_type, bool spiral_vase)
{
if (m_force_normal_lift)
lift_type = LiftType::NormalLift;
// check whether the above/below conditions are met
double target_lift = 0;
{
@@ -885,10 +824,6 @@ std::string GCodeWriter::lazy_lift(LiftType lift_type, bool spiral_vase)
// BBS
if (m_lifted == 0 && m_to_lift == 0 && target_lift > 0) {
if (spiral_vase) {
if (this->must_skip_lift_now())
// Record no lift, so a later unlift() does not descend from a
// height that was never commanded.
return "";
m_lifted = target_lift;
return this->_travel_to_z(m_pos(2) + target_lift, "lift Z");
}
@@ -901,9 +836,8 @@ std::string GCodeWriter::lazy_lift(LiftType lift_type, bool spiral_vase)
}
// 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) {
const LiftType effective_type = m_force_normal_lift ? LiftType::NormalLift : type;
std::string lift_move;
double target_lift = 0;
{
@@ -917,7 +851,7 @@ std::string GCodeWriter::eager_lift(const LiftType type) {
}
// BBS: spiral lift only safe with known position
if (effective_type == LiftType::SpiralLift && this->is_current_position_clear()) {
if (type == LiftType::SpiralLift && this->is_current_position_clear()) {
double radius = target_lift / (2 * PI * atan(filament()->travel_slope()));
// static spiral alignment when no move in x,y plane.
// spiral centra is a radius distance to the right (y=0)
@@ -934,12 +868,7 @@ std::string GCodeWriter::eager_lift(const LiftType type) {
}
//BBS: if position is unknown use normal lift
else if (target_lift > 0) {
if (this->must_skip_lift_now())
// Skipped, not deferred: leave m_lifted at zero below so unlift()
// does not descend from a height that was never commanded.
target_lift = 0.;
else
lift_move = _travel_to_z(m_pos(2) + target_lift, "normal lift Z");
lift_move = _travel_to_z(m_pos(2) + target_lift, "normal lift Z");
}
m_lifted = target_lift;
m_to_lift = 0;
@@ -960,12 +889,7 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co
// BBS
Vec3d dest_point = point;
auto travel_speed =
this->point_on_first_layer(Vec3d(point.x() - m_x_offset, point.y() - m_y_offset, point.z())) ? this->config.get_abs_value_at("initial_layer_travel_speed", m_cached_extruder_idx) : this->config.travel_speed.get_at(m_cached_extruder_idx);
// See uses_pointwise_travel_speed(): the historical path deliberately emits the
// raw configured speed in the final branch below, ignoring travel_speed.
const double final_travel_speed = this->uses_pointwise_travel_speed()
? travel_speed
: this->config.travel_speed.get_at(m_cached_extruder_idx);
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);
//BBS: a z_hop need to be handle when travel
if (std::abs(m_to_lift) > EPSILON) {
assert(std::abs(m_lifted) < EPSILON);
@@ -1014,23 +938,13 @@ 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());
Vec3d slope_top_point = Vec3d(temp(0), temp(1), delta(2)) + source;
GCodeG1Formatter w0;
// 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_xyz(slope_top_point);
w0.emit_f(travel_speed * 60.0);
//BBS
w0.emit_comment(GCodeWriter::full_gcode_comment, comment);
slop_move = w0.string();
}
else if (m_to_lift_type == LiftType::NormalLift && ! this->must_skip_lift_now()) {
// Only lift in place when the current position is known, for a mapping
// that makes _travel_to_z re-emit logical X/Y: at print start (and after
// custom gcode) m_pos.xy is still the uninitialised origin, which would
// map to a bogus machine point. The xy_z_move below then travels straight
// to the destination with full XYZ and establishes the correct position.
// Mappings that do not need this (the historical Cartesian behaviour)
// report false and keep lifting unconditionally.
else if (m_to_lift_type == LiftType::NormalLift) {
slop_move = _travel_to_z(target.z(), "normal lift Z");
}
}
@@ -1038,14 +952,7 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co
std::string xy_z_move;
{
GCodeG1Formatter w0;
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()) {
if (this->is_current_position_clear()) {
w0.emit_xyz(target);
w0.emit_f(travel_speed * 60.0);
w0.emit_comment(GCodeWriter::full_gcode_comment, comment);
@@ -1083,23 +990,17 @@ 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) };
std::string out_string;
GCodeG1Formatter w;
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(final_travel_speed * 60.0);
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
out_string = w.string();
} else if (!this->is_current_position_clear())
if (!this->is_current_position_clear())
{
//force to move xy first then z after filament change
w.emit_xy(Vec2d(point_on_plate.x(), point_on_plate.y()));
w.emit_f(final_travel_speed * 60.0);
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() + _travel_to_z(point_on_plate.z(), comment);
} else {
GCodeG1Formatter w;
w.emit_xyz(point_on_plate);
w.emit_f(final_travel_speed * 60.0);
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();
}
@@ -1134,19 +1035,12 @@ std::string GCodeWriter::_travel_to_z(double z, const std::string &comment)
double speed = this->config.travel_speed_z.get_at(m_cached_extruder_idx);
if (speed == 0.) {
speed = this->point_on_first_layer(Vec3d(m_pos.x() - m_x_offset, m_pos.y() - m_y_offset, z))
? this->config.get_abs_value_at("initial_layer_travel_speed", m_cached_extruder_idx)
: this->config.travel_speed.get_at(m_cached_extruder_idx);
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);
}
GCodeG1Formatter w;
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_z(z);
w.emit_f(speed * 60.0);
//BBS
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
@@ -1155,14 +1049,6 @@ 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)
{
// 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;
double speed = this->config.travel_speed_z.get_at(m_cached_extruder_idx);
@@ -1269,12 +1155,7 @@ void GCodeWriter::extrude_to_xy(std::string &out, const Vec2d &point, double dE,
Vec2d point_on_plate = { point(0) - m_x_offset, point(1) - m_y_offset };
GCodeG1Formatter w;
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);
}
w.emit_xy(point_on_plate);
if (!force_no_extrusion)
w.emit_e(filament()->E());
//BBS
@@ -1282,42 +1163,6 @@ void GCodeWriter::extrude_to_xy(std::string &out, const Vec2d &point, double dE,
w.append_to(out);
}
// Approximate an arc with linear extrusions, for machine mappings that cannot
// express a G2/G3 (see extrude_arc_to_xy). center_offset is I/J: the centre
// relative to the CURRENT position, which is why this must run before m_pos is
// updated.
void GCodeWriter::extrude_arc_as_polyline(std::string &out, const Vec2d &point, const Vec2d &center_offset,
double dE, const bool is_ccw,
const std::string &comment, bool force_no_extrusion)
{
const Vec2d start = Vec2d(m_pos.x(), m_pos.y());
const Vec2d centre = start + center_offset;
const double r = (start - centre).norm();
if (r < EPSILON) {
// Degenerate: no arc to speak of, so a single move is exact.
this->extrude_to_xy(out, point, dE, comment, force_no_extrusion);
return;
}
double a0 = std::atan2(start.y() - centre.y(), start.x() - centre.x());
double a1 = std::atan2(point.y() - centre.y(), point.x() - centre.x());
double sweep = a1 - a0;
if (is_ccw) { while (sweep <= 0.) sweep += 2. * PI; }
else { while (sweep >= 0.) sweep -= 2. * PI; }
// Segment count from a chord-deviation bound: r*(1-cos(dtheta/2)) <= tol.
const double tol = 0.005; // mm
const double dmax = (tol >= r) ? PI : 2. * std::acos(1. - tol / r);
const int n = std::max(2, int(std::ceil(std::abs(sweep) / std::max(dmax, EPSILON))));
for (int i = 1; i <= n; ++ i) {
const double a = a0 + sweep * (double(i) / double(n));
const Vec2d p = (i == n) ? point
: Vec2d(centre.x() + r * std::cos(a), centre.y() + r * std::sin(a));
this->extrude_to_xy(out, p, dE / double(n), i == n ? comment : std::string(), force_no_extrusion);
}
}
//BBS: generate G2 or G3 extrude which moves by arc
//point is end point which means X and Y axis
//center_offset is I and J axis
@@ -1330,23 +1175,6 @@ std::string GCodeWriter::extrude_arc_to_xy(const Vec2d& point, const Vec2d& cent
void GCodeWriter::extrude_arc_to_xy(std::string &out, const Vec2d& point, const Vec2d& center_offset, double dE, const bool is_ccw, const std::string& comment, bool force_no_extrusion)
{
// Arcs emit only X/Y/I/J, so a mapping that moves logical X or Y cannot be
// expressed as a G2/G3. GCode::should_disable_arc_fitting() normally stops
// arcs being generated at all for such a mapping, but this is public API, so
// define the behaviour rather than asserting.
//
// This check MUST precede every state mutation below: falling through to
// extrude_to_xy() after filament()->extrude(dE) would advance E twice.
//
// A single chord is not a safe substitute either -- a semicircle would become
// its diameter and a full circle a stationary blob -- so approximate the arc
// with linear segments bounded by a chord tolerance, splitting dE between
// them in proportion to arc length.
if (! m_kinematics->supports_arc_moves()) {
this->extrude_arc_as_polyline(out, point, center_offset, dE, is_ccw, comment, force_no_extrusion);
return;
}
m_pos(0) = point(0);
m_pos(1) = point(1);
if (!force_no_extrusion)
@@ -1387,18 +1215,10 @@ void GCodeWriter::extrude_to_xyz(std::string &out, const Vec3d &point, double dE
Vec3d point_on_plate = { point(0) - m_x_offset, point(1) - m_y_offset, point(2) };
GCodeG1Formatter w;
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);
if (z_changed)
w.emit_xyz(point_on_plate);
} else if (z_changed) {
w.emit_xyz(point_on_plate);
} else {
else
w.emit_xy(Vec2d(point_on_plate.x(), point_on_plate.y()));
}
if (!force_no_extrusion)
w.emit_e(filament()->E());
//BBS
+27 -100
View File
@@ -10,38 +10,31 @@
#include <string>
#include <charconv>
#include <vector>
#include <functional>
#include <utility>
#include "Extruder.hpp"
#include "Point.hpp"
#include "Polygon.hpp"
#include "PrintConfig.hpp"
#include "Config.hpp"
#include "GCode/MachineKinematics.hpp"
#include <memory>
namespace Slic3r {
class GCodeWriter {
public:
GCodeConfig config;
bool multiple_extruders;
GCodeWriter() :
multiple_extruders(false),
m_lifted(0),
m_to_lift(0),
m_to_lift_type(LiftType::NormalLift),
m_is_first_layer(true), m_current_speed(3600),
m_kinematics(std::make_unique<CartesianKinematics>()),
m_cached_extruder_idx(0),
m_curr_filament_extruder(MAXIMUM_EXTRUDER_NUMBER, nullptr),
multiple_extruders(false), m_curr_filament_extruder(MAXIMUM_EXTRUDER_NUMBER, nullptr),
m_curr_extruder_id (-1),
m_cached_extruder_idx(0),
m_single_extruder_multi_material(false),
m_last_acceleration(0), m_max_acceleration(0),m_last_travel_acceleration(0), m_max_travel_acceleration(0),
m_last_jerk(0), m_max_jerk_x(0), m_max_jerk_y(0),
m_last_bed_temperature(0), m_last_bed_temperature_reached(true)
m_last_bed_temperature(0), m_last_bed_temperature_reached(true),
m_lifted(0),
m_to_lift(0),
m_to_lift_type(LiftType::NormalLift),
m_current_speed(3600), m_is_first_layer(true)
{}
Extruder* filament(size_t extruder_id) { assert(extruder_id < m_curr_filament_extruder.size()); return m_curr_filament_extruder[extruder_id]; }
const Extruder* filament(size_t extruder_id) const { assert(extruder_id < m_curr_filament_extruder.size()); return m_curr_filament_extruder[extruder_id]; }
@@ -98,10 +91,6 @@ public:
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
std::string extrude_arc_to_xy(const Vec2d &point, const Vec2d &center_offset, double dE, const bool is_ccw, const std::string &comment = std::string(), bool force_no_extrusion = false);
// Linear approximation of an arc, used when the machine mapping cannot
// express a G2/G3. Must be called before m_pos is updated: center_offset is
// relative to the current position.
void extrude_arc_as_polyline(std::string &out, const Vec2d &point, const Vec2d &center_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);
// Each appends its line to `out`.
void set_speed(std::string &out, double F, const std::string &comment = std::string(), const std::string &cooling_marker = std::string());
@@ -158,94 +147,16 @@ public:
void invalidate_acceleration() { m_last_acceleration = 0; m_last_travel_acceleration = 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;
// Install the machine frame mapping. Any axis remap / build volume already
// configured is carried over, so install order does not matter.
void set_kinematics(std::unique_ptr<MachineKinematics> kinematics);
const MachineKinematics& kinematics() const { return *m_kinematics; }
// Per-point first-layer test. When set, travel speed selection asks it per
// destination point (in the writer's logical placed frame) instead of using
// the layer-coarse m_is_first_layer flag. GCode installs it on belt printers
// with the same test its extrusions use (GCode::on_first_layer(point)), so a
// travel is judged against the belt surface exactly as the path it leads to.
using FirstLayerPointTest = std::function<bool(const Vec3d &point_logical)>;
void set_first_layer_point_test(FirstLayerPointTest test) { m_first_layer_point_test = std::move(test); }
// Force every lift to a plain vertical lift. Spiral and slope lifts compute
// their slope in the logical frame and do not account for a machine mapping
// that couples axes.
void set_force_normal_lift(bool force) { m_force_normal_lift = force; }
// Returns whether this flavor supports separate print and travel acceleration.
static bool supports_separate_travel_acceleration(GCodeFlavor flavor);
protected:
// Position/lift/offset state.
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;
std::string _travel_to_z(double z, const std::string &comment);
// Whether a destination gets first-layer treatment. With a point test
// installed it decides; otherwise the layer-coarse m_is_first_layer flag does.
bool point_on_first_layer(const Vec3d &point_logical) const;
// True when a lift must be skipped because this mapping would emit the
// stored logical X/Y and that position is not yet known.
bool must_skip_lift_now() const;
// True when travel speed is selected per destination point rather than per
// layer. Set for writers that install a first-layer point test. The
// historical path emits the raw configured travel speed in the final branch
// of travel_to_xyz(), ignoring the first-layer selection computed at the top
// of that function; a point-test-driven writer uses the first-layer-aware
// value throughout. Both are preserved exactly -- unifying them would change
// emitted feedrates and belongs in its own commit.
bool uses_pointwise_travel_speed() const { return bool(m_first_layer_point_test); }
FirstLayerPointTest m_first_layer_point_test;
bool m_force_normal_lift = false;
// The machine frame mapping. Owns the axis-remap state that used to live
// here as m_remap_* / m_build_vol_max; the setters above forward to it.
// Never null: a CartesianKinematics at the identity remap reproduces the
// historical behaviour exactly.
std::unique_ptr<MachineKinematics> m_kinematics;
// Last configured remap / build volume, replayed onto a newly installed
// kinematics so set_kinematics() and the setters are order-independent.
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 the machine frame mapping to a point. Returns pos unchanged when the
// mapping is the identity.
Vec3d apply_axis_remap(const Vec3d &pos) const;
// Motion uses the global/base process variant until a filament becomes active.
// Protected so subclasses index 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:
private:
// Extruders are sorted by their ID, so that binary search is possible.
std::vector<Extruder> m_filament_extruders;
bool m_single_extruder_multi_material;
std::vector<Extruder*> m_curr_filament_extruder;
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_travel_acceleration;
std::vector<unsigned int> m_max_travel_acceleration;
@@ -267,6 +178,19 @@ private:
//BBS
int m_last_bed_temperature;
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
double m_resolution = 0.01;
@@ -278,18 +202,21 @@ private:
// non-rectangular beds such as delta/circular printers.
Polygon m_bed_printable_area;
std::vector<Polygon> m_extruder_printable_areas;
std::string m_gcode_label_objects_start;
std::string m_gcode_label_objects_end;
//SoftFever
bool m_is_bbl_printers = false;
double m_current_speed;
bool m_is_first_layer = true;
enum class Acceleration {
Travel,
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);
// Orca: printable area of the active extruder (per-extruder when configured, otherwise the bed). Null when unknown.
const Polygon *active_printable_area() const;
+9 -16
View File
@@ -1259,7 +1259,7 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
// project downards pointing painted triangles over bottom surfaces.
std::vector<std::vector<Polygons>> top_raw(num_facets_states), bottom_raw(num_facets_states);
std::vector<float> zs = zs_from_layers(layers);
Transform3d object_trafo = print_object.trafo_sliced();
Transform3d object_trafo = print_object.trafo_centered();
#ifdef MM_SEGMENTATION_DEBUG_TOP_BOTTOM
static int iRun = 0;
@@ -1288,16 +1288,10 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
slicing_params.trafo = volume_trafo;
Polygons bottom_slice = slice_mesh(painted, zs[0], slicing_params);
// Only the requested projections exist: with
// top_shell_layers = 0 `top` is empty and erasing its begin() was
// undefined (found by fuzzing: a sunk, painted object crashed here).
if (! top.empty())
top.erase(top.begin());
if (! bottom.empty()) {
bottom.erase(bottom.begin());
if (! bottom.empty())
bottom[0] = union_(bottom[0], bottom_slice);
}
top.erase(top.begin());
bottom.erase(bottom.begin());
bottom[0] = union_(bottom[0], bottom_slice);
} else
slice_mesh_slabs(painted, zs, volume_trafo, max_top_layers > 0 ? &top : nullptr, max_bottom_layers > 0 ? &bottom : nullptr, nullptr, throw_on_cancel_callback);
auto merge = [](std::vector<Polygons> &&src, std::vector<Polygons> &dst) {
@@ -2093,19 +2087,17 @@ std::vector<std::vector<ExPolygons>> segmentation_by_painting(const PrintObject
}
BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Projection of painted triangles - Begin";
// The layers were sliced in this frame (belt rotation, remap and Z lift included), and it already centers the object.
const Transform3d object_trafo = print_object.trafo_sliced();
for (const ModelVolume *mv : print_object.model_object()->volumes) {
const ModelVolumeFacetsInfo facets_info = extract_facets_info(*mv);
tbb::parallel_for(tbb::blocked_range<size_t>(1, num_facets_states), [&mv, &object_trafo, &facets_info, &layers, &edge_grids, &painted_lines, &painted_lines_mutex, &input_expolygons, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
tbb::parallel_for(tbb::blocked_range<size_t>(1, num_facets_states), [&mv, &print_object, &facets_info, &layers, &edge_grids, &painted_lines, &painted_lines_mutex, &input_expolygons, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
for (size_t extruder_idx = range.begin(); extruder_idx < range.end(); ++extruder_idx) {
throw_on_cancel_callback();
const indexed_triangle_set custom_facets = facets_info.facets_annotation.get_facets(*mv, EnforcerBlockerType(extruder_idx));
if (!mv->is_model_part() || custom_facets.indices.empty())
continue;
const Transform3f tr = (object_trafo * mv->get_matrix()).cast<float>();
tbb::parallel_for(tbb::blocked_range<size_t>(0, custom_facets.indices.size()), [&tr, &custom_facets, &layers, &edge_grids, &input_expolygons, &painted_lines, &painted_lines_mutex, &extruder_idx](const tbb::blocked_range<size_t> &range) {
const Transform3f tr = print_object.trafo().cast<float>() * mv->get_matrix().cast<float>();
tbb::parallel_for(tbb::blocked_range<size_t>(0, custom_facets.indices.size()), [&tr, &custom_facets, &print_object, &layers, &edge_grids, &input_expolygons, &painted_lines, &painted_lines_mutex, &extruder_idx](const tbb::blocked_range<size_t> &range) {
for (size_t facet_idx = range.begin(); facet_idx < range.end(); ++facet_idx) {
float min_z = std::numeric_limits<float>::max();
float max_z = std::numeric_limits<float>::lowest();
@@ -2158,6 +2150,7 @@ std::vector<std::vector<ExPolygons>> segmentation_by_painting(const PrintObject
Line line_to_test(Point(scale_(line_start_f.x()), scale_(line_start_f.y())),
Point(scale_(line_end_f.x()), scale_(line_end_f.y())));
line_to_test.translate(-print_object.center_offset());
// BoundingBoxes for EdgeGrids are computed from printable regions. It is possible that the painted line (line_to_test) could
// be outside EdgeGrid's BoundingBox, for example, when the negative volume is used on the painted area (GH #7618).
+3 -14
View File
@@ -1237,7 +1237,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",
"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",
"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",
"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",
"support_base_pattern", "support_base_pattern_spacing", "support_expansion", "support_style",
// BBS
@@ -1307,8 +1307,6 @@ static std::vector<std::string> s_Preset_print_options{
"prime_volume",
"prime_tower_infill_gap",
"prime_tower_flat_ironing",
"belt_purge_tower_width",
"belt_purge_tower_object",
"enable_tower_interface_features",
"enable_tower_interface_cooldown_during_tower",
"wipe_tower_no_sparse_layers",
@@ -1559,17 +1557,8 @@ static std::vector<std::string> s_Preset_machine_limits_options {
static std::vector<std::string> s_Preset_printer_options {
"printer_technology",
"printable_area", "extruder_printable_area", "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",
"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",
"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",
"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",
"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", "extruder_clearance_dist_to_rod",
+49 -332
View File
@@ -69,8 +69,6 @@
#include "Thread.hpp"
#include "Time.hpp"
#include "GCode.hpp"
#include "BeltGCode.hpp"
#include "BeltTransform.hpp"
#include "GCode/WipeTower.hpp"
#include "GCode/WipeTower2.hpp"
#include "GCode/WipeTowerEstimate.hpp"
@@ -79,7 +77,6 @@
#include "MaterialType.hpp"
#include "Model.hpp"
#include "format.hpp"
#include "LocalesUtils.hpp"
#include <float.h>
#include <algorithm>
@@ -106,7 +103,6 @@
#include "Format/STEP.hpp"
#include "PlaceholderParser.hpp"
#include "SurfaceCollection.hpp"
#include "BeltBrim.hpp"
namespace fs = boost::filesystem;
@@ -170,16 +166,6 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
// Cache the plenty of parameters, which influence the G-code generator only,
// or they are only notes not influencing the generated G-code.
static std::unordered_set<std::string> steps_gcode = {
// Belt printer G-code axis remap (only affects G-code output, not slicing).
"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",
"gcode_back_transform",
"first_layer_plane", "first_layer_plane_offset", "first_layer_plane_thickness",
// Only inflates the GUI bed volume, like printable_area.
"belt_printer_infinite_y",
//BBS
"additional_cooling_fan_speed",
"reduce_crossing_wall",
@@ -381,26 +367,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
// Spiral Vase forces different kind of slicing than the normal model:
// In Spiral Vase mode, holes are closed and only the largest area contour is kept at each layer.
// Therefore toggling the Spiral Vase on / off requires complete reslicing.
|| opt_key == "spiral_mode"
// 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") {
|| opt_key == "spiral_mode") {
osteps.emplace_back(posSlice);
} else if (
opt_key == "belt_support_floor_offset"
|| opt_key == "belt_support_floor_mode"
|| opt_key == "belt_support_z_offset_mode") {
osteps.emplace_back(posSupportMaterial);
} else if (
opt_key == "print_sequence"
|| opt_key == "filament_type"
@@ -435,7 +403,6 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|| opt_key == "hot_plate_temp"
|| opt_key == "textured_plate_temp"
|| opt_key == "enable_prime_tower"
|| opt_key == "enable_belt_purge_tower"
|| opt_key == "enable_wrapping_detection"
|| opt_key == "prime_tower_enable_framework"
|| opt_key == "prime_tower_width"
@@ -470,7 +437,6 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|| opt_key == "prime_volume"
|| opt_key == "flush_into_infill"
|| opt_key == "flush_into_support"
|| opt_key == "belt_purge_tower_width"
|| opt_key == "initial_layer_infill_speed"
|| opt_key == "travel_speed"
|| opt_key == "travel_speed_z"
@@ -702,9 +668,6 @@ std::vector<ObjectID> Print::print_object_ids() 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.
// return (m_config.draft_shield == dsEnabled && m_config.skirt_loops > 0) || (m_config.ooze_prevention && this->extruders().size() > 1);
@@ -713,9 +676,6 @@ bool Print::has_infinite_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);
}
@@ -724,24 +684,6 @@ bool Print::has_brim() const
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
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)
{
@@ -1861,83 +1803,6 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
if (extruders.empty())
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
// spiral vase cannot share. The prime tower setting is no obstacle: belt
// printers never print the classic tower, and the belt purge prism is an
// ordinary object that never takes a brim.
if (this->has_belt_brim()) {
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;
// without it ToolOrdering::resolve_mixed_filaments prints one whole component per layer and
// the gradient is dropped silently. extruders() already covers painting, height ranges,
@@ -2003,40 +1868,6 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
}
}
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);
}
// The purge tower is a model object the GUI creates and sizes; libslic3r only purges
// into one that exists. A project sliced without it (the CLI on a project saved before
// the tower was generated) changes filament with nowhere to purge.
if (m_config.belt_printer.value && m_config.enable_belt_purge_tower.value
&& m_config.print_sequence != PrintSequence::ByObject
&& ! m_config.spiral_mode.value && this->object_extruders().size() > 1 && ! this->has_belt_purge_tower()) {
StringObjectException warningtemp;
warningtemp.string = L("The belt purge tower is enabled but the project has no purge tower object; "
"filament changes will not be purged. Open the project in the application "
"to generate the tower.");
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) {
for (const PrintObject* object : m_objects) {
if (object->config().precise_z_height.value) {
@@ -2090,59 +1921,35 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
return profile;
};
// Checks that the print does not exceed the max print height.
// 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.
// The machine-frame transform only changes how that height is written to
// G-code, not how much room there is under the gantry.
const bool belt_printer = this->config().belt_printer.value;
const double shrinkage_compensation_z = this->shrinkage_compensation().z();
// 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) {
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()) {
double effective_max_z = 0;
bool last_layer_below_max = false;
bool have_height = false;
if (belt_printer) {
double raw_z = print_object.model_object()->max_z();
if (BeltTransformPipeline::has_preslice_remap(this->config()))
raw_z = BeltTransformPipeline::remap_bbox(*print_object.model_object(), this->config()).size().z();
effective_max_z = raw_z;
have_height = raw_z > 0;
} else {
//FIXME It is quite expensive to generate object layers just to get the print height!
auto layers = generate_object_layers(print_object.slicing_parameters(), layer_height_profile(print_object_idx), print_object.config().precise_z_height.value);
if (!layers.empty()) {
effective_max_z = layers.back();
last_layer_below_max = layers.size() >= 2 &&
0.5 * (layers[layers.size() - 2] + layers.back()) <= this->config().printable_height + EPSILON;
have_height = true;
Vec3d test =this->shrinkage_compensation();
const double shrinkage_compensation_z = this->shrinkage_compensation().z();
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.
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 (layers.back() > this->config().printable_height + EPSILON) {
// Test whether the last slicing plane is below or above the print volume.
return StringObjectException{
0.5 * (layers[layers.size() - 2] + layers.back()) > this->config().printable_height + EPSILON ?
Slic3r::format(_u8L("The object %1% exceeds the maximum build volume height."), print_object.model_object()->name) :
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."),
print_object.model_object(),
""
};
}
}
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.
@@ -2162,12 +1969,12 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
return {_u8L("Variable layer height is not supported with Organic supports.") };
}
if ((this->has_wipe_tower() || this->has_belt_purge_tower()) && ! m_objects.empty()) {
if (this->has_wipe_tower() && ! m_objects.empty()) {
// 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
// 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.
if (this->has_wipe_tower() && m_config.wipe_tower_filament > 0) {
if (m_config.wipe_tower_filament > 0) {
const auto &is_mixed = m_config.filament_is_mixed.values;
const size_t wipe_idx = size_t(m_config.wipe_tower_filament - 1);
if (wipe_idx < is_mixed.size() && is_mixed[wipe_idx])
@@ -2189,17 +1996,12 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
}
}
// The following two constraints come from the classic wipe tower G-code
// 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.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 0
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)
@@ -2925,28 +2727,8 @@ BoundingBox PrintObject::get_first_layer_bbox(float& a, float& layer_height, std
a += area(slice);
}
}
// 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)
if (has_brim())
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;
}
@@ -2993,19 +2775,6 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
if (m_objects.empty())
return;
// Belt purge prism: _plan_belt_purge() (psWipeTower) truncates the prism's
// layers and drops its unclaimed fills, stashing both so a replan can undo
// them. The object steps below regenerate per-layer content over m_layers
// ONLY, so if any of them is about to rerun the stashes must go back first;
// otherwise truncated layers keep stale perimeters/fills and dropped fills
// are re-inserted next to freshly generated ones. Every object-step
// invalidation also invalidates psWipeTower, so "psWipeTower not done" is
// exactly "some object step may rerun" -- and when it IS done nothing below
// regenerates, and the plan's edits have to stay.
if (!this->is_step_done(psWipeTower))
for (PrintObject *obj : m_objects)
obj->belt_undo_purge_plan();
{
LifecycleEventContext ctx;
ctx.id = std::to_string(m_model.id().id);
@@ -3067,24 +2836,15 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
int object_count = m_objects.size();
std::set<PrintObject*> need_slicing_objects;
std::set<PrintObject*> re_slicing_objects;
// Belt global 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) {
for (int index = 0; index < object_count; index++)
{
PrintObject *obj = m_objects[index];
if (!belt_no_share) {
for (PrintObject *slicing_obj : need_slicing_objects)
{
if (is_print_object_the_same(obj, slicing_obj)) {
obj->set_shared_object(slicing_obj);
break;
}
for (PrintObject *slicing_obj : need_slicing_objects)
{
if (is_print_object_the_same(obj, slicing_obj)) {
obj->set_shared_object(slicing_obj);
break;
}
}
if (!obj->get_shared_object())
@@ -3103,14 +2863,12 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
PrintObject *obj = m_objects[index];
bool found_shared = false;
if (need_slicing_objects.find(obj) == need_slicing_objects.end()) {
if (!belt_no_share) {
for (PrintObject *slicing_obj : need_slicing_objects)
{
if (is_print_object_the_same(obj, slicing_obj)) {
obj->set_shared_object(slicing_obj);
found_shared = true;
break;
}
for (PrintObject *slicing_obj : need_slicing_objects)
{
if (is_print_object_the_same(obj, slicing_obj)) {
obj->set_shared_object(slicing_obj);
found_shared = true;
break;
}
}
if (!found_shared) {
@@ -3318,10 +3076,7 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
m_wipe_tower_data.clear();
m_tool_ordering.clear();
if (this->has_belt_purge_tower() && this->config().print_sequence != PrintSequence::ByObject) {
this->_plan_belt_purge();
}
else if (this->has_wipe_tower()) {
if (this->has_wipe_tower()) {
this->_make_wipe_tower();
}
else if (this->config().print_sequence != PrintSequence::ByObject) {
@@ -3575,26 +3330,6 @@ 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()) {
// Generate skirt/brim groups after brim so per-object and draft-shield footprints
// include brims when grouping and offsetting skirt loops.
@@ -3698,17 +3433,12 @@ std::string Print::export_gcode(const std::string& path_template, GCodeProcessor
this->set_status(80, message);
// The following line may die for multiple reasons.
// 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>();
GCode gcode;
//BBS: compute plate offset for gcode-generator
const Vec3d origin = this->get_plate_origin();
gcode->set_gcode_offset(origin(0), origin(1));
gcode->do_export(this, path.c_str(), result, thumbnail_cb);
gcode->export_layer_filaments(result);
gcode.set_gcode_offset(origin(0), origin(1));
gcode.do_export(this, path.c_str(), result, thumbnail_cb);
gcode.export_layer_filaments(result);
//BBS
if (result != nullptr) {
result->conflict_result = m_conflict_result;
@@ -3723,10 +3453,6 @@ std::string Print::export_gcode(const std::string& path_template, GCodeProcessor
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();
// First off we need to decide how tall the skirt must be.
@@ -4775,13 +4501,6 @@ int Print::get_config_index(int filament_id, int layer_id, const std::vector<std
// Wipe tower support.
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_wrapping_detection.value && m_config.wrapping_exclude_area.values.size() > 2)
return true;
@@ -4794,7 +4513,6 @@ bool Print::has_wipe_tower() const
return false;
}
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
@@ -4824,7 +4542,6 @@ bool Print::enable_timelapse_print() const
return m_config.timelapse_type.value == TimelapseType::tlSmooth;
}
void Print::_make_wipe_tower()
{
m_wipe_tower_data.clear();

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