Add real slice validation for all printers (#14771)

# Description

Adds a --slice (-s) mode to the profile validator that slices a
two-colour cube through every shipped printer, expanding all custom
g-code (change_filament_gcode, machine start/end, etc.). This catches
invalid-placeholder / bad-flow / slicing errors that the static JSON
checks and unit tests can't see.

Included:
- Validator: new -s sweep mode; per-profile error attribution in the
log; resolves the synthetic 2nd-filament nozzle-mapping so multi-nozzle
BBL printers (incl. the Direct-Drive+Bowden X2D) validate cleanly.
- CI, two complementary paths:
- check_profiles.yml — runs the sweep on profile-only PRs (nightly
binary).
- build_all.yml — new parallel slice_check_linux job runs it on
engine/src PRs with the PR-built binary (build_all doesn't trigger on
resources/**, so no overlap). Runs off the build's artifact, so it
doesn't lengthen the build leg.
- Profile fixes surfaced by the sweep: Creality, FLSun, Ginger, Qidi,
RatRig, iQ.
- Engine: whitelist BBL firmware T-opcodes (T1001/T65279/T65535) in the
time estimator (log-only, no g-code change); dedupe a
per-filament/per-layer log flood in get_config_index.

# Screenshots/Recordings/Graphs

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

## Tests

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

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

[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
This commit is contained in:
SoftFever
2026-07-15 16:32:09 +08:00
committed by GitHub
46 changed files with 1462 additions and 454 deletions

View File

@@ -165,6 +165,30 @@ jobs:
os: ${{ vars.SELF_HOSTED && 'orca-macos-arm64' || 'macos-14' }}
artifact: ${{ github.sha }}-tests-macos-arm64
test-dir: build/arm64/tests
# Slice a two-colour cube through every shipped printer with this PR's engine
# so all custom g-code (change_filament_gcode, machine start/end, etc.) is
# expanded - catches slicing regressions the static profile checks and unit
# tests can't see. Profile-only PRs are covered by check_profiles.yml (nightly
# binary); this covers src/engine PRs with the PR-built binary. Runs in
# parallel off build_linux's artifact so it doesn't lengthen the build leg.
slice_check_linux:
name: Slice check (Linux x86_64)
needs: build_linux
if: ${{ !cancelled() && success() }}
runs-on: ${{ vars.SELF_HOSTED && 'orca-lnx-server' || 'ubuntu-24.04' }}
steps:
- name: Checkout repository
uses: actions/checkout@v7
- name: Download profile validator
uses: actions/download-artifact@v8
with:
name: ${{ github.sha }}-profile-validator-linux-x86_64
path: validator-bin
- name: Validate slice (expand custom g-code)
timeout-minutes: 60
run: |
chmod +x validator-bin/OrcaSlicer_profile_validator
./validator-bin/OrcaSlicer_profile_validator -p "${{ github.workspace }}/resources/profiles" -s -l 2
publish_test_results:
name: Publish Test Results
needs: [unit_tests_linux_x86_64, unit_tests_linux_aarch64, unit_tests_windows_x64, unit_tests_windows_arm64, unit_tests_macos_arm64]

View File

@@ -508,6 +508,20 @@ jobs:
retention-days: 5
if-no-files-found: error
# Ship the freshly-built validator so the parallel slice_check_linux job
# (build_all.yml) can slice-sweep the shipped profiles with this PR's
# engine. Stable sha-based name mirrors the tests artifact above so the
# downstream job downloads it by exact name.
- name: Upload profile validator (for slice check)
if: runner.os == 'Linux' && inputs.arch != 'aarch64'
uses: actions/upload-artifact@v7
with:
name: ${{ github.sha }}-profile-validator-linux-x86_64
overwrite: true
path: ./build/src/Release/OrcaSlicer_profile_validator
retention-days: 5
if-no-files-found: error
- name: Run external slicer regression tests
if: runner.os == 'Linux' && inputs.arch != 'aarch64'
timeout-minutes: 20

View File

@@ -49,6 +49,15 @@ jobs:
set +e
./OrcaSlicer_profile_validator -p ${{ github.workspace }}/resources/profiles -l 2 2>&1 | tee ${{ runner.temp }}/validate_system.log
exit ${PIPESTATUS[0]}
# Slice a two-colour cube through every printer so all custom g-code (incl. change_filament_gcode)
# is expanded - catches undefined-placeholder / invalid-flow bugs the static checks above cannot see.
- name: validate slice (expand custom g-code)
id: validate_slice
continue-on-error: true
run: |
set +e
./OrcaSlicer_profile_validator -p ${{ github.workspace }}/resources/profiles -s -l 2 2>&1 | tee ${{ runner.temp }}/validate_slice.log
exit ${PIPESTATUS[0]}
# For now run filament subtype check only for BBL profiles until we fix other vendors' profiles.
- name: validate filament subtype check for BBL profiles
id: validate_filament_subtypes
@@ -166,7 +175,7 @@ jobs:
echo "${{ github.event.pull_request.number }}" > ${{ runner.temp }}/profile-check-results/pr_number.txt
- name: Prepare comment artifact
if: ${{ always() && github.event_name == 'pull_request' && (steps.extra_json_check.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_filament_subtypes.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }}
if: ${{ always() && github.event_name == 'pull_request' && (steps.extra_json_check.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_slice.outcome == 'failure' || steps.validate_filament_subtypes.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }}
run: |
{
# Marker matched by check_profiles_comment.yml to delete prior comments.
@@ -192,6 +201,15 @@ jobs:
echo ""
fi
if [ "${{ steps.validate_slice.outcome }}" = "failure" ]; then
echo "### Slice Validation Failed (custom g-code expansion)"
echo ""
echo '```'
head -c 30000 ${{ runner.temp }}/validate_slice.log || echo "No output captured"
echo '```'
echo ""
fi
if [ "${{ steps.validate_filament_subtypes.outcome }}" = "failure" ]; then
echo "### BBL Filament Subtype Validation Failed"
echo ""
@@ -223,7 +241,7 @@ jobs:
retention-days: 1
- name: Fail if any check failed
if: ${{ always() && (steps.extra_json_check.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_filament_subtypes.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }}
if: ${{ always() && (steps.extra_json_check.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_slice.outcome == 'failure' || steps.validate_filament_subtypes.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }}
run: |
echo "One or more profile checks failed. See above for details."
exit 1

View File

@@ -28,11 +28,15 @@ jobs:
- name: Checkout
uses: actions/checkout@v7
with:
# tests/data is referenced by absolute path (TEST_DATA_DIR).
# Tests reach outside tests/ at runtime: tests/data (TEST_DATA_DIR) and
# resources/profiles (PROFILES_DIR) by baked-in absolute path, plus
# resources/info (nozzle data) via resources_dir() during a real slice.
# Check out all of resources/ so no test hits a missing-file path.
sparse-checkout: |
.github
scripts
tests
resources
- name: Apt-Install Dependencies
if: runner.os == 'Linux' && !vars.SELF_HOSTED
uses: ./.github/actions/apt-install-deps

View File

@@ -1230,6 +1230,10 @@
"name": "0.40mm Standard @Creality K1C",
"sub_path": "process/0.40mm Standard @Creality K1C 0.8 nozzle.json"
},
{
"name": "0.40mm Standard @Creality K1 SE 0.8 nozzle",
"sub_path": "process/0.40mm Standard @Creality K1 SE 0.8 nozzle.json"
},
{
"name": "0.40mm Standard @Creality K1Max (0.8 nozzle)",
"sub_path": "process/0.40mm Standard @Creality K1Max (0.8 nozzle).json"

View File

@@ -11,7 +11,7 @@
"Creality Generic PLA"
],
"printer_variant": "0.2",
"default_print_profile": "0.16mm Opitmal @Creality CR-6 0.2",
"default_print_profile": "0.16mm Optimal @Creality CR-6 0.2",
"nozzle_diameter": [
"0.2"
],

View File

@@ -11,7 +11,7 @@
"Creality Generic PLA"
],
"printer_variant": "0.2",
"default_print_profile": "0.16mm Opitmal @Creality CR-6 0.2",
"default_print_profile": "0.16mm Optimal @Creality CR-6 0.2",
"nozzle_diameter": [
"0.2"
],

View File

@@ -69,7 +69,7 @@
"support_on_build_plate_only": "0",
"support_top_z_distance": "0.15",
"support_filament": "0",
"support_line_width": "0.18",
"support_line_width": "0.22",
"support_interface_loop_pattern": "0",
"support_interface_filament": "0",
"support_interface_top_layers": "3",
@@ -85,7 +85,7 @@
"tree_support_wall_count": "0",
"detect_thin_wall": "1",
"top_surface_pattern": "monotonicline",
"top_surface_line_width": "0.2",
"top_surface_line_width": "0.25",
"top_shell_layers": "5",
"top_shell_thickness": "0.8",
"initial_layer_speed": "35%",

View File

@@ -69,7 +69,7 @@
"support_on_build_plate_only": "0",
"support_top_z_distance": "0.15",
"support_filament": "0",
"support_line_width": "0.18",
"support_line_width": "0.22",
"support_interface_loop_pattern": "0",
"support_interface_filament": "0",
"support_interface_top_layers": "3",
@@ -85,7 +85,7 @@
"tree_support_wall_count": "0",
"detect_thin_wall": "1",
"top_surface_pattern": "monotonicline",
"top_surface_line_width": "0.2",
"top_surface_line_width": "0.25",
"top_shell_layers": "5",
"top_shell_thickness": "0.8",
"initial_layer_speed": "35%",

View File

@@ -59,7 +59,7 @@
"skirt_height": "2",
"skirt_loops": "2",
"minimum_sparse_infill_area": "10",
"internal_solid_infill_line_width": "0.2",
"internal_solid_infill_line_width": "0.25",
"spiral_mode": "0",
"standby_temperature_delta": "-5",
"enable_support": "0",
@@ -69,7 +69,7 @@
"support_on_build_plate_only": "0",
"support_top_z_distance": "0.15",
"support_filament": "0",
"support_line_width": "0.18",
"support_line_width": "0.22",
"support_interface_loop_pattern": "0",
"support_interface_filament": "0",
"support_interface_top_layers": "3",
@@ -85,7 +85,7 @@
"tree_support_wall_count": "0",
"detect_thin_wall": "1",
"top_surface_pattern": "monotonicline",
"top_surface_line_width": "0.2",
"top_surface_line_width": "0.25",
"top_shell_layers": "7",
"top_shell_thickness": "0.8",
"initial_layer_speed": "35%",

View File

@@ -71,7 +71,7 @@
"support_on_build_plate_only": "0",
"support_top_z_distance": "0.15",
"support_filament": "0",
"support_line_width": "0.18",
"support_line_width": "0.22",
"support_interface_loop_pattern": "0",
"support_interface_filament": "0",
"support_interface_top_layers": "2",

View File

@@ -0,0 +1,264 @@
{
"type": "process",
"name": "0.40mm Standard @Creality K1 SE 0.8 nozzle",
"inherits": "fdm_process_creality_common",
"from": "system",
"setting_id": "caYmVSsFmtESzLNF",
"instantiation": "true",
"reduce_crossing_wall": "0",
"max_travel_detour_distance": "0",
"bottom_surface_pattern": "monotonic",
"bottom_shell_layers": "3",
"bottom_shell_thickness": "0",
"bridge_flow": "0.8",
"bridge_speed": "10",
"brim_width": "5",
"brim_object_gap": "0.1",
"print_sequence": "by layer",
"default_acceleration": "10000",
"bridge_no_support": "0",
"draft_shield": "disabled",
"elefant_foot_compensation": "0.15",
"outer_wall_line_width": "0.82",
"outer_wall_speed": "100",
"outer_wall_acceleration": "2000",
"inner_wall_acceleration": "2000",
"line_width": "0.82",
"infill_direction": "45",
"sparse_infill_density": "15%",
"sparse_infill_pattern": "grid",
"initial_layer_acceleration": "500",
"initial_layer_line_width": "0.82",
"initial_layer_print_height": "0.4",
"initial_layer_speed": "40",
"gap_infill_speed": "50",
"infill_combination": "0",
"sparse_infill_line_width": "0.82",
"infill_wall_overlap": "30",
"sparse_infill_speed": "150",
"interface_shells": "0",
"ironing_flow": "10%",
"ironing_spacing": "0.15",
"ironing_speed": "30",
"ironing_type": "no ironing",
"layer_height": "0.4",
"reduce_infill_retraction": "1",
"filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode",
"detect_overhang_wall": "1",
"overhang_1_4_speed": "50",
"overhang_2_4_speed": "35",
"overhang_3_4_speed": "20",
"overhang_4_4_speed": "10",
"only_one_wall_top": "1",
"inner_wall_line_width": "0.82",
"inner_wall_speed": "150",
"wall_loops": "2",
"raft_layers": "0",
"seam_position": "aligned",
"seam_slope_conditional": "0",
"seam_slope_inner_walls": "0",
"seam_slope_entire_loop": "0",
"skirt_distance": "2",
"skirt_height": "1",
"skirt_loops": "0",
"minimum_sparse_infill_area": "15",
"internal_solid_infill_line_width": "0.82",
"internal_solid_infill_speed": "150",
"spiral_mode": "0",
"initial_layer_infill_speed": "60",
"standby_temperature_delta": "-5",
"enable_support": "0",
"resolution": "0.012",
"support_type": "normal(auto)",
"support_style": "default",
"support_on_build_plate_only": "0",
"support_top_z_distance": "0.2",
"support_bottom_z_distance": "0.2",
"support_filament": "0",
"support_line_width": "0.82",
"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_expansion": "0",
"support_interface_speed": "80",
"support_base_pattern": "rectilinear",
"support_base_pattern_spacing": "2.5",
"support_speed": "150",
"support_threshold_angle": "30",
"support_object_xy_distance": "0.35",
"tree_support_branch_diameter": "2",
"tree_support_branch_angle": "45",
"tree_support_wall_count": "0",
"detect_thin_wall": "0",
"top_surface_pattern": "monotonicline",
"top_surface_line_width": "0.82",
"top_surface_acceleration": "2000",
"top_surface_speed": "100",
"top_shell_layers": "3",
"top_shell_thickness": "0.8",
"travel_acceleration": "10000",
"travel_speed": "500",
"enable_prime_tower": "0",
"wipe_tower_no_sparse_layers": "0",
"prime_tower_width": "60",
"xy_hole_compensation": "0",
"xy_contour_compensation": "0",
"gcode_label_objects": "0",
"compatible_printers": [
"Creality K1 SE 0.8 nozzle"
],
"accel_to_decel_enable": "1",
"accel_to_decel_factor": "50",
"acceleration_limit_mess_enable": "0",
"ai_infill": "0",
"alternate_extra_wall": "0",
"bottom_solid_infill_flow_ratio": "1",
"bridge_acceleration": "50%",
"bridge_angle": "0",
"bridge_density": "100%",
"brim_ears_detection_length": "1",
"brim_ears_max_angle": "125",
"brim_type": "auto_brim",
"counterbore_hole_bridging": "none",
"default_jerk": "6",
"detect_narrow_internal_solid_infill": "1",
"dont_filter_internal_bridges": "disabled",
"elefant_foot_compensation_layers": "1",
"enable_arc_fitting": "1",
"enable_overhang_speed": "1",
"enforce_support_layers": "0",
"ensure_vertical_shell_thickness": "ensure_all",
"exclude_object": "1",
"extra_perimeters_on_overhangs": "0",
"filter_out_gap_fill": "0",
"flush_into_infill": "0",
"flush_into_objects": "0",
"flush_into_support": "1",
"fuzzy_skin": "none",
"fuzzy_skin_first_layer": "0",
"fuzzy_skin_point_distance": "0.8",
"fuzzy_skin_thickness": "0.3",
"gap_fill_target": "everywhere",
"gcode_add_line_number": "0",
"gcode_comments": "0",
"hole_to_polyhole": "0",
"hole_to_polyhole_threshold": "0.01",
"hole_to_polyhole_twisted": "1",
"independent_support_layer_height": "1",
"infill_anchor": "400%",
"infill_anchor_max": "20",
"infill_jerk": "6",
"initial_layer_jerk": "6",
"initial_layer_min_bead_width": "85%",
"initial_layer_travel_speed": "100%",
"inner_wall_jerk": "6",
"internal_bridge_flow": "1",
"internal_bridge_speed": "70",
"internal_solid_infill_acceleration": "100%",
"internal_solid_infill_pattern": "zig-zag",
"ironing_angle": "90",
"ironing_pattern": "zig-zag",
"ironing_support_layer": "0",
"is_infill_first": "0",
"make_overhang_printable": "0",
"make_overhang_printable_angle": "55",
"make_overhang_printable_hole_size": "0",
"max_bridge_length": "10",
"max_volumetric_extrusion_rate_slope": "0",
"max_volumetric_extrusion_rate_slope_segment_length": "3",
"min_bead_width": "85%",
"min_feature_size": "25%",
"min_length_factor": "0.5",
"min_width_top_surface": "300%",
"minimum_support_area": "5",
"mmu_segmented_region_interlocking_depth": "0",
"mmu_segmented_region_max_width": "0",
"only_one_wall_first_layer": "0",
"ooze_prevention": "0",
"outer_wall_jerk": "6",
"overhang_reverse": "0",
"overhang_reverse_internal_only": "0",
"overhang_reverse_threshold": "50%",
"overhang_speed_classic": "0",
"precise_outer_wall": "0",
"prime_tower_brim_width": "3",
"prime_tower_enhance_type": "chamfer",
"prime_volume": "45",
"print_flow_ratio": "1",
"print_order": "default",
"raft_contact_distance": "0.1",
"raft_expansion": "1.5",
"raft_first_layer_density": "90%",
"raft_first_layer_expansion": "2",
"role_based_wipe_speed": "1",
"scarf_angle_threshold": "155",
"scarf_joint_flow_ratio": "1",
"scarf_joint_speed": "100%",
"scarf_overhang_threshold": "40%",
"seam_gap": "10%",
"seam_slope_min_length": "20",
"seam_slope_start_height": "0",
"seam_slope_steps": "10",
"seam_slope_type": "none",
"single_extruder_multi_material_priming": "0",
"skirt_speed": "50",
"slice_closing_radius": "0.049",
"slicing_mode": "regular",
"slow_down_layers": "0",
"slowdown_for_curled_perimeters": "0",
"small_area_infill_flow_compensation": "0",
"small_area_infill_flow_compensation_model": "0,0;\n0.2,0.4444;\n0.4,0.6145;\n0.6,0.7059;\n0.8,0.7619;\n1.5,0.8571;\n2,0.8889;\n3,0.9231;\n5,0.9520;\n10,1",
"small_perimeter_speed": "30%",
"small_perimeter_threshold": "20",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "100%",
"sparse_infill_filament": "1",
"speed_limit_to_height_enable": "0",
"spiral_mode_max_xy_smoothing": "200%",
"spiral_mode_smooth": "0",
"staggered_inner_seams": "1",
"support_angle": "0",
"support_bottom_interface_spacing": "0.5",
"support_critical_regions_only": "0",
"support_interface_not_for_body": "1",
"support_interface_pattern": "auto",
"support_remove_small_overhang": "1",
"support_xy_overrides_z": "xy_overrides_z",
"thick_bridges": "0",
"thick_internal_bridges": "0",
"timelapse_type": "0",
"top_solid_infill_flow_ratio": "1",
"top_surface_jerk": "6",
"travel_jerk": "6",
"travel_speed_z": "0",
"tree_support_adaptive_layer_height": "1",
"tree_support_angle_slow": "25",
"tree_support_auto_brim": "1",
"tree_support_branch_angle_organic": "40",
"tree_support_branch_diameter_angle": "5",
"tree_support_branch_diameter_double_wall": "3",
"tree_support_branch_diameter_organic": "2",
"tree_support_branch_distance": "5",
"tree_support_branch_distance_organic": "1",
"tree_support_brim_width": "3",
"tree_support_tip_diameter": "0.82",
"tree_support_top_rate": "30%",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_generator": "classic",
"wall_sequence": "inner wall/outer wall",
"wall_transition_angle": "10",
"wall_transition_filter_deviation": "25%",
"wall_transition_length": "100%",
"wipe_before_external_loop": "0",
"wipe_on_loops": "0",
"wipe_speed": "80%",
"wipe_tower_bridging": "10",
"wipe_tower_cone_angle": "0",
"wipe_tower_extra_spacing": "100%",
"wipe_tower_rotation_angle": "0",
"wiping_volumes_extruders": "70,70,70,70,70,70,70,70,70,70"
}

View File

@@ -1,6 +1,6 @@
{
"name": "FLSun",
"version": "02.04.00.01",
"version": "02.04.00.02",
"force_update": "0",
"description": "FLSun configurations",
"machine_model_list": [

View File

@@ -182,7 +182,7 @@
"default_filament_profile": [
"FLSun Generic PLA"
],
"machine_start_gcode": ";STARTGCODE\nM117 Initializing\n; Set coordinate modes\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\n; Reset speed and extrusion rates\nM200 D0 ; disable volumetric E\nM220 S100 ; reset speed\n; Set initial warmup temps\nM117 Nozzle preheat\nM104 S100 ; preheat extruder to no ooze temp\nM140 S[bed_temperature_initial_layer_single] ; set bed temp\nM190 S[bed_temperature_initial_layer_single] ; wait for bed final temp\nM300 S40 P10 ; Bip\n; Home\nM117 Homing\nG28 ; home all with default mesh bed level\n; For ABL users put G29 for a leveling request\n; Final warmup routine\nM117 Final warmup\nM104 S[nozzle_temperature_initial_layer] ; set extruder final temp\nM109 S[nozzle_temperature_initial_layer] ; wait for extruder final temp\nM190 S[bed_temperature_initial_layer_single] ; wait for bed final temp\nM300 S440 P200; 1st beep for printer ready and allow some time to clean nozzle\nM300 S0 P250; wait between dual beep\nM300 S440 P200; 2nd beep for printer ready\nG4 S10; wait to clean the nozzle\nM300 S440 P200; 3rd beep for ready to start printing\n; Prime line routine\nM117 Printing prime line\n;M900 K0; Disable Linear Advance (Marlin) for prime line\nG92 E0.0; reset extrusion distance\nG1 X-54.672 Y-95.203 Z0.3 F4000; go outside print area\nG92 E0.0; reset extrusion distance\nG1 E2 F1000 ; de-retract and push ooze\nG3 X38.904 Y-102.668 I54.672 J95.105 E20.999\nG3 X54.671 Y-95.203 I-38.815 J102.373 E5.45800\nG92 E0.0\nG1 E-5 F3000 ; retract 5mm\nG1 X52.931 Y-96.185 F1000 ; wipe\nG1 X50.985 Y-97.231 F1000 ; wipe\nG1 X49.018 Y-98.238 F1000 ; wipe\nG1 X0 Y-109.798 F1000\nG1 E4.8 F1500; de-retract\nG92 E0.0 ; reset extrusion distance\n; Final print adjustments\nM117 Preparing to print\n;M82 ; extruder absolute mode\nM221 S{if layer_height<0.075}100{else}95{endif}\nM300 S40 P10 ; chirp\nM117 Print [output_filename_format]; Display: Printing started...",
"machine_start_gcode": ";STARTGCODE\nM117 Initializing\n; Set coordinate modes\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\n; Reset speed and extrusion rates\nM200 D0 ; disable volumetric E\nM220 S100 ; reset speed\n; Set initial warmup temps\nM117 Nozzle preheat\nM104 S100 ; preheat extruder to no ooze temp\nM140 S[bed_temperature_initial_layer_single] ; set bed temp\nM190 S[bed_temperature_initial_layer_single] ; wait for bed final temp\nM300 S40 P10 ; Bip\n; Home\nM117 Homing\nG28 ; home all with default mesh bed level\n; For ABL users put G29 for a leveling request\n; Final warmup routine\nM117 Final warmup\nM104 S[nozzle_temperature_initial_layer] ; set extruder final temp\nM109 S[nozzle_temperature_initial_layer] ; wait for extruder final temp\nM190 S[bed_temperature_initial_layer_single] ; wait for bed final temp\nM300 S440 P200; 1st beep for printer ready and allow some time to clean nozzle\nM300 S0 P250; wait between dual beep\nM300 S440 P200; 2nd beep for printer ready\nG4 S10; wait to clean the nozzle\nM300 S440 P200; 3rd beep for ready to start printing\n; Prime line routine\nM117 Printing prime line\n;M900 K0; Disable Linear Advance (Marlin) for prime line\nG92 E0.0; reset extrusion distance\nG1 X-54.672 Y-95.203 Z0.3 F4000; go outside print area\nG92 E0.0; reset extrusion distance\nG1 E2 F1000 ; de-retract and push ooze\nG3 X38.904 Y-102.668 I54.672 J95.105 E20.999\nG3 X54.671 Y-95.203 I-38.815 J102.373 E5.45800\nG92 E0.0\nG1 E-5 F3000 ; retract 5mm\nG1 X52.931 Y-96.185 F1000 ; wipe\nG1 X50.985 Y-97.231 F1000 ; wipe\nG1 X49.018 Y-98.238 F1000 ; wipe\nG1 X0 Y-109.798 F1000\nG1 E4.8 F1500; de-retract\nG92 E0.0 ; reset extrusion distance\n; Final print adjustments\nM117 Preparing to print\n;M82 ; extruder absolute mode\nM221 S{if layer_height<0.075}100{else}95{endif}\nM300 S40 P10 ; chirp\nM117 Print [input_filename_base]; Display: Printing started...",
"machine_end_gcode": "; printing object ENDGCODE\nG92 E0.0 ; prepare to retract\nG1 E-6 F3000; retract to avoid stringing\n; Anti-stringing end wiggle\n{if layer_z < max_print_height}G1 Z{min(layer_z+100, max_print_height)}{endif} F4000 ; Move print head up\nG1 X0 Y120 F3000 ; present print\n; Reset print setting overrides\nG92 E0\nM200 D0 ; disable volumetric e\nM220 S100 ; reset speed factor to 100%\nM221 S100 ; reset extruder factor to 100%\n;M900 K0 ; reset linear acceleration(Marlin)\n; Shut down printer\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nM18 S180 ;disable motors after 180s\nM300 S40 P10 ; Bip\nM117 Print finish.",
"scan_first_layer": "0"
}

View File

@@ -1,6 +1,6 @@
{
"name": "Ginger Additive",
"version": "02.04.00.02",
"version": "02.04.00.03",
"force_update": "1",
"description": "Ginger configuration",
"machine_model_list": [

View File

@@ -18,7 +18,6 @@
"enable_pressure_advance": [
"1"
],
"extruder_rotation_volume": "624",
"fan_cooling_layer_time": [
"100"
],
@@ -94,13 +93,6 @@
"hot_plate_temp_initial_layer": [
"75"
],
"mixing_stepper_rotation_volume": "8000",
"multi_zone_1_initial_layer": "240",
"multi_zone_1_temperature": "240",
"multi_zone_2_initial_layer": "240",
"multi_zone_2_temperature": "240",
"multi_zone_3_initial_layer": "220",
"multi_zone_3_temperature": "220",
"nozzle_temperature_range_high": [
"260"
],

View File

@@ -18,7 +18,6 @@
"enable_pressure_advance": [
"1"
],
"extruder_rotation_volume": "456",
"fan_cooling_layer_time": [
"100"
],
@@ -94,13 +93,6 @@
"hot_plate_temp_initial_layer": [
"50"
],
"mixing_stepper_rotation_volume": "8000",
"multi_zone_1_initial_layer": "200",
"multi_zone_1_temperature": "200",
"multi_zone_2_initial_layer": "200",
"multi_zone_2_temperature": "200",
"multi_zone_3_initial_layer": "200",
"multi_zone_3_temperature": "200",
"nozzle_temperature_range_high": [
"220"
],

View File

@@ -69,7 +69,7 @@
"12",
"12"
],
"machine_start_gcode": "START_PRINT BED_TEMPERATURE=[bed_temperature_initial_layer] KAMP_LEVELING=1 EXTRUDER_ROTATION_VOLUME={extruder_rotation_volume[0]} MIXING_STEPPER_ROTATION_VOLUME={mixing_stepper_rotation_volume[0]} PURGE_LAYER_HEIGHT=2 PURGE_PARKING_SPEED=10000 PURGE_LENGHT=500 PURGE_SPEED=500 PURGE_MATERIAL_QUANTITY=10000 EXTRUDER_TEMPERATURE=[nozzle_temperature] EXTRUDER_TEMPERATURE_INITIAL_LAYER=[nozzle_temperature_initial_layer] PRESSURE_ADVANCE=0.2 PRESSURE_ADVANCE_SMOOTH_TIME=0.5 ZONE_1_TEMPERATURE={multi_zone_1_initial_layer[0]} ZONE_2_TEMPERATURE={multi_zone_2_initial_layer[0]} ZONE_3_TEMPERATURE={multi_zone_3_initial_layer[0]}",
"machine_start_gcode": "START_PRINT BED_TEMPERATURE=[bed_temperature_initial_layer] KAMP_LEVELING=1 PURGE_LAYER_HEIGHT=2 PURGE_PARKING_SPEED=10000 PURGE_LENGHT=500 PURGE_SPEED=500 PURGE_MATERIAL_QUANTITY=10000 EXTRUDER_TEMPERATURE=[nozzle_temperature] EXTRUDER_TEMPERATURE_INITIAL_LAYER=[nozzle_temperature_initial_layer] PRESSURE_ADVANCE=0.2 PRESSURE_ADVANCE_SMOOTH_TIME=0.5",
"print_host": "G1OS.local",
"printer_model": "Ginger G1",
"retract_before_wipe": [
@@ -98,12 +98,5 @@
"z_hop_types": [
"Normal Lift"
],
"pellet_modded_printer": "1",
"use_extruder_rotation_volume": "1",
"use_active_pellet_feeding": "1",
"multi_zone": "1",
"multi_zone_number": "3",
"active_feeder_motor_name": [
"mixing_stepper"
]
"pellet_modded_printer": "1"
}

View File

@@ -1,6 +1,6 @@
{
"name": "Qidi",
"version": "02.04.00.06",
"version": "02.04.00.07",
"force_update": "0",
"description": "Qidi configurations",
"machine_model_list": [

View File

@@ -84,7 +84,6 @@
"locked_skin_infill_pattern": "crosszag",
"locked_skeleton_infill_pattern": "zigzag",
"max_travel_detour_distance": "0",
"machine_prepare_compensation_time": "260",
"minimum_sparse_infill_area": "15",
"only_one_wall_top": "1",
"outer_wall_acceleration": [

View File

@@ -206,6 +206,10 @@
"name": "0.30mm Big @RatRig V-Core 4 0.6",
"sub_path": "process/0.30mm Big @RatRig V-Core 4 0.6.json"
},
{
"name": "0.30mm Big @RatRig V-Core 4 0.8",
"sub_path": "process/0.30mm Big @RatRig V-Core 4 0.8.json"
},
{
"name": "0.30mm Big @RatRig V-Core 4 HYBRID 0.6",
"sub_path": "process/0.30mm Big @RatRig V-Core 4 HYBRID 0.6.json"

View File

@@ -2,6 +2,7 @@
"type": "machine",
"name": "RatRig V-Core 4 300 0.8 nozzle",
"inherits": "fdm_klipper_common",
"default_print_profile": "0.30mm Big @RatRig V-Core 4 0.8",
"from": "system",
"setting_id": "feTeGuFkLzWFFSC4",
"instantiation": "true",

View File

@@ -2,6 +2,7 @@
"type": "machine",
"name": "RatRig V-Core 4 400 0.8 nozzle",
"inherits": "fdm_klipper_common",
"default_print_profile": "0.30mm Big @RatRig V-Core 4 0.8",
"from": "system",
"setting_id": "DQ7Lzsuk87A4qtav",
"instantiation": "true",

View File

@@ -2,6 +2,7 @@
"type": "machine",
"name": "RatRig V-Core 4 500 0.8 nozzle",
"inherits": "fdm_klipper_common",
"default_print_profile": "0.30mm Big @RatRig V-Core 4 0.8",
"from": "system",
"setting_id": "VMKIyofeEpPvUvkY",
"instantiation": "true",

View File

@@ -0,0 +1,67 @@
{
"type": "process",
"name": "0.30mm Big @RatRig V-Core 4 0.8",
"inherits": "fdm_process_ratrig_common",
"from": "system",
"setting_id": "7FOG8fkUbrLknvuz",
"instantiation": "true",
"layer_height": "0.3",
"inital_layer_height": "0.35",
"wall_count": "3",
"top_shell_layers": "4",
"bottom_shell_layers": "3",
"top_shell_thickness": "0",
"sparse_infill_density": "25%",
"infill_anchor": "600%",
"infill_anchor_max": "5",
"infill_combination": "1",
"skirt_loops": "2",
"skirt_distance": "10",
"support_threshold_angle": "65",
"support_bottom_z_distance": "0.2",
"support_on_build_plate_only": "1",
"support_object_xy_distance": "60%",
"inner_wall_speed": "300",
"small_perimeter_speed": "250",
"outer_wall_speed": "250",
"sparse_infill_speed": "400",
"internal_solid_infill_speed": "100%",
"top_surface_speed": "100%",
"support_speed": "50",
"support_interface_speed": "100%",
"bridge_speed": "50",
"gap_infill_speed": "200",
"travel_speed": "600",
"initial_layer_speed": "80",
"enable_overhang_speed": "1",
"overhang_1_4_speed": "20",
"overhang_2_4_speed": "45",
"overhang_3_4_speed": "80",
"overhang_4_4_speed": "100",
"outer_wall_acceleration": "8000",
"inner_wall_acceleration": "10000",
"top_surface_acceleration": "0",
"internal_solid_infill_acceleration": "0",
"sparse_infill_acceleration": "15000",
"bridge_acceleration": "5000",
"initial_layer_acceleration": "2500",
"travel_acceleration": "15000",
"default_acceleration": "15000",
"line_width": "0.75",
"initial_layer_line_width": "1.1",
"inner_wall_line_width": "0.75",
"outer_wall_line_width": "0.70",
"sparse_infill_line_width": "0.75",
"internal_solid_infill_line_width": "0.75",
"top_surface_line_width": "0.75",
"support_line_width": "0.75",
"infill_wall_overlap": "18%",
"bridge_flow": "0.85",
"resolution": "0.0125",
"elefant_foot_compensation": "0.1",
"compatible_printers": [
"RatRig V-Core 4 300 0.8 nozzle",
"RatRig V-Core 4 400 0.8 nozzle",
"RatRig V-Core 4 500 0.8 nozzle"
]
}

View File

@@ -75,6 +75,30 @@
{
"name": "0.20mm Standard @iQ TiQ8 P1 - ABS Natur Material4Print (0.4 Nozzle)",
"sub_path": "process/0.20mm Standard @iQ TiQ8 P1 - ABS Natur Material4Print (0.4 Nozzle).json"
},
{
"name": "0.15mm Standard @iQ TiQ2 (0.25 Nozzle)",
"sub_path": "process/0.15mm Standard @iQ TiQ2 (0.25 Nozzle).json"
},
{
"name": "0.30mm Standard @iQ TiQ2 (0.6 Nozzle)",
"sub_path": "process/0.30mm Standard @iQ TiQ2 (0.6 Nozzle).json"
},
{
"name": "0.40mm Standard @iQ TiQ2 (0.8 Nozzle)",
"sub_path": "process/0.40mm Standard @iQ TiQ2 (0.8 Nozzle).json"
},
{
"name": "0.15mm Standard @iQ TiQ8 (0.25 Nozzle)",
"sub_path": "process/0.15mm Standard @iQ TiQ8 (0.25 Nozzle).json"
},
{
"name": "0.30mm Standard @iQ TiQ8 (0.6 Nozzle)",
"sub_path": "process/0.30mm Standard @iQ TiQ8 (0.6 Nozzle).json"
},
{
"name": "0.40mm Standard @iQ TiQ8 (0.8 Nozzle)",
"sub_path": "process/0.40mm Standard @iQ TiQ8 (0.8 Nozzle).json"
}
],
"filament_list": [

View File

@@ -1,134 +1,135 @@
{
"type": "machine",
"name": "iQ TiQ2 0.25 Nozzle",
"inherits": "fdm_tiq_common",
"from": "system",
"setting_id": "cIJ59rG6rulmYsqA",
"instantiation": "true",
"printer_settings_id": "iQ TiQ2 0.25 Nozzle",
"printer_model": "TiQ2",
"printer_variant": "0.25",
"printer_notes": "Machine file version 1.0 20251106",
"change_filament_gcode": "G1 Z{layer_z+2} F900 ; safe distance while tool change\nG1 X32 Y3 F3000\nM109 S{nozzle_temperature[next_extruder]} T[next_extruder] ; set new tool temperature so it can start heating while changing\n",
"deretraction_speed": [
"30",
"30"
],
"disable_m73": "1",
"emit_machine_limits_to_gcode": "0",
"enable_filament_ramming": "0",
"extruder_colour": [
"#FCE94F",
"#FCE94F"
],
"extruder_offset": [
"0x0",
"0x0"
],
"gcode_flavor": "marlin",
"host_type": "simplyprint",
"long_retractions_when_cut": [
"0",
"0"
],
"machine_end_gcode": "G1 X-19 F3000 ; home X axis\nG1 Y1 F3000 ; home Y axis\nM104 S0 T0 ; turn off extruder\nM104 S0 T1 ; turn off extruder\nM104 S0 T2 ; turn off extruder\nM140 S0 ; turn off bed\nM106 S0 ; turn off fan\nM806 S0 ; turn of housing fan\nM84 ; disable motor\n",
"machine_max_speed_z": [
"15",
"12"
],
"machine_pause_gcode": "M10710 S0",
"machine_start_gcode": "T[initial_extruder]\nM109 S{nozzle_temperature_initial_layer[current_extruder]}\nG1 Z15 F900\nG1 X-19 Y1 F9000\nG1 X-19 Y45 F9000\nG1 Y1 F9000\nG1 Y45 F9000\nG1 Y1 F9000\nG1 Y45 F9000\nG1 X0",
"max_layer_height": [
"0.2",
"0.2"
],
"min_layer_height": [
"0.08",
"0.08"
],
"nozzle_diameter": [
"0.25",
"0.25"
],
"print_host": "https://simplyprint.io/panel",
"printable_area": [
"0x0",
"330x0",
"330x330",
"0x330"
],
"printable_height": "300",
"retract_before_wipe": [
"70%",
"70%"
],
"retract_length_toolchange": [
"10",
"12"
],
"retract_lift_above": [
"0",
"0"
],
"retract_lift_below": [
"0",
"0"
],
"retract_lift_enforce": [
"All Surfaces",
"All Surfaces"
],
"retract_on_top_layer": [
"1",
"1"
],
"retract_restart_extra": [
"0",
"0"
],
"retract_restart_extra_toolchange": [
"-0.2",
"-0.2"
],
"retract_when_changing_layer": [
"1",
"1"
],
"retraction_distances_when_cut": [
"18",
"18"
],
"retraction_length": [
"0.8",
"0.9"
],
"retraction_minimum_travel": [
"1",
"1"
],
"retraction_speed": [
"30",
"30"
],
"thumbnails": "",
"travel_slope": [
"3",
"3"
],
"wipe": [
"1",
"1"
],
"wipe_distance": [
"1",
"1"
],
"z_hop": [
"0.25",
"0.25"
],
"z_hop_types": [
"Normal Lift",
"Normal Lift"
]
}
{
"type": "machine",
"name": "iQ TiQ2 0.25 Nozzle",
"inherits": "fdm_tiq_common",
"from": "system",
"setting_id": "cIJ59rG6rulmYsqA",
"instantiation": "true",
"printer_settings_id": "iQ TiQ2 0.25 Nozzle",
"printer_model": "TiQ2",
"printer_variant": "0.25",
"default_print_profile": "0.15mm Standard @iQ TiQ2 (0.25 Nozzle)",
"printer_notes": "Machine file version 1.0 20251106",
"change_filament_gcode": "G1 Z{layer_z+2} F900 ; safe distance while tool change\nG1 X32 Y3 F3000\nM109 S{nozzle_temperature[next_extruder]} T[next_extruder] ; set new tool temperature so it can start heating while changing\n",
"deretraction_speed": [
"30",
"30"
],
"disable_m73": "1",
"emit_machine_limits_to_gcode": "0",
"enable_filament_ramming": "0",
"extruder_colour": [
"#FCE94F",
"#FCE94F"
],
"extruder_offset": [
"0x0",
"0x0"
],
"gcode_flavor": "marlin",
"host_type": "simplyprint",
"long_retractions_when_cut": [
"0",
"0"
],
"machine_end_gcode": "G1 X-19 F3000 ; home X axis\nG1 Y1 F3000 ; home Y axis\nM104 S0 T0 ; turn off extruder\nM104 S0 T1 ; turn off extruder\nM104 S0 T2 ; turn off extruder\nM140 S0 ; turn off bed\nM106 S0 ; turn off fan\nM806 S0 ; turn of housing fan\nM84 ; disable motor\n",
"machine_max_speed_z": [
"15",
"12"
],
"machine_pause_gcode": "M10710 S0",
"machine_start_gcode": "T[initial_extruder]\nM109 S{nozzle_temperature_initial_layer[current_extruder]}\nG1 Z15 F900\nG1 X-19 Y1 F9000\nG1 X-19 Y45 F9000\nG1 Y1 F9000\nG1 Y45 F9000\nG1 Y1 F9000\nG1 Y45 F9000\nG1 X0",
"max_layer_height": [
"0.2",
"0.2"
],
"min_layer_height": [
"0.08",
"0.08"
],
"nozzle_diameter": [
"0.25",
"0.25"
],
"print_host": "https://simplyprint.io/panel",
"printable_area": [
"0x0",
"330x0",
"330x330",
"0x330"
],
"printable_height": "300",
"retract_before_wipe": [
"70%",
"70%"
],
"retract_length_toolchange": [
"10",
"12"
],
"retract_lift_above": [
"0",
"0"
],
"retract_lift_below": [
"0",
"0"
],
"retract_lift_enforce": [
"All Surfaces",
"All Surfaces"
],
"retract_on_top_layer": [
"1",
"1"
],
"retract_restart_extra": [
"0",
"0"
],
"retract_restart_extra_toolchange": [
"-0.2",
"-0.2"
],
"retract_when_changing_layer": [
"1",
"1"
],
"retraction_distances_when_cut": [
"18",
"18"
],
"retraction_length": [
"0.8",
"0.9"
],
"retraction_minimum_travel": [
"1",
"1"
],
"retraction_speed": [
"30",
"30"
],
"thumbnails": "",
"travel_slope": [
"3",
"3"
],
"wipe": [
"1",
"1"
],
"wipe_distance": [
"1",
"1"
],
"z_hop": [
"0.25",
"0.25"
],
"z_hop_types": [
"Normal Lift",
"Normal Lift"
]
}

View File

@@ -1,134 +1,135 @@
{
"type": "machine",
"name": "iQ TiQ2 0.6 Nozzle",
"inherits": "fdm_tiq_common",
"from": "system",
"setting_id": "U5GboRipEfd1fsBm",
"instantiation": "true",
"printer_settings_id": "iQ TiQ2 0.6 Nozzle",
"printer_model": "TiQ2",
"printer_variant": "0.6",
"printer_notes": "Machine file version 1.0 20251106",
"change_filament_gcode": "G1 Z{layer_z+2} F900 ; safe distance while tool change\nG1 X32 Y3 F3000\nM109 S{nozzle_temperature[next_extruder]} T[next_extruder] ; set new tool temperature so it can start heating while changing\n",
"deretraction_speed": [
"30",
"30"
],
"disable_m73": "1",
"emit_machine_limits_to_gcode": "0",
"enable_filament_ramming": "0",
"extruder_colour": [
"#FCE94F",
"#FCE94F"
],
"extruder_offset": [
"0x0",
"0x0"
],
"gcode_flavor": "marlin",
"host_type": "simplyprint",
"long_retractions_when_cut": [
"0",
"0"
],
"machine_end_gcode": "G1 X-19 F3000 ; home X axis\nG1 Y1 F3000 ; home Y axis\nM104 S0 T0 ; turn off extruder\nM104 S0 T1 ; turn off extruder\nM104 S0 T2 ; turn off extruder\nM140 S0 ; turn off bed\nM106 S0 ; turn off fan\nM806 S0 ; turn of housing fan\nM84 ; disable motor\n",
"machine_max_speed_z": [
"15",
"12"
],
"machine_pause_gcode": "M10710 S0",
"machine_start_gcode": "T[initial_extruder]\nM109 S{nozzle_temperature_initial_layer[current_extruder]}\nG1 Z15 F900\nG1 X-19 Y1 F9000\nG1 X-19 Y45 F9000\nG1 Y1 F9000\nG1 Y45 F9000\nG1 Y1 F9000\nG1 Y45 F9000\nG1 X0",
"max_layer_height": [
"0.6",
"0.6"
],
"min_layer_height": [
"0.08",
"0.08"
],
"nozzle_diameter": [
"0.6",
"0.6"
],
"print_host": "https://simplyprint.io/panel",
"printable_area": [
"0x0",
"330x0",
"330x330",
"0x330"
],
"printable_height": "300",
"retract_before_wipe": [
"70%",
"70%"
],
"retract_length_toolchange": [
"10",
"12"
],
"retract_lift_above": [
"0",
"0"
],
"retract_lift_below": [
"0",
"0"
],
"retract_lift_enforce": [
"All Surfaces",
"All Surfaces"
],
"retract_on_top_layer": [
"1",
"1"
],
"retract_restart_extra": [
"0",
"0"
],
"retract_restart_extra_toolchange": [
"-0.2",
"-0.2"
],
"retract_when_changing_layer": [
"1",
"1"
],
"retraction_distances_when_cut": [
"18",
"18"
],
"retraction_length": [
"0.8",
"0.9"
],
"retraction_minimum_travel": [
"1",
"1"
],
"retraction_speed": [
"30",
"30"
],
"thumbnails": "",
"travel_slope": [
"3",
"3"
],
"wipe": [
"1",
"1"
],
"wipe_distance": [
"1",
"1"
],
"z_hop": [
"0.6",
"0.6"
],
"z_hop_types": [
"Normal Lift",
"Normal Lift"
]
}
{
"type": "machine",
"name": "iQ TiQ2 0.6 Nozzle",
"inherits": "fdm_tiq_common",
"from": "system",
"setting_id": "U5GboRipEfd1fsBm",
"instantiation": "true",
"printer_settings_id": "iQ TiQ2 0.6 Nozzle",
"printer_model": "TiQ2",
"printer_variant": "0.6",
"default_print_profile": "0.30mm Standard @iQ TiQ2 (0.6 Nozzle)",
"printer_notes": "Machine file version 1.0 20251106",
"change_filament_gcode": "G1 Z{layer_z+2} F900 ; safe distance while tool change\nG1 X32 Y3 F3000\nM109 S{nozzle_temperature[next_extruder]} T[next_extruder] ; set new tool temperature so it can start heating while changing\n",
"deretraction_speed": [
"30",
"30"
],
"disable_m73": "1",
"emit_machine_limits_to_gcode": "0",
"enable_filament_ramming": "0",
"extruder_colour": [
"#FCE94F",
"#FCE94F"
],
"extruder_offset": [
"0x0",
"0x0"
],
"gcode_flavor": "marlin",
"host_type": "simplyprint",
"long_retractions_when_cut": [
"0",
"0"
],
"machine_end_gcode": "G1 X-19 F3000 ; home X axis\nG1 Y1 F3000 ; home Y axis\nM104 S0 T0 ; turn off extruder\nM104 S0 T1 ; turn off extruder\nM104 S0 T2 ; turn off extruder\nM140 S0 ; turn off bed\nM106 S0 ; turn off fan\nM806 S0 ; turn of housing fan\nM84 ; disable motor\n",
"machine_max_speed_z": [
"15",
"12"
],
"machine_pause_gcode": "M10710 S0",
"machine_start_gcode": "T[initial_extruder]\nM109 S{nozzle_temperature_initial_layer[current_extruder]}\nG1 Z15 F900\nG1 X-19 Y1 F9000\nG1 X-19 Y45 F9000\nG1 Y1 F9000\nG1 Y45 F9000\nG1 Y1 F9000\nG1 Y45 F9000\nG1 X0",
"max_layer_height": [
"0.6",
"0.6"
],
"min_layer_height": [
"0.08",
"0.08"
],
"nozzle_diameter": [
"0.6",
"0.6"
],
"print_host": "https://simplyprint.io/panel",
"printable_area": [
"0x0",
"330x0",
"330x330",
"0x330"
],
"printable_height": "300",
"retract_before_wipe": [
"70%",
"70%"
],
"retract_length_toolchange": [
"10",
"12"
],
"retract_lift_above": [
"0",
"0"
],
"retract_lift_below": [
"0",
"0"
],
"retract_lift_enforce": [
"All Surfaces",
"All Surfaces"
],
"retract_on_top_layer": [
"1",
"1"
],
"retract_restart_extra": [
"0",
"0"
],
"retract_restart_extra_toolchange": [
"-0.2",
"-0.2"
],
"retract_when_changing_layer": [
"1",
"1"
],
"retraction_distances_when_cut": [
"18",
"18"
],
"retraction_length": [
"0.8",
"0.9"
],
"retraction_minimum_travel": [
"1",
"1"
],
"retraction_speed": [
"30",
"30"
],
"thumbnails": "",
"travel_slope": [
"3",
"3"
],
"wipe": [
"1",
"1"
],
"wipe_distance": [
"1",
"1"
],
"z_hop": [
"0.6",
"0.6"
],
"z_hop_types": [
"Normal Lift",
"Normal Lift"
]
}

View File

@@ -1,134 +1,135 @@
{
"type": "machine",
"name": "iQ TiQ2 0.8 Nozzle",
"inherits": "fdm_tiq_common",
"from": "system",
"setting_id": "Hnv1OfrxjIFdQTpK",
"instantiation": "true",
"printer_settings_id": "iQ TiQ2 0.8 Nozzle",
"printer_model": "TiQ2",
"printer_variant": "0.8",
"printer_notes": "Machine file version 1.0 20251106",
"change_filament_gcode": "G1 Z{layer_z+2} F900 ; safe distance while tool change\nG1 X32 Y3 F3000\nM109 S{nozzle_temperature[next_extruder]} T[next_extruder] ; set new tool temperature so it can start heating while changing\n",
"deretraction_speed": [
"30",
"30"
],
"disable_m73": "1",
"emit_machine_limits_to_gcode": "0",
"enable_filament_ramming": "0",
"extruder_colour": [
"#FCE94F",
"#FCE94F"
],
"extruder_offset": [
"0x0",
"0x0"
],
"gcode_flavor": "marlin",
"host_type": "simplyprint",
"long_retractions_when_cut": [
"0",
"0"
],
"machine_end_gcode": "G1 X-19 F3000 ; home X axis\nG1 Y1 F3000 ; home Y axis\nM104 S0 T0 ; turn off extruder\nM104 S0 T1 ; turn off extruder\nM104 S0 T2 ; turn off extruder\nM140 S0 ; turn off bed\nM106 S0 ; turn off fan\nM806 S0 ; turn of housing fan\nM84 ; disable motor\n",
"machine_max_speed_z": [
"15",
"12"
],
"machine_pause_gcode": "M10710 S0",
"machine_start_gcode": "T[initial_extruder]\nM109 S{nozzle_temperature_initial_layer[current_extruder]}\nG1 Z15 F900\nG1 X-19 Y1 F9000\nG1 X-19 Y45 F9000\nG1 Y1 F9000\nG1 Y45 F9000\nG1 Y1 F9000\nG1 Y45 F9000\nG1 X0",
"max_layer_height": [
"0.8",
"0.8"
],
"min_layer_height": [
"0.08",
"0.08"
],
"nozzle_diameter": [
"0.8",
"0.8"
],
"print_host": "https://simplyprint.io/panel",
"printable_area": [
"0x0",
"330x0",
"330x330",
"0x330"
],
"printable_height": "300",
"retract_before_wipe": [
"70%",
"70%"
],
"retract_length_toolchange": [
"10",
"12"
],
"retract_lift_above": [
"0",
"0"
],
"retract_lift_below": [
"0",
"0"
],
"retract_lift_enforce": [
"All Surfaces",
"All Surfaces"
],
"retract_on_top_layer": [
"1",
"1"
],
"retract_restart_extra": [
"0",
"0"
],
"retract_restart_extra_toolchange": [
"-0.2",
"-0.2"
],
"retract_when_changing_layer": [
"1",
"1"
],
"retraction_distances_when_cut": [
"18",
"18"
],
"retraction_length": [
"0.8",
"0.9"
],
"retraction_minimum_travel": [
"1",
"1"
],
"retraction_speed": [
"30",
"30"
],
"thumbnails": "",
"travel_slope": [
"3",
"3"
],
"wipe": [
"1",
"1"
],
"wipe_distance": [
"1",
"1"
],
"z_hop": [
"0.8",
"0.8"
],
"z_hop_types": [
"Normal Lift",
"Normal Lift"
]
}
{
"type": "machine",
"name": "iQ TiQ2 0.8 Nozzle",
"inherits": "fdm_tiq_common",
"from": "system",
"setting_id": "Hnv1OfrxjIFdQTpK",
"instantiation": "true",
"printer_settings_id": "iQ TiQ2 0.8 Nozzle",
"printer_model": "TiQ2",
"printer_variant": "0.8",
"default_print_profile": "0.40mm Standard @iQ TiQ2 (0.8 Nozzle)",
"printer_notes": "Machine file version 1.0 20251106",
"change_filament_gcode": "G1 Z{layer_z+2} F900 ; safe distance while tool change\nG1 X32 Y3 F3000\nM109 S{nozzle_temperature[next_extruder]} T[next_extruder] ; set new tool temperature so it can start heating while changing\n",
"deretraction_speed": [
"30",
"30"
],
"disable_m73": "1",
"emit_machine_limits_to_gcode": "0",
"enable_filament_ramming": "0",
"extruder_colour": [
"#FCE94F",
"#FCE94F"
],
"extruder_offset": [
"0x0",
"0x0"
],
"gcode_flavor": "marlin",
"host_type": "simplyprint",
"long_retractions_when_cut": [
"0",
"0"
],
"machine_end_gcode": "G1 X-19 F3000 ; home X axis\nG1 Y1 F3000 ; home Y axis\nM104 S0 T0 ; turn off extruder\nM104 S0 T1 ; turn off extruder\nM104 S0 T2 ; turn off extruder\nM140 S0 ; turn off bed\nM106 S0 ; turn off fan\nM806 S0 ; turn of housing fan\nM84 ; disable motor\n",
"machine_max_speed_z": [
"15",
"12"
],
"machine_pause_gcode": "M10710 S0",
"machine_start_gcode": "T[initial_extruder]\nM109 S{nozzle_temperature_initial_layer[current_extruder]}\nG1 Z15 F900\nG1 X-19 Y1 F9000\nG1 X-19 Y45 F9000\nG1 Y1 F9000\nG1 Y45 F9000\nG1 Y1 F9000\nG1 Y45 F9000\nG1 X0",
"max_layer_height": [
"0.8",
"0.8"
],
"min_layer_height": [
"0.08",
"0.08"
],
"nozzle_diameter": [
"0.8",
"0.8"
],
"print_host": "https://simplyprint.io/panel",
"printable_area": [
"0x0",
"330x0",
"330x330",
"0x330"
],
"printable_height": "300",
"retract_before_wipe": [
"70%",
"70%"
],
"retract_length_toolchange": [
"10",
"12"
],
"retract_lift_above": [
"0",
"0"
],
"retract_lift_below": [
"0",
"0"
],
"retract_lift_enforce": [
"All Surfaces",
"All Surfaces"
],
"retract_on_top_layer": [
"1",
"1"
],
"retract_restart_extra": [
"0",
"0"
],
"retract_restart_extra_toolchange": [
"-0.2",
"-0.2"
],
"retract_when_changing_layer": [
"1",
"1"
],
"retraction_distances_when_cut": [
"18",
"18"
],
"retraction_length": [
"0.8",
"0.9"
],
"retraction_minimum_travel": [
"1",
"1"
],
"retraction_speed": [
"30",
"30"
],
"thumbnails": "",
"travel_slope": [
"3",
"3"
],
"wipe": [
"1",
"1"
],
"wipe_distance": [
"1",
"1"
],
"z_hop": [
"0.8",
"0.8"
],
"z_hop_types": [
"Normal Lift",
"Normal Lift"
]
}

View File

@@ -8,6 +8,7 @@
"printer_settings_id": "iQ TiQ8 0.25 Nozzle",
"printer_model": "TiQ8",
"printer_variant": "0.25",
"default_print_profile": "0.15mm Standard @iQ TiQ8 (0.25 Nozzle)",
"change_filament_gcode": "G1 Z{layer_z+2} F900 ; safe distance while tool change\n{if next_extruder==0}G1 X30 Y-12 F9000{endif}\nM109 S{nozzle_temperature[next_extruder]} T[next_extruder] ; set new tool temperature so it can start heating while changing",
"deretraction_speed": [
"30",

View File

@@ -8,6 +8,7 @@
"printer_settings_id": "iQ TiQ8 0.4 Nozzle",
"printer_model": "TiQ8",
"printer_variant": "0.4",
"default_print_profile": "0.20mm Standard @iQ TiQ8 P1 - ABS Natur Material4Print (0.4 Nozzle)",
"change_filament_gcode": "G1 Z{layer_z+2} F900 ; safe distance while tool change\n{if next_extruder==0}G1 X30 Y-12 F9000{endif}\nM109 S{nozzle_temperature[next_extruder]} T[next_extruder] ; set new tool temperature so it can start heating while changing",
"deretraction_speed": [
"30",

View File

@@ -8,6 +8,7 @@
"printer_settings_id": "iQ TiQ8 0.6 Nozzle",
"printer_model": "TiQ8",
"printer_variant": "0.6",
"default_print_profile": "0.30mm Standard @iQ TiQ8 (0.6 Nozzle)",
"change_filament_gcode": "G1 Z{layer_z+2} F900 ; safe distance while tool change\n{if next_extruder==0}G1 X30 Y-12 F9000{endif}\nM109 S{nozzle_temperature[next_extruder]} T[next_extruder] ; set new tool temperature so it can start heating while changing",
"deretraction_speed": [
"30",

View File

@@ -8,6 +8,7 @@
"printer_settings_id": "iQ TiQ8 0.8 Nozzle",
"printer_model": "TiQ8",
"printer_variant": "0.8",
"default_print_profile": "0.40mm Standard @iQ TiQ8 (0.8 Nozzle)",
"change_filament_gcode": "G1 Z{layer_z+2} F900 ; safe distance while tool change\n{if next_extruder==0}G1 X30 Y-12 F9000{endif}\nM109 S{nozzle_temperature[next_extruder]} T[next_extruder] ; set new tool temperature so it can start heating while changing",
"deretraction_speed": [
"30",

View File

@@ -0,0 +1,25 @@
{
"type": "process",
"name": "0.15mm Standard @iQ TiQ2 (0.25 Nozzle)",
"inherits": "fdm_process_tiq_common",
"from": "system",
"instantiation": "true",
"compatible_printers": [
"iQ TiQ2 0.25 Nozzle"
],
"layer_height": "0.15",
"line_width": "0.25",
"outer_wall_line_width": "0.25",
"inner_wall_line_width": "0.25",
"sparse_infill_line_width": "0.25",
"internal_solid_infill_line_width": "0.25",
"top_surface_line_width": "0.25",
"inner_wall_speed": "150",
"internal_solid_infill_speed": "150",
"sparse_infill_speed": "100",
"gap_infill_speed": "50",
"initial_layer_speed": "35",
"initial_layer_infill_speed": "55",
"bridge_speed": "30",
"print_settings_id": "0.15mm Standard @iQ TiQ2 (0.25 Nozzle)"
}

View File

@@ -0,0 +1,25 @@
{
"type": "process",
"name": "0.15mm Standard @iQ TiQ8 (0.25 Nozzle)",
"inherits": "fdm_process_tiq_common",
"from": "system",
"instantiation": "true",
"compatible_printers": [
"iQ TiQ8 0.25 Nozzle"
],
"layer_height": "0.15",
"line_width": "0.25",
"outer_wall_line_width": "0.25",
"inner_wall_line_width": "0.25",
"sparse_infill_line_width": "0.25",
"internal_solid_infill_line_width": "0.25",
"top_surface_line_width": "0.25",
"inner_wall_speed": "150",
"internal_solid_infill_speed": "150",
"sparse_infill_speed": "100",
"gap_infill_speed": "50",
"initial_layer_speed": "35",
"initial_layer_infill_speed": "55",
"bridge_speed": "30",
"print_settings_id": "0.15mm Standard @iQ TiQ8 (0.25 Nozzle)"
}

View File

@@ -0,0 +1,27 @@
{
"type": "process",
"name": "0.30mm Standard @iQ TiQ2 (0.6 Nozzle)",
"inherits": "fdm_process_tiq_common",
"from": "system",
"instantiation": "true",
"compatible_printers": [
"iQ TiQ2 0.6 Nozzle"
],
"layer_height": "0.3",
"initial_layer_print_height": "0.3",
"line_width": "0.6",
"outer_wall_line_width": "0.6",
"inner_wall_line_width": "0.6",
"sparse_infill_line_width": "0.6",
"internal_solid_infill_line_width": "0.6",
"top_surface_line_width": "0.6",
"wall_loops": "2",
"inner_wall_speed": "150",
"internal_solid_infill_speed": "150",
"sparse_infill_speed": "100",
"gap_infill_speed": "50",
"initial_layer_speed": "35",
"initial_layer_infill_speed": "55",
"bridge_speed": "30",
"print_settings_id": "0.30mm Standard @iQ TiQ2 (0.6 Nozzle)"
}

View File

@@ -0,0 +1,27 @@
{
"type": "process",
"name": "0.30mm Standard @iQ TiQ8 (0.6 Nozzle)",
"inherits": "fdm_process_tiq_common",
"from": "system",
"instantiation": "true",
"compatible_printers": [
"iQ TiQ8 0.6 Nozzle"
],
"layer_height": "0.3",
"initial_layer_print_height": "0.3",
"line_width": "0.6",
"outer_wall_line_width": "0.6",
"inner_wall_line_width": "0.6",
"sparse_infill_line_width": "0.6",
"internal_solid_infill_line_width": "0.6",
"top_surface_line_width": "0.6",
"wall_loops": "2",
"inner_wall_speed": "150",
"internal_solid_infill_speed": "150",
"sparse_infill_speed": "100",
"gap_infill_speed": "50",
"initial_layer_speed": "35",
"initial_layer_infill_speed": "55",
"bridge_speed": "30",
"print_settings_id": "0.30mm Standard @iQ TiQ8 (0.6 Nozzle)"
}

View File

@@ -0,0 +1,27 @@
{
"type": "process",
"name": "0.40mm Standard @iQ TiQ2 (0.8 Nozzle)",
"inherits": "fdm_process_tiq_common",
"from": "system",
"instantiation": "true",
"compatible_printers": [
"iQ TiQ2 0.8 Nozzle"
],
"layer_height": "0.4",
"initial_layer_print_height": "0.4",
"line_width": "0.8",
"outer_wall_line_width": "0.8",
"inner_wall_line_width": "0.8",
"sparse_infill_line_width": "0.8",
"internal_solid_infill_line_width": "0.8",
"top_surface_line_width": "0.8",
"wall_loops": "2",
"inner_wall_speed": "150",
"internal_solid_infill_speed": "150",
"sparse_infill_speed": "100",
"gap_infill_speed": "50",
"initial_layer_speed": "35",
"initial_layer_infill_speed": "55",
"bridge_speed": "30",
"print_settings_id": "0.40mm Standard @iQ TiQ2 (0.8 Nozzle)"
}

View File

@@ -0,0 +1,27 @@
{
"type": "process",
"name": "0.40mm Standard @iQ TiQ8 (0.8 Nozzle)",
"inherits": "fdm_process_tiq_common",
"from": "system",
"instantiation": "true",
"compatible_printers": [
"iQ TiQ8 0.8 Nozzle"
],
"layer_height": "0.4",
"initial_layer_print_height": "0.4",
"line_width": "0.8",
"outer_wall_line_width": "0.8",
"inner_wall_line_width": "0.8",
"sparse_infill_line_width": "0.8",
"internal_solid_infill_line_width": "0.8",
"top_surface_line_width": "0.8",
"wall_loops": "2",
"inner_wall_speed": "150",
"internal_solid_infill_speed": "150",
"sparse_infill_speed": "100",
"gap_infill_speed": "50",
"initial_layer_speed": "35",
"initial_layer_infill_speed": "55",
"bridge_speed": "30",
"print_settings_id": "0.40mm Standard @iQ TiQ8 (0.8 Nozzle)"
}

View File

@@ -1,12 +1,32 @@
// This single-TU executable links libslic3r, whose SVG/emboss objects (pulled in by the slice mode
// below) reference the header-only nanosvg implementation. Provide it here BEFORE any libslic3r header:
// several of them transitively include nanosvg.h without the implementation macro, and its include
// guard would then suppress the implementation if the macro were defined afterwards. Same pattern as
// the test mains.
#define NANOSVG_IMPLEMENTATION
#include "nanosvg/nanosvg.h"
#define NANOSVGRAST_IMPLEMENTATION
#include "nanosvg/nanosvgrast.h"
#include "libslic3r/GCode.hpp"
#include "libslic3r/Preset.hpp"
#include "libslic3r/Config.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/Print.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/TriangleMesh.hpp"
#include "libslic3r/Utils.hpp"
#include <boost/filesystem/operations.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/core.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/sinks/sync_frontend.hpp>
#include <boost/log/sinks/text_ostream_backend.hpp>
#include <boost/core/null_deleter.hpp>
#include <boost/make_shared.hpp>
#include <boost/program_options.hpp>
#include <algorithm>
#include <fstream>
#include <iostream>
#include <string>
@@ -83,6 +103,254 @@ void generate_custom_presets(PresetBundle* preset_bundle, AppConfig& app_config)
std::cout << "Custom presets generated successfully" << std::endl;
}
namespace {
Vec2d printable_area_center(const DynamicPrintConfig &cfg)
{
const auto *opt = cfg.option<ConfigOptionPoints>("printable_area");
if (opt == nullptr || opt->values.empty())
return Vec2d(100., 100.);
Vec2d lo = opt->values.front(), hi = opt->values.front();
for (const Vec2d &p : opt->values) { lo = lo.cwiseMin(p); hi = hi.cwiseMax(p); }
return 0.5 * (lo + hi);
}
// Slice one centered cube that switches from filament 1 to filament 2 partway up, so exactly one
// filament change fires, then export. The change drives the printer's own change_filament_gcode: on a
// single-nozzle machine it rides the AMS prime tower (append_tcr), on a multi-nozzle machine it routes
// through the nozzle swap (set_extruder / append_tcr2) - the engine picks the path from the printer's
// topology, so one model covers both. An undefined placeholder in any shipped custom g-code throws
// Slic3r::PlaceholderParserError from export.
std::string slice_two_color_cube_and_export(const DynamicPrintConfig &cfg, bool is_bbl)
{
const Vec2d center = printable_area_center(cfg);
TriangleMesh m = make_cube(10, 10, 10);
m.translate(float(center.x() - 5.), float(center.y() - 5.), 0.f);
Model model;
Print print;
ModelObject *obj = model.add_object();
obj->name = "cube"; // populates [input_filename_base] the way a loaded model does
obj->add_volume(m);
obj->add_instance();
// Filament 2 is used only above z=4, so the upper layers carry a single filament change.
DynamicPrintConfig range_config;
range_config.set_key_value("extruder", new ConfigOptionInt(2));
// Every range must carry a layer_height; use the process's own so a fine nozzle (e.g. 0.15 mm
// printing ~0.1 mm layers) isn't forced to a height its extrusion width can't support - that
// trips Flow::with_spacing.
range_config.set_key_value("layer_height", new ConfigOptionFloat(cfg.opt_float("layer_height")));
obj->layer_config_ranges[{4.0, 10.0}].assign_config(std::move(range_config));
print.is_BBL_printer() = is_bbl;
obj->ensure_on_bed();
print.auto_assign_extruders(obj);
print.apply(model, cfg);
print.validate();
// Process + export to a temp file, then read it back (the app's own export path is where the
// custom *_gcode placeholders expand).
print.set_status_silent();
print.process();
const fs::path tmp = fs::temp_directory_path() / fs::unique_path("orca-validate-%%%%-%%%%.gcode");
print.export_gcode(tmp.string(), nullptr, nullptr);
std::ifstream in(tmp.string());
std::string out((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
in.close();
boost::system::error_code ec;
fs::remove(tmp, ec);
return out;
}
// Select the printer's OWN default process + filament (as the app does on a printer change) so we
// slice with settings the printer actually ships, not the generic "Default Setting" that stays
// selected because it is compatible with every printer.
void select_printer_default_presets(PresetBundle &bundle)
{
const Preset &printer_preset = bundle.printers.get_selected_preset();
const std::string def_print = printer_preset.config.opt_string("default_print_profile");
if (!def_print.empty())
bundle.prints.select_preset_by_name(def_print, /*force=*/true);
if (const auto *def_fil = printer_preset.config.option<ConfigOptionStrings>("default_filament_profile");
def_fil != nullptr && !def_fil->values.empty())
bundle.filaments.select_preset_by_name(def_fil->values.front(), /*force=*/true);
}
// The vendor/printer currently being sliced, stamped onto every engine log record by the sink below so
// the interleaved [error] lines can be attributed to a profile. Updated once per loop iteration; safe as
// a plain global because the sweep is single-threaded and synchronous (see slice_all_printers).
static std::string g_slice_context;
// Route Boost.Log through a sink that prefixes every record with g_slice_context. Without this the
// engine's [error] lines (emitted deep inside process()/export_gcode()) carry no printer context, so a
// failing profile cannot be told apart from the ~1000 others in the sweep. Drops the default trivial
// sink's timestamp/thread columns - noise here - in favour of the vendor/printer tag.
void install_slice_context_log_sink()
{
namespace logging = boost::log;
namespace sinks = boost::log::sinks;
namespace expr = boost::log::expressions;
auto backend = boost::make_shared<sinks::text_ostream_backend>();
backend->add_stream(boost::shared_ptr<std::ostream>(&std::clog, boost::null_deleter()));
backend->auto_flush(true);
auto sink = boost::make_shared<sinks::synchronous_sink<sinks::text_ostream_backend>>(backend);
sink->set_formatter([](const logging::record_view &rec, logging::formatting_ostream &strm) {
strm << "[" << rec[logging::trivial::severity] << "]";
if (!g_slice_context.empty())
strm << " [" << g_slice_context << "]";
strm << " " << rec[expr::smessage];
});
logging::core::get()->remove_all_sinks(); // drop the default trivial sink so lines are not doubled
logging::core::get()->add_sink(sink);
}
// Slice-and-export a two-colour cube through every shipped printer (optionally scoped to one vendor via
// -v). Unlike the static reference/placeholder checks, this expands every custom *_gcode - including
// change_filament_gcode at the one filament change - against the printer's fully-resolved config, so
// undefined-placeholder / invalid-flow bugs surface here. Reports every offending printer and returns 1
// if any failed, 0 otherwise. The sweep is SEQUENTIAL by necessity: Print::process() keeps
// process-global state, so slicing printers concurrently in one process races even with per-slice
// Model+Print. Load in validation mode so the vendors are read straight from the -p profiles dir
// (no data_dir/system tree) and -v scoping is honoured for free.
int slice_all_printers(const std::string &vendor)
{
install_slice_context_log_sink();
PresetBundle bundle;
bundle.set_is_validation_mode(true);
bundle.set_vendor_to_validate(vendor); // empty == all vendors
AppConfig app_config;
app_config.set("preset_folder", "default");
try {
bundle.load_presets(app_config, ForwardCompatibilitySubstitutionRule::Disable);
} catch (const std::exception &ex) {
BOOST_LOG_TRIVIAL(error) << ex.what();
std::cout << "Validation failed" << std::endl;
return 1;
}
// Enable every instantiable model/variant in AppConfig - system printers are hidden until enabled;
// without this select_preset_by_name silently falls back to the "Default Printer".
std::vector<std::pair<std::string, std::string>> printers; // (vendor name, preset name)
for (const Preset &p : bundle.printers.get_presets()) {
if (p.vendor == nullptr) continue; // skips the Default Printer
const std::string model = p.config.opt_string("printer_model");
const std::string variant = p.config.opt_string("printer_variant");
if (model.empty() || variant.empty()) continue; // skip non-instantiable base/common configs
app_config.set_variant(p.vendor->id, model, variant, true);
printers.push_back({p.vendor->name, p.name});
}
bundle.load_installed_printers(app_config);
if (printers.empty()) {
BOOST_LOG_TRIVIAL(error) << "No instantiable printer presets found"
<< (vendor.empty() ? "" : " for vendor " + vendor);
std::cout << "Validation failed" << std::endl;
return 1;
}
std::cout << "Slicing " << printers.size() << " printer preset(s)"
<< (vendor.empty() ? "" : " for vendor " + vendor) << "..." << std::endl;
int failures = 0;
for (const auto &[vendor_name, printer] : printers) {
g_slice_context = vendor_name + " / " + printer; // tag every engine log line from this slice
const bool selected = bundle.printers.select_preset_by_name(printer, /*force=*/true);
if (!selected || bundle.printers.get_selected_preset_name() != printer) {
BOOST_LOG_TRIVIAL(error) << "Printer preset \"" << printer << "\" could not be selected";
++failures;
continue;
}
select_printer_default_presets(bundle); // slice with the printer's shipped process/filament
bundle.update_multi_material_filament_presets(); // size filament_presets to nozzle count
bundle.update_compatible(PresetSelectCompatibleType::Always);
// Never slice with a generic default preset - that would validate stand-in settings, not the
// real profile (a legit per-profile error).
if (bundle.prints.get_selected_preset().is_default || bundle.filaments.get_selected_preset().is_default) {
BOOST_LOG_TRIVIAL(error) << "Printer \"" << printer << "\" fell back to a default preset (process=\""
<< bundle.prints.get_selected_preset_name() << "\", filament=\""
<< bundle.filaments.get_selected_preset_name() << "\")";
++failures;
continue;
}
// Grow to a 2nd filament so the cube can change colour; never shrink a multi-nozzle printer
// below its nozzle count, or full_config()'s flush-volume matrix no longer matches validate().
const size_t nozzles = bundle.printers.get_selected_preset().config.option<ConfigOptionFloats>("nozzle_diameter")->size();
bundle.set_num_filaments((unsigned int) std::max<size_t>(2, nozzles));
// Mirror the app's manual filament->nozzle assignment for a multi-nozzle BBL printer: put each
// filament on its own nozzle and pin the map (fmmManual) so full_config() collapses every filament to
// the variant of the nozzle it actually prints from, and the engine keeps that assignment instead of
// auto-remapping it during process(). Without this the synthetic 2nd filament keeps nozzle 1's variant
// while the auto map moves it to nozzle 2 - harmless, but on the one printer whose nozzles differ in
// type (Direct Drive + Bowden) the mismatched lookup spams [error] lines. Single-nozzle and non-BBL
// printers keep the default map (their toolchange rides the AMS/tool-changer path unchanged).
const bool pin_filament_map = bundle.is_bbl_vendor() && nozzles > 1;
if (pin_filament_map) {
auto &fmap = bundle.project_config.option<ConfigOptionInts>("filament_map", true)->values;
for (size_t i = 0; i < fmap.size(); ++i)
fmap[i] = int(i % nozzles) + 1;
}
DynamicPrintConfig cfg = bundle.full_config();
cfg.set_key_value("enable_prime_tower", new ConfigOptionBool(true)); // force a purge tower so the change is detectable
// The map above drives full_config()'s per-filament variant collapse; fmmManual on the sliced config
// stops process() from auto-remapping filaments back onto a different nozzle (which would re-introduce
// the variant mismatch this pinning avoids).
if (pin_filament_map)
cfg.set_key_value("filament_map_mode", new ConfigOptionEnum<FilamentMapMode>(fmmManual));
// full_config() grows filament_extruder_variant to one entry per filament, but because the synthetic
// 2nd filament is a duplicate of the first (set_num_filaments copies the same preset), it leaves
// filament_self_index at size 1. That makes update_values_to_printer_extruders_for_multiple_filaments
// fail to resolve the 2nd filament's variant - a benign fallback that spams [error] lines. A real
// 2-colour project ships filament_self_index = 1,2,...; mirror that so the sweep log stays clean. The
// slice output is unaffected: the duplicated filament's per-variant values are identical to the first.
if (auto *variants = cfg.option<ConfigOptionStrings>("filament_extruder_variant")) {
auto &self_index = cfg.option<ConfigOptionInts>("filament_self_index", true)->values;
if (self_index.size() != variants->size()) {
self_index.resize(variants->size());
for (size_t i = 0; i < self_index.size(); ++i)
self_index[i] = int(i) + 1;
}
}
try {
const std::string out = slice_two_color_cube_and_export(cfg, bundle.is_bbl_vendor());
if (out.empty() || out.find("G1") == std::string::npos) {
BOOST_LOG_TRIVIAL(error) << "Printer \"" << printer << "\" produced no g-code";
++failures;
} else if (out.find("CP TOOLCHANGE START") == std::string::npos) {
// The filament change never rode the tower, so change_filament_gcode was not exercised.
BOOST_LOG_TRIVIAL(error) << "Printer \"" << printer
<< "\" sliced but the filament change never fired (no CP TOOLCHANGE START)";
++failures;
}
} catch (const std::exception &ex) {
BOOST_LOG_TRIVIAL(error) << "Printer \"" << printer << "\" failed to slice: " << ex.what();
++failures;
}
}
g_slice_context.clear();
if (failures > 0) {
std::cout << failures << " of " << printers.size() << " printer preset(s) failed to slice" << std::endl;
std::cout << "Validation failed" << std::endl;
return 1;
}
std::cout << "All " << printers.size() << " printer preset(s) sliced successfully" << std::endl;
std::cout << "Validation completed successfully" << std::endl;
return 0;
}
} // namespace
int main(int argc, char* argv[])
{
po::options_description desc("Orca Profile Validator\nUsage");
@@ -95,6 +363,7 @@ int main(int argc, char* argv[])
#endif
("vendor,v", po::value<std::string>()->default_value(""), "Vendor name. Optional, all profiles present in the folder will be validated if not specified")
("generate_presets,g", po::value<bool>()->default_value(false), "Generate user presets for mock test")
("slice,s", po::bool_switch()->default_value(false), "Slice a two-colour cube through every printer to expand all custom g-code (catches placeholder/flow errors that static checks miss). Off unless this flag is present.")
("check_filament_subtypes,f", po::bool_switch()->default_value(false), "Also flag printers with duplicate (ambiguous) filament subtypes. Off unless this flag is present.")
("log_level,l", po::value<int>()->default_value(2), "Log level. Optional, default is 2 (warning). Higher values produce more detailed logs.");
// clang-format on
@@ -119,6 +388,7 @@ int main(int argc, char* argv[])
std::string vendor = vm["vendor"].as<std::string>();
int log_level = vm["log_level"].as<int>();
bool generate_user_preset = vm["generate_presets"].as<bool>();
bool slice_mode = vm["slice"].as<bool>();
bool check_filament_subtypes = vm["check_filament_subtypes"].as<bool>();
// check if path is valid, and return error if not
@@ -132,6 +402,12 @@ int main(int argc, char* argv[])
// std::cout<<"log_level: "<<log_level<<std::endl;
set_data_dir(path);
// Orca: the profiles folder lives at <resources>/profiles, so point resources_dir() at that
// <resources> parent. Without this, resources_dir() is empty and slice mode's HRC lookup
// (info/nozzle_info.json) resolves to a non-existent relative path and falls back to a
// built-in table (logging a spurious parse error and dropping the E3D entry).
if (fs::exists(fs::path(path).parent_path() / "info"))
set_resources_dir(fs::path(path).parent_path().string());
auto user_dir = fs::path(Slic3r::data_dir()) / PRESET_USER_DIR;
user_dir.make_preferred();
@@ -139,6 +415,12 @@ int main(int argc, char* argv[])
fs::create_directory(user_dir);
set_logging_level(log_level);
// Slice mode expands every printer's custom g-code by actually slicing (see slice_all_printers).
// A distinct opt-in mode so the default static checks stay fast for every profile PR.
if (slice_mode)
return slice_all_printers(vendor);
auto preset_bundle = new PresetBundle();
// preset_bundle->setup_directories();
preset_bundle->set_is_validation_mode(true);

View File

@@ -6551,8 +6551,11 @@ void GCodeProcessor::process_T(const std::string_view command, int nozzle_id)
if (command.length() > 1) {
if (eid < 0 || eid > 254) {
//BBS: T255, T1000 and T1100 is used as special command for BBL machine and does not cost time. return directly
// Orca: T1001 (hotend-type detection) and T65535/T65279 (AMS unload virtual-tool selects, paired with
// M620/M621 S65535/S65279) are firmware opcodes emitted verbatim by BBL machine start/end g-code, not
// real tool changes - whitelist them so the time estimator stops flagging these valid lines.
if ((m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware) && (command == "Tx" || command == "Tc" || command == "T?" ||
eid == 1000 || eid == 1100 || eid == 255))
eid == 1000 || eid == 1100 || eid == 255 || eid == 1001 || eid == 65279 || eid == 65535))
return;
// T-1 is a valid gcode line for RepRap Firmwares (used to deselects all tools)

View File

@@ -3677,6 +3677,8 @@ int Print::get_filament_config_indx(int filament_id, int layer_id)
void Print::update_filament_self_index_cache()
{
m_missing_nozzle_group_logged.clear(); // reset the per-slice get_config_index log dedupe
std::vector<int> values;
if (m_full_print_config.has("filament_self_index")) {
values = m_full_print_config.option<ConfigOptionInts>("filament_self_index")->values;
@@ -3722,9 +3724,12 @@ int Print::get_config_index(int filament_id, int layer_id, const std::vector<std
return filament_id;
auto nozzle_info = group_result->get_nozzle_for_filament(filament_id, layer_id);
if (!nozzle_info.has_value()) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__
<< boost::format(", Line %1%: could not found group_nozzle_info corresponding to filament_id %2%, layer_id %3%") % __LINE__ % filament_id %
layer_id;
// Orca: this fallback runs per-filament/per-layer in the g-code hot path — log once per filament
// (reset each slice) instead of flooding thousands of identical lines that bury the real error.
if (m_missing_nozzle_group_logged.insert(filament_id).second)
BOOST_LOG_TRIVIAL(error) << __FUNCTION__
<< boost::format(", Line %1%: could not found group_nozzle_info corresponding to filament_id %2%, layer_id %3% (further occurrences for this filament suppressed)") % __LINE__ % filament_id %
layer_id;
return 0;
}
@@ -3751,9 +3756,12 @@ int Print::get_config_index(int filament_id, int layer_id, const std::vector<std
return (int)get_extruder_id(filament_id);
auto nozzle_info = group_result->get_nozzle_for_filament(filament_id, layer_id);
if (!nozzle_info.has_value()) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__
<< boost::format(", Line %1%: could not found group_nozzle_info corresponding to filament_id %2%, layer_id %3%") % __LINE__ % filament_id %
layer_id;
// Orca: this fallback runs per-filament/per-layer in the g-code hot path — log once per filament
// (reset each slice) instead of flooding thousands of identical lines that bury the real error.
if (m_missing_nozzle_group_logged.insert(filament_id).second)
BOOST_LOG_TRIVIAL(error) << __FUNCTION__
<< boost::format(", Line %1%: could not found group_nozzle_info corresponding to filament_id %2%, layer_id %3% (further occurrences for this filament suppressed)") % __LINE__ % filament_id %
layer_id;
return 0;
}

View File

@@ -1303,6 +1303,10 @@ private:
FilamentIndexMap m_filament_index_map;
// Used to cache printer and process parameter information
PrintIndexMap m_nozzle_index_map;
// Orca: filament ids already reported as missing a nozzle-group entry this slice. get_config_index()
// falls back per-filament/per-layer in the g-code hot path, so this dedupes its log to once per
// filament instead of flooding thousands of identical error lines. Cleared with the caches each slice.
std::set<int> m_missing_nozzle_group_logged;
// save the config value of "filament_self_index"
std::vector<int> m_filament_self_index;

View File

@@ -22,7 +22,7 @@ struct Params
: /*max_acceleration(max_acceleration), */raft_layers_count(raft_layers_count), brim_type(brim_type), brim_width(brim_width)
{
if (filament_types.size() > 1) {
BOOST_LOG_TRIVIAL(warning)
BOOST_LOG_TRIVIAL(debug)
<< "SupportSpotsGenerator does not currently handle different materials properly, only first will be used";
}
if (filament_types.empty() || filament_types[0].empty()) {

View File

@@ -13,13 +13,17 @@ endif()
set(TEST_DATA_DIR ${CMAKE_CURRENT_SOURCE_DIR}/data)
file(TO_NATIVE_PATH "${TEST_DATA_DIR}" TEST_DATA_DIR)
# Shipped vendor profiles, so tests can exercise the real machine/filament gcode.
set(PROFILES_DIR ${CMAKE_SOURCE_DIR}/resources/profiles)
file(TO_NATIVE_PATH "${PROFILES_DIR}" PROFILES_DIR)
include(CTest)
include(Catch)
set(CATCH_EXTRA_ARGS "" CACHE STRING "Extra arguments for catch2 test suites.") # Unknown if this still works and/or should be replaced with something else.
add_library(test_common INTERFACE)
target_compile_definitions(test_common INTERFACE TEST_DATA_DIR=R"\(${TEST_DATA_DIR}\)" CATCH_CONFIG_FAST_COMPILE)
target_compile_definitions(test_common INTERFACE TEST_DATA_DIR=R"\(${TEST_DATA_DIR}\)" PROFILES_DIR=R"\(${PROFILES_DIR}\)" CATCH_CONFIG_FAST_COMPILE)
target_include_directories(test_common INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
if (APPLE)

View File

@@ -12,6 +12,8 @@
#include "libslic3r/Print.hpp"
#include "libslic3r/ModelArrange.hpp"
#include <boost/filesystem.hpp>
#include "test_helpers.hpp"
using namespace Slic3r;
@@ -680,3 +682,141 @@ SCENARIO("Prime-tower visits without a filament change do not advance the toolch
}
}
}
// ---------------------------------------------------------------------------
// Real-profile toolchange coverage, targeted. The all-vendors sweep in test_profile_slicing.cpp now
// slices a two-colour cube per printer, so it already expands every shipped change_filament_gcode with
// each printer's DEFAULT extruder variants (both the single-nozzle append_tcr and dual-nozzle set_extruder
// paths). What that sweep can't reach is a variant-conditional branch the defaults never select — H2D's
// change gcode has an `== "Direct Drive TPU High Flow"` block. This scenario forces that branch by handing
// the extruders distinct kits, so an unregistered placeholder inside it still throws "Variable does not
// exist" here instead of only in the field.
// ---------------------------------------------------------------------------
// Two 20mm cubes on separate extruders of a BBL machine, printed by object so exactly
// one real toolchange fires and drives the change_filament_gcode. Returns the g-code.
static std::string slice_two_object_bbl(DynamicPrintConfig &config)
{
config.set_key_value("print_sequence", new ConfigOptionEnum<PrintSequence>(PrintSequence::ByObject));
Model model;
auto *obj1 = model.add_object();
obj1->add_volume(cube(20));
obj1->add_instance();
auto *obj2 = model.add_object();
obj2->add_volume(cube(20));
obj2->add_instance();
obj2->config.set_key_value("extruder", new ConfigOptionInt(2));
Print print;
print.is_BBL_printer() = true;
arrange_objects_on_test_bed(model, config);
for (auto *mo : model.objects) {
mo->ensure_on_bed();
print.auto_assign_extruders(mo);
}
print.apply(model, config);
print.validate();
print.set_status_silent();
print.process();
return Slic3r::Test::gcode(print);
}
// The real change_filament_gcode of a shipped "<printer> 0.4 nozzle" machine profile.
static std::string shipped_change_filament_gcode(const std::string &printer)
{
const std::string path = std::string(PROFILES_DIR) + "/BBL/machine/Bambu Lab " + printer + " 0.4 nozzle.json";
// PROFILES_DIR is an absolute path baked in at build time; a sparse test checkout
// without resources/ leaves it missing. Skip rather than dereference a config that
// never loaded - this is the only fff_print test that reads a shipped profile.
if (!boost::filesystem::exists(path))
SKIP("shipped profile not present in this checkout: " << path);
DynamicPrintConfig config;
std::map<std::string, std::string> key_values;
std::string reason;
config.load_from_json(path, ForwardCompatibilitySubstitutionRule::Enable, key_values, reason);
// Fail loudly on a malformed/renamed profile instead of null-dereferencing in opt_string.
INFO("profile: " << path << (reason.empty() ? "" : (" load reason: " + reason)));
REQUIRE(config.has("change_filament_gcode"));
return config.opt_string("change_filament_gcode");
}
SCENARIO("Toolchange gcode resolves old/new_extruder_variant from printer_extruder_variant", "[GCodeWriter][H2C]")
{
GIVEN("a BBL dual-extruder print whose change gcode reads the extruder-variant placeholders") {
DynamicPrintConfig config = dual_extruder_toolchange_config();
// A distinctive variant that can only reach the g-code through printer_extruder_variant.
// Both entries carry it so the assertion is independent of which physical extruder the
// emitted change routes through.
config.set_key_value("printer_extruder_variant",
new ConfigOptionStrings({"Direct Drive TPU High Flow", "Direct Drive TPU High Flow"}));
config.set_key_value("change_filament_gcode", new ConfigOptionString(
"; VARIANT old={old_extruder_variant} new={new_extruder_variant}\nT[next_filament_id]\n"));
WHEN("the print is sliced") {
const std::string gcode = slice_two_object_bbl(config);
THEN("both placeholders resolve to the printer_extruder_variant value") {
// The resolved line is the proof: an unresolved token or a parser throw would
// prevent this exact line from being emitted. (A negative "{token}" check is
// unreliable — the g-code's trailing config dump echoes the raw template.)
REQUIRE_THAT(gcode, Catch::Matchers::ContainsSubstring(
"; VARIANT old=Direct Drive TPU High Flow new=Direct Drive TPU High Flow"));
}
}
}
}
SCENARIO("Global current-tool placeholders resolve in a context with no local injection", "[GCodeWriter][H2C]")
{
GIVEN("a BBL dual-extruder print whose before_layer_change_gcode reads the current-tool placeholders") {
DynamicPrintConfig config = dual_extruder_toolchange_config();
// before_layer_change is one of the contexts that inject NO current_* into their local config
// (unlike change_filament / machine_end / layer_change), so these placeholders can only resolve
// through the GLOBAL parser vars published at each toolchange (and at the initial set_extruder).
// Pre-fix current_filament_id / current_extruder_id / current_nozzle_id were undefined here and the
// whole slice threw a PlaceholderParserError — the same failure mode X2D's layer_change hit.
config.set_key_value("before_layer_change_gcode", new ConfigOptionString(
"; GVAR fid={current_filament_id} eid={current_extruder_id} nid={current_nozzle_id}\n"));
WHEN("the print is sliced (obj1 on filament 0, obj2 on filament 1)") {
std::string gcode;
REQUIRE_NOTHROW(gcode = slice_two_object_bbl(config));
THEN("all three globals resolve to the CORRECT active-tool values on both sides of the change") {
// Assert the FULL resolved marker, not just no-throw: obj1 prints on filament 0 (extruder 0,
// nozzle 0) and obj2 on filament 1 (extruder 1, nozzle 1). Locking every field means a
// stale or wrong global (e.g. obj2 still reading fid=0, or a mismatched extruder/nozzle id)
// fails here — this is the value guard that replaces the old "throws on undefined" canary.
// Only the emitted before_layer_change lines carry resolved values; the trailing config dump
// keeps the raw "{current_filament_id}" template, so these are unambiguous.
REQUIRE_THAT(gcode, Catch::Matchers::ContainsSubstring("; GVAR fid=0 eid=0 nid=0"));
REQUIRE_THAT(gcode, Catch::Matchers::ContainsSubstring("; GVAR fid=1 eid=1 nid=1"));
}
}
}
}
SCENARIO("Shipped dual-nozzle change_filament_gcode resolves during a real slice", "[GCodeWriter][H2C][Profiles]")
{
const std::string printer = GENERATE(std::string("H2C"), std::string("H2D"), std::string("H2D Pro"), std::string("X2D"));
GIVEN("the real " + printer + " change_filament_gcode driving a BBL dual-extruder slice") {
DynamicPrintConfig config = dual_extruder_toolchange_config();
config.set_key_value("change_filament_gcode", new ConfigOptionString(shipped_change_filament_gcode(printer)));
// H2D's gcode branches on the extruder variant; give the extruders distinct kits so the
// "Direct Drive TPU High Flow" branch is reachable.
config.set_key_value("printer_extruder_variant",
new ConfigOptionStrings({"Direct Drive Standard", "Direct Drive TPU High Flow"}));
// Extruder-indexed machine rates the stock gcode divides by (default size 1); size to 2 extruders.
config.set_key_value("hotend_cooling_rate", new ConfigOptionFloatsNullable({2.0, 2.0}));
config.set_key_value("hotend_heating_rate", new ConfigOptionFloatsNullable({2.0, 2.0}));
THEN("every placeholder resolves (no undefined-variable throw) and the change block runs") {
std::string gcode;
REQUIRE_NOTHROW(gcode = slice_two_object_bbl(config));
// A resolved marker only the emitted change block produces (the trailing config
// dump keeps the raw "{filament_type[...]}" template), so this confirms the real
// change_filament_gcode was expanded, not merely echoed.
REQUIRE_THAT(gcode, Catch::Matchers::ContainsSubstring("set_filament_type:PLA"));
}
}
}