Merge branch 'main' into dev/ams-heat

This commit is contained in:
SoftFever
2026-07-15 19:45:27 +08:00
committed by GitHub
95 changed files with 18718 additions and 6505 deletions
+24
View File
@@ -165,6 +165,30 @@ jobs:
os: ${{ vars.SELF_HOSTED && 'orca-macos-arm64' || 'macos-14' }} os: ${{ vars.SELF_HOSTED && 'orca-macos-arm64' || 'macos-14' }}
artifact: ${{ github.sha }}-tests-macos-arm64 artifact: ${{ github.sha }}-tests-macos-arm64
test-dir: build/arm64/tests 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 aarch64)
needs: build_linux
if: ${{ !cancelled() && success() }}
runs-on: ubuntu-24.04-arm
steps:
- name: Checkout repository
uses: actions/checkout@v7
- name: Download profile validator
uses: actions/download-artifact@v8
with:
name: ${{ github.sha }}-profile-validator-linux-aarch64
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: publish_test_results:
name: 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] needs: [unit_tests_linux_x86_64, unit_tests_linux_aarch64, unit_tests_windows_x64, unit_tests_windows_arm64, unit_tests_macos_arm64]
+16
View File
@@ -508,6 +508,22 @@ jobs:
retention-days: 5 retention-days: 5
if-no-files-found: error 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. Taken from the aarch64 leg so the sweep runs on arm64 (its
# GitHub-hosted runner is free, and it also exercises the arm build).
# 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-aarch64
overwrite: true
path: ./build/src/Release/OrcaSlicer_profile_validator
retention-days: 5
if-no-files-found: error
- name: Run external slicer regression tests - name: Run external slicer regression tests
if: runner.os == 'Linux' && inputs.arch != 'aarch64' if: runner.os == 'Linux' && inputs.arch != 'aarch64'
timeout-minutes: 20 timeout-minutes: 20
+20 -2
View File
@@ -49,6 +49,15 @@ jobs:
set +e set +e
./OrcaSlicer_profile_validator -p ${{ github.workspace }}/resources/profiles -l 2 2>&1 | tee ${{ runner.temp }}/validate_system.log ./OrcaSlicer_profile_validator -p ${{ github.workspace }}/resources/profiles -l 2 2>&1 | tee ${{ runner.temp }}/validate_system.log
exit ${PIPESTATUS[0]} 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. # For now run filament subtype check only for BBL profiles until we fix other vendors' profiles.
- name: validate filament subtype check for BBL profiles - name: validate filament subtype check for BBL profiles
id: validate_filament_subtypes id: validate_filament_subtypes
@@ -166,7 +175,7 @@ jobs:
echo "${{ github.event.pull_request.number }}" > ${{ runner.temp }}/profile-check-results/pr_number.txt echo "${{ github.event.pull_request.number }}" > ${{ runner.temp }}/profile-check-results/pr_number.txt
- name: Prepare comment artifact - 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: | run: |
{ {
# Marker matched by check_profiles_comment.yml to delete prior comments. # Marker matched by check_profiles_comment.yml to delete prior comments.
@@ -192,6 +201,15 @@ jobs:
echo "" echo ""
fi 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 if [ "${{ steps.validate_filament_subtypes.outcome }}" = "failure" ]; then
echo "### BBL Filament Subtype Validation Failed" echo "### BBL Filament Subtype Validation Failed"
echo "" echo ""
@@ -223,7 +241,7 @@ jobs:
retention-days: 1 retention-days: 1
- name: Fail if any check failed - 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: | run: |
echo "One or more profile checks failed. See above for details." echo "One or more profile checks failed. See above for details."
exit 1 exit 1
+86
View File
@@ -79,6 +79,92 @@ jobs:
throw error; throw error;
} }
localization-pr:
if: github.event_name == 'pull_request_target'
permissions:
contents: read
pull-requests: write
issues: write
runs-on: ubuntu-latest
steps:
- name: Auto-label and remind about the localization glossary
uses: actions/github-script@v9
with:
script: |
function isPermissionDenied(error) {
return error && error.status === 403 && /Resource not accessible by integration/i.test(error.message || '');
}
const pr = context.payload.pull_request;
// List changed files once (mirrors the `localization/**` paths filter in check_locale.yml)
const files = await github.paginate(github.rest.pulls.listFiles, {
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
per_page: 100
});
const touchesLocalization = files.some((file) => file.filename.startsWith('localization/'));
const onlyPoFiles = files.length > 0 && files.every((file) => file.filename.endsWith('.po'));
// If the PR changes only .po files, automatically apply the Localization label
if (onlyPoFiles) {
try {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
labels: ['Localization']
});
core.info('Applied Localization label (PR changes only .po files).');
} catch (error) {
if (isPermissionDenied(error)) {
core.warning('Cannot add Localization label because token cannot write.');
} else {
throw error;
}
}
}
if (!touchesLocalization) {
core.info('No localization changes detected; skipping glossary reminder.');
return;
}
// Avoid posting the reminder twice (e.g. on reopen)
const marker = '<!-- localization-glossary-reminder -->';
const comments = await github.paginate(github.rest.issues.listComments, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
per_page: 100
});
if (comments.some((comment) => (comment.body || '').includes(marker))) {
core.info('Glossary reminder already present; skipping.');
return;
}
try {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body:
`${marker}\n` +
`Hi @${pr.user.login}, this PR changes translations (\`localization/**\`).\n\n` +
`Please make sure recurring terms follow the [Localization glossary](https://www.orcaslicer.com/wiki/localization_glossary), ` +
`so the same English term is always rendered the same way within a language and terms that must stay in English ` +
`(brand/product names, acronyms, file formats, G-code, macros/variables) are not translated.`
});
} catch (error) {
if (isPermissionDenied(error)) {
core.warning('Skipping glossary reminder because token cannot write comments.');
return;
}
throw error;
}
apply-label: apply-label:
if: github.event_name == 'issue_comment' if: github.event_name == 'issue_comment'
permissions: permissions:
+5 -1
View File
@@ -28,11 +28,15 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v7 uses: actions/checkout@v7
with: 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: | sparse-checkout: |
.github .github
scripts scripts
tests tests
resources
- name: Apt-Install Dependencies - name: Apt-Install Dependencies
if: runner.os == 'Linux' && !vars.SELF_HOSTED if: runner.os == 'Linux' && !vars.SELF_HOSTED
uses: ./.github/actions/apt-install-deps uses: ./.github/actions/apt-install-deps
+8
View File
@@ -55,3 +55,11 @@ ctest --test-dir ./tests/fff_print
- Add helper functions or utilities only when existing code cannot reasonably be reused. Avoid duplication. - Add helper functions or utilities only when existing code cannot reasonably be reused. Avoid duplication.
- Keep code concise and clear. Manually simplify AI generated bloated codes before review. - Keep code concise and clear. Manually simplify AI generated bloated codes before review.
- Include targeted tests or documented verification for behavior changes, especially in slicing logic, profiles, formats, and GUI defaults. - Include targeted tests or documented verification for behavior changes, especially in slicing logic, profiles, formats, and GUI defaults.
- For translation changes (`localization/i18n/**/*.po`), check that recurring terms match the [Localization glossary](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/main/guides/localization_glossary.md) for that language.
## Localization & translations
- Translation catalogs live in `localization/i18n/<lang>/OrcaSlicer_<lang>.po`.
- When creating or reviewing translations, use the [Localization glossary](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/main/guides/localization_glossary.md) as the source of truth for recurring terms, so the same English term is always rendered the same way within a language and terms that must stay in English (brand/product names, acronyms, file formats, G-code, macros/variables) are not translated.
- If a term's established translation changes, update both the affected `.po` files and the glossary so they stay in sync.
- Only edit `msgstr` (never `msgid`); keep placeholders (`%s`, `%1%`, `\n`), context (`msgctxt`), and file encoding/line endings intact.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+4
View File
@@ -1230,6 +1230,10 @@
"name": "0.40mm Standard @Creality K1C", "name": "0.40mm Standard @Creality K1C",
"sub_path": "process/0.40mm Standard @Creality K1C 0.8 nozzle.json" "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)", "name": "0.40mm Standard @Creality K1Max (0.8 nozzle)",
"sub_path": "process/0.40mm Standard @Creality K1Max (0.8 nozzle).json" "sub_path": "process/0.40mm Standard @Creality K1Max (0.8 nozzle).json"
@@ -11,7 +11,7 @@
"Creality Generic PLA" "Creality Generic PLA"
], ],
"printer_variant": "0.2", "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": [ "nozzle_diameter": [
"0.2" "0.2"
], ],
@@ -11,7 +11,7 @@
"Creality Generic PLA" "Creality Generic PLA"
], ],
"printer_variant": "0.2", "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": [ "nozzle_diameter": [
"0.2" "0.2"
], ],
@@ -69,7 +69,7 @@
"support_on_build_plate_only": "0", "support_on_build_plate_only": "0",
"support_top_z_distance": "0.15", "support_top_z_distance": "0.15",
"support_filament": "0", "support_filament": "0",
"support_line_width": "0.18", "support_line_width": "0.22",
"support_interface_loop_pattern": "0", "support_interface_loop_pattern": "0",
"support_interface_filament": "0", "support_interface_filament": "0",
"support_interface_top_layers": "3", "support_interface_top_layers": "3",
@@ -85,7 +85,7 @@
"tree_support_wall_count": "0", "tree_support_wall_count": "0",
"detect_thin_wall": "1", "detect_thin_wall": "1",
"top_surface_pattern": "monotonicline", "top_surface_pattern": "monotonicline",
"top_surface_line_width": "0.2", "top_surface_line_width": "0.25",
"top_shell_layers": "5", "top_shell_layers": "5",
"top_shell_thickness": "0.8", "top_shell_thickness": "0.8",
"initial_layer_speed": "35%", "initial_layer_speed": "35%",
@@ -69,7 +69,7 @@
"support_on_build_plate_only": "0", "support_on_build_plate_only": "0",
"support_top_z_distance": "0.15", "support_top_z_distance": "0.15",
"support_filament": "0", "support_filament": "0",
"support_line_width": "0.18", "support_line_width": "0.22",
"support_interface_loop_pattern": "0", "support_interface_loop_pattern": "0",
"support_interface_filament": "0", "support_interface_filament": "0",
"support_interface_top_layers": "3", "support_interface_top_layers": "3",
@@ -85,7 +85,7 @@
"tree_support_wall_count": "0", "tree_support_wall_count": "0",
"detect_thin_wall": "1", "detect_thin_wall": "1",
"top_surface_pattern": "monotonicline", "top_surface_pattern": "monotonicline",
"top_surface_line_width": "0.2", "top_surface_line_width": "0.25",
"top_shell_layers": "5", "top_shell_layers": "5",
"top_shell_thickness": "0.8", "top_shell_thickness": "0.8",
"initial_layer_speed": "35%", "initial_layer_speed": "35%",
@@ -59,7 +59,7 @@
"skirt_height": "2", "skirt_height": "2",
"skirt_loops": "2", "skirt_loops": "2",
"minimum_sparse_infill_area": "10", "minimum_sparse_infill_area": "10",
"internal_solid_infill_line_width": "0.2", "internal_solid_infill_line_width": "0.25",
"spiral_mode": "0", "spiral_mode": "0",
"standby_temperature_delta": "-5", "standby_temperature_delta": "-5",
"enable_support": "0", "enable_support": "0",
@@ -69,7 +69,7 @@
"support_on_build_plate_only": "0", "support_on_build_plate_only": "0",
"support_top_z_distance": "0.15", "support_top_z_distance": "0.15",
"support_filament": "0", "support_filament": "0",
"support_line_width": "0.18", "support_line_width": "0.22",
"support_interface_loop_pattern": "0", "support_interface_loop_pattern": "0",
"support_interface_filament": "0", "support_interface_filament": "0",
"support_interface_top_layers": "3", "support_interface_top_layers": "3",
@@ -85,7 +85,7 @@
"tree_support_wall_count": "0", "tree_support_wall_count": "0",
"detect_thin_wall": "1", "detect_thin_wall": "1",
"top_surface_pattern": "monotonicline", "top_surface_pattern": "monotonicline",
"top_surface_line_width": "0.2", "top_surface_line_width": "0.25",
"top_shell_layers": "7", "top_shell_layers": "7",
"top_shell_thickness": "0.8", "top_shell_thickness": "0.8",
"initial_layer_speed": "35%", "initial_layer_speed": "35%",
@@ -71,7 +71,7 @@
"support_on_build_plate_only": "0", "support_on_build_plate_only": "0",
"support_top_z_distance": "0.15", "support_top_z_distance": "0.15",
"support_filament": "0", "support_filament": "0",
"support_line_width": "0.18", "support_line_width": "0.22",
"support_interface_loop_pattern": "0", "support_interface_loop_pattern": "0",
"support_interface_filament": "0", "support_interface_filament": "0",
"support_interface_top_layers": "2", "support_interface_top_layers": "2",
@@ -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"
}
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "FLSun", "name": "FLSun",
"version": "02.04.00.01", "version": "02.04.00.02",
"force_update": "0", "force_update": "0",
"description": "FLSun configurations", "description": "FLSun configurations",
"machine_model_list": [ "machine_model_list": [
@@ -182,7 +182,7 @@
"default_filament_profile": [ "default_filament_profile": [
"FLSun Generic PLA" "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.", "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" "scan_first_layer": "0"
} }
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Ginger Additive", "name": "Ginger Additive",
"version": "02.04.00.02", "version": "02.04.00.03",
"force_update": "1", "force_update": "1",
"description": "Ginger configuration", "description": "Ginger configuration",
"machine_model_list": [ "machine_model_list": [
@@ -18,7 +18,6 @@
"enable_pressure_advance": [ "enable_pressure_advance": [
"1" "1"
], ],
"extruder_rotation_volume": "624",
"fan_cooling_layer_time": [ "fan_cooling_layer_time": [
"100" "100"
], ],
@@ -94,13 +93,6 @@
"hot_plate_temp_initial_layer": [ "hot_plate_temp_initial_layer": [
"75" "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": [ "nozzle_temperature_range_high": [
"260" "260"
], ],
@@ -18,7 +18,6 @@
"enable_pressure_advance": [ "enable_pressure_advance": [
"1" "1"
], ],
"extruder_rotation_volume": "456",
"fan_cooling_layer_time": [ "fan_cooling_layer_time": [
"100" "100"
], ],
@@ -94,13 +93,6 @@
"hot_plate_temp_initial_layer": [ "hot_plate_temp_initial_layer": [
"50" "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": [ "nozzle_temperature_range_high": [
"220" "220"
], ],
@@ -69,7 +69,7 @@
"12", "12",
"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", "print_host": "G1OS.local",
"printer_model": "Ginger G1", "printer_model": "Ginger G1",
"retract_before_wipe": [ "retract_before_wipe": [
@@ -98,12 +98,5 @@
"z_hop_types": [ "z_hop_types": [
"Normal Lift" "Normal Lift"
], ],
"pellet_modded_printer": "1", "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"
]
} }
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Qidi", "name": "Qidi",
"version": "02.04.00.06", "version": "02.04.00.07",
"force_update": "0", "force_update": "0",
"description": "Qidi configurations", "description": "Qidi configurations",
"machine_model_list": [ "machine_model_list": [
@@ -84,7 +84,6 @@
"locked_skin_infill_pattern": "crosszag", "locked_skin_infill_pattern": "crosszag",
"locked_skeleton_infill_pattern": "zigzag", "locked_skeleton_infill_pattern": "zigzag",
"max_travel_detour_distance": "0", "max_travel_detour_distance": "0",
"machine_prepare_compensation_time": "260",
"minimum_sparse_infill_area": "15", "minimum_sparse_infill_area": "15",
"only_one_wall_top": "1", "only_one_wall_top": "1",
"outer_wall_acceleration": [ "outer_wall_acceleration": [
+4
View File
@@ -206,6 +206,10 @@
"name": "0.30mm Big @RatRig V-Core 4 0.6", "name": "0.30mm Big @RatRig V-Core 4 0.6",
"sub_path": "process/0.30mm Big @RatRig V-Core 4 0.6.json" "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", "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" "sub_path": "process/0.30mm Big @RatRig V-Core 4 HYBRID 0.6.json"
@@ -2,6 +2,7 @@
"type": "machine", "type": "machine",
"name": "RatRig V-Core 4 300 0.8 nozzle", "name": "RatRig V-Core 4 300 0.8 nozzle",
"inherits": "fdm_klipper_common", "inherits": "fdm_klipper_common",
"default_print_profile": "0.30mm Big @RatRig V-Core 4 0.8",
"from": "system", "from": "system",
"setting_id": "feTeGuFkLzWFFSC4", "setting_id": "feTeGuFkLzWFFSC4",
"instantiation": "true", "instantiation": "true",
@@ -2,6 +2,7 @@
"type": "machine", "type": "machine",
"name": "RatRig V-Core 4 400 0.8 nozzle", "name": "RatRig V-Core 4 400 0.8 nozzle",
"inherits": "fdm_klipper_common", "inherits": "fdm_klipper_common",
"default_print_profile": "0.30mm Big @RatRig V-Core 4 0.8",
"from": "system", "from": "system",
"setting_id": "DQ7Lzsuk87A4qtav", "setting_id": "DQ7Lzsuk87A4qtav",
"instantiation": "true", "instantiation": "true",
@@ -2,6 +2,7 @@
"type": "machine", "type": "machine",
"name": "RatRig V-Core 4 500 0.8 nozzle", "name": "RatRig V-Core 4 500 0.8 nozzle",
"inherits": "fdm_klipper_common", "inherits": "fdm_klipper_common",
"default_print_profile": "0.30mm Big @RatRig V-Core 4 0.8",
"from": "system", "from": "system",
"setting_id": "VMKIyofeEpPvUvkY", "setting_id": "VMKIyofeEpPvUvkY",
"instantiation": "true", "instantiation": "true",
@@ -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"
]
}
+24
View File
@@ -75,6 +75,30 @@
{ {
"name": "0.20mm Standard @iQ TiQ8 P1 - ABS Natur Material4Print (0.4 Nozzle)", "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" "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": [ "filament_list": [
@@ -8,6 +8,7 @@
"printer_settings_id": "iQ TiQ2 0.25 Nozzle", "printer_settings_id": "iQ TiQ2 0.25 Nozzle",
"printer_model": "TiQ2", "printer_model": "TiQ2",
"printer_variant": "0.25", "printer_variant": "0.25",
"default_print_profile": "0.15mm Standard @iQ TiQ2 (0.25 Nozzle)",
"printer_notes": "Machine file version 1.0 20251106", "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", "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": [ "deretraction_speed": [
@@ -8,6 +8,7 @@
"printer_settings_id": "iQ TiQ2 0.6 Nozzle", "printer_settings_id": "iQ TiQ2 0.6 Nozzle",
"printer_model": "TiQ2", "printer_model": "TiQ2",
"printer_variant": "0.6", "printer_variant": "0.6",
"default_print_profile": "0.30mm Standard @iQ TiQ2 (0.6 Nozzle)",
"printer_notes": "Machine file version 1.0 20251106", "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", "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": [ "deretraction_speed": [
@@ -8,6 +8,7 @@
"printer_settings_id": "iQ TiQ2 0.8 Nozzle", "printer_settings_id": "iQ TiQ2 0.8 Nozzle",
"printer_model": "TiQ2", "printer_model": "TiQ2",
"printer_variant": "0.8", "printer_variant": "0.8",
"default_print_profile": "0.40mm Standard @iQ TiQ2 (0.8 Nozzle)",
"printer_notes": "Machine file version 1.0 20251106", "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", "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": [ "deretraction_speed": [
@@ -8,6 +8,7 @@
"printer_settings_id": "iQ TiQ8 0.25 Nozzle", "printer_settings_id": "iQ TiQ8 0.25 Nozzle",
"printer_model": "TiQ8", "printer_model": "TiQ8",
"printer_variant": "0.25", "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", "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": [ "deretraction_speed": [
"30", "30",
@@ -8,6 +8,7 @@
"printer_settings_id": "iQ TiQ8 0.4 Nozzle", "printer_settings_id": "iQ TiQ8 0.4 Nozzle",
"printer_model": "TiQ8", "printer_model": "TiQ8",
"printer_variant": "0.4", "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", "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": [ "deretraction_speed": [
"30", "30",
@@ -8,6 +8,7 @@
"printer_settings_id": "iQ TiQ8 0.6 Nozzle", "printer_settings_id": "iQ TiQ8 0.6 Nozzle",
"printer_model": "TiQ8", "printer_model": "TiQ8",
"printer_variant": "0.6", "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", "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": [ "deretraction_speed": [
"30", "30",
@@ -8,6 +8,7 @@
"printer_settings_id": "iQ TiQ8 0.8 Nozzle", "printer_settings_id": "iQ TiQ8 0.8 Nozzle",
"printer_model": "TiQ8", "printer_model": "TiQ8",
"printer_variant": "0.8", "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", "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": [ "deretraction_speed": [
"30", "30",
@@ -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)"
}
@@ -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)"
}
@@ -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)"
}
@@ -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)"
}
@@ -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)"
}
@@ -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)"
}
@@ -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/GCode.hpp"
#include "libslic3r/Preset.hpp" #include "libslic3r/Preset.hpp"
#include "libslic3r/Config.hpp" #include "libslic3r/Config.hpp"
#include "libslic3r/PresetBundle.hpp" #include "libslic3r/PresetBundle.hpp"
#include "libslic3r/Print.hpp" #include "libslic3r/Print.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/TriangleMesh.hpp"
#include "libslic3r/Utils.hpp" #include "libslic3r/Utils.hpp"
#include <boost/filesystem/operations.hpp> #include <boost/filesystem/operations.hpp>
#include <boost/log/trivial.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 <boost/program_options.hpp>
#include <algorithm>
#include <fstream>
#include <iostream> #include <iostream>
#include <string> #include <string>
@@ -83,6 +103,270 @@ void generate_custom_presets(PresetBundle* preset_bundle, AppConfig& app_config)
std::cout << "Custom presets generated successfully" << std::endl; 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. When outdir is non-empty, each printer's g-code is also written there as
// "<vendor>__<printer>.gcode" for manual inspection. 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, const std::string &outdir)
{
install_slice_context_log_sink();
if (!outdir.empty()) {
boost::system::error_code ec;
fs::create_directories(outdir, ec);
if (ec) {
BOOST_LOG_TRIVIAL(error) << "Could not create output directory \"" << outdir << "\": " << ec.message();
std::cout << "Validation failed" << std::endl;
return 1;
}
std::cout << "Saving sliced g-code to " << outdir << std::endl;
}
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 (!outdir.empty() && !out.empty()) {
const fs::path f = fs::path(outdir) / (sanitize_filename(vendor_name) + "__" + sanitize_filename(printer) + ".gcode");
save_string_file(f, out);
}
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[]) int main(int argc, char* argv[])
{ {
po::options_description desc("Orca Profile Validator\nUsage"); po::options_description desc("Orca Profile Validator\nUsage");
@@ -95,6 +379,8 @@ int main(int argc, char* argv[])
#endif #endif
("vendor,v", po::value<std::string>()->default_value(""), "Vendor name. Optional, all profiles present in the folder will be validated if not specified") ("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") ("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.")
("outdir,o", po::value<std::string>()->default_value(""), "With -s, also save each printer's g-code to this folder (as <vendor>__<printer>.gcode) for manual inspection. Optional.")
("check_filament_subtypes,f", po::bool_switch()->default_value(false), "Also flag printers with duplicate (ambiguous) filament subtypes. 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."); ("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 // clang-format on
@@ -119,6 +405,8 @@ int main(int argc, char* argv[])
std::string vendor = vm["vendor"].as<std::string>(); std::string vendor = vm["vendor"].as<std::string>();
int log_level = vm["log_level"].as<int>(); int log_level = vm["log_level"].as<int>();
bool generate_user_preset = vm["generate_presets"].as<bool>(); bool generate_user_preset = vm["generate_presets"].as<bool>();
bool slice_mode = vm["slice"].as<bool>();
std::string slice_outdir = vm["outdir"].as<std::string>();
bool check_filament_subtypes = vm["check_filament_subtypes"].as<bool>(); bool check_filament_subtypes = vm["check_filament_subtypes"].as<bool>();
// check if path is valid, and return error if not // check if path is valid, and return error if not
@@ -132,6 +420,12 @@ int main(int argc, char* argv[])
// std::cout<<"log_level: "<<log_level<<std::endl; // std::cout<<"log_level: "<<log_level<<std::endl;
set_data_dir(path); 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; auto user_dir = fs::path(Slic3r::data_dir()) / PRESET_USER_DIR;
user_dir.make_preferred(); user_dir.make_preferred();
@@ -139,6 +433,12 @@ int main(int argc, char* argv[])
fs::create_directory(user_dir); fs::create_directory(user_dir);
set_logging_level(log_level); 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, slice_outdir);
auto preset_bundle = new PresetBundle(); auto preset_bundle = new PresetBundle();
// preset_bundle->setup_directories(); // preset_bundle->setup_directories();
preset_bundle->set_is_validation_mode(true); preset_bundle->set_is_validation_mode(true);
+75
View File
@@ -1,3 +1,7 @@
#include <limits>
#include <numeric>
#include <unordered_map>
#include "ClipperUtils.hpp" #include "ClipperUtils.hpp"
#include "Geometry.hpp" #include "Geometry.hpp"
#include "ShortestPath.hpp" #include "ShortestPath.hpp"
@@ -930,6 +934,77 @@ Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r
Slic3r::Polylines intersection_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip) Slic3r::Polylines intersection_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip)
{ return _clipper_pl_closed(ClipperLib::ctIntersection, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip)); } { return _clipper_pl_closed(ClipperLib::ctIntersection, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip)); }
// Orca: Sort and orient open polyline fragments produced by clipping `source` with
// intersection_pl(), so that they run in the same order and direction as the source
// polyline. Clipping creates new endpoints at the clip boundary, but it keeps the
// interior source vertices intact, so a fragment's position on the source path is
// recovered exactly by looking its vertices up in the source. Fragments without any
// surviving source vertex lie on a single source segment, found by a nearest-segment
// search.
void restore_source_path_order(const Slic3r::Polyline &source, Slic3r::Polylines &fragments)
{
const Points &src = source.points;
if (src.size() < 2 || fragments.empty())
return;
std::unordered_map<Point, size_t, PointHash> source_index;
source_index.reserve(src.size());
for (size_t i = 0; i < src.size(); ++ i)
source_index.emplace(src[i], i);
// Sort key: index of the source vertex where the fragment starts, then the signed
// offset of the fragment's start from that vertex, to order multiple fragments cut
// from one long source segment.
std::vector<std::pair<size_t, double>> keys(fragments.size());
for (size_t n = 0; n < fragments.size(); ++ n) {
Polyline &pl = fragments[n];
const size_t npos = size_t(-1);
size_t front = npos;
size_t back = npos;
for (const Point &pt : pl.points)
if (auto it = source_index.find(pt); it != source_index.end()) {
front = it->second;
break;
}
for (auto i = pl.points.rbegin(); i != pl.points.rend(); ++ i)
if (auto it = source_index.find(*i); it != source_index.end()) {
back = it->second;
break;
}
Vec2crd source_dir;
if (front == npos) {
// All vertices were created by clipping, thus the whole fragment lies on a
// single source segment. Find that segment.
double best = std::numeric_limits<double>::max();
for (size_t i = 0; i + 1 < src.size(); ++ i)
if (double d = Line::distance_to_squared(pl.first_point(), src[i], src[i + 1]); d < best) {
best = d;
front = i;
}
back = front;
source_dir = src[front + 1] - src[front];
} else
source_dir = src[std::min(back + 1, src.size() - 1)] - src[front > 0 ? front - 1 : 0];
if (front > back) {
pl.reverse();
std::swap(front, back);
} else if (front == back &&
(pl.last_point() - pl.first_point()).cast<double>().dot(source_dir.cast<double>()) < 0.)
pl.reverse();
const Vec2crd seg = src[std::min(front + 1, src.size() - 1)] - src[front];
keys[n] = { front, (pl.first_point() - src[front]).cast<double>().dot(seg.cast<double>()) };
}
std::vector<size_t> order(fragments.size());
std::iota(order.begin(), order.end(), size_t(0));
std::sort(order.begin(), order.end(), [&keys](size_t a, size_t b) { return keys[a] < keys[b]; });
Polylines sorted;
sorted.reserve(fragments.size());
for (size_t n : order)
sorted.emplace_back(std::move(fragments[n]));
fragments = std::move(sorted);
}
Lines _clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Polygons &clip) Lines _clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Polygons &clip)
{ {
// convert Lines to Polylines // convert Lines to Polylines
+4
View File
@@ -528,6 +528,10 @@ Slic3r::Polylines intersection_pl(const Slic3r::Polygons &subject, const Slic3r
Slic3r::Polylines3 intersection_pl(const Slic3r::Polylines3 &subject, const Slic3r::Polygon &clip); Slic3r::Polylines3 intersection_pl(const Slic3r::Polylines3 &subject, const Slic3r::Polygon &clip);
Slic3r::Polylines3 intersection_pl(const Slic3r::Polylines3 &subject, const Slic3r::ExPolygon &clip); Slic3r::Polylines3 intersection_pl(const Slic3r::Polylines3 &subject, const Slic3r::ExPolygon &clip);
// Orca: Sort and orient open polyline fragments produced by clipping `source` with
// intersection_pl(), so that they run in the same order and direction as the source polyline.
void restore_source_path_order(const Slic3r::Polyline &source, Slic3r::Polylines &fragments);
inline Slic3r::Lines intersection_ln(const Slic3r::Lines &subject, const Slic3r::Polygons &clip) inline Slic3r::Lines intersection_ln(const Slic3r::Lines &subject, const Slic3r::Polygons &clip)
{ {
return _clipper_ln(ClipperLib::ctIntersection, subject, clip); return _clipper_ln(ClipperLib::ctIntersection, subject, clip);
+8 -1
View File
@@ -177,7 +177,14 @@ double Extruder::filament_flow_ratio() const
// Return a "retract_before_wipe" percentage as a factor clamped to <0, 1> // Return a "retract_before_wipe" percentage as a factor clamped to <0, 1>
double Extruder::retract_before_wipe() const double Extruder::retract_before_wipe() const
{ {
return std::min(1., std::max(0., m_config->retract_before_wipe.get_at(m_config_index) * 0.01)); return std::clamp(m_config->retract_before_wipe.get_at(m_config_index) * 0.01, 0., 1.);
}
// Orca:
// Return a "retract_after_wipe" percentage as a factor clamped to <0, 1>
double Extruder::retract_after_wipe() const
{
return std::min(std::clamp(m_config->retract_after_wipe.get_at(m_config_index) * 0.01, 0., 1.), 1. - retract_before_wipe());
} }
double Extruder::retraction_length() const double Extruder::retraction_length() const
+2
View File
@@ -74,6 +74,8 @@ public:
double filament_cost() const; double filament_cost() const;
double filament_flow_ratio() const; double filament_flow_ratio() const;
double retract_before_wipe() const; double retract_before_wipe() const;
// Orca:
double retract_after_wipe() const;
double retraction_length() const; double retraction_length() const;
double retract_lift() const; double retract_lift() const;
int retract_speed() const; int retract_speed() const;
+10 -7
View File
@@ -352,8 +352,7 @@ namespace Slic3r
int k, int k,
const std::vector<unsigned int>& used_filaments, const std::vector<unsigned int>& used_filaments,
const std::unordered_map<int, std::vector<int>>& unplaceable_limits, const std::unordered_map<int, std::vector<int>>& unplaceable_limits,
int* cost, int* cost)
int timeout_ms)
{ {
auto distance_evaluator = std::make_shared<FlushDistanceEvaluator>(ctx.model_info.flush_matrix, used_filaments, ctx.model_info.layer_filaments); auto distance_evaluator = std::make_shared<FlushDistanceEvaluator>(ctx.model_info.flush_matrix, used_filaments, ctx.model_info.layer_filaments);
KMediods PAM(k, (int)used_filaments.size(), distance_evaluator, ctx.machine_info.master_extruder_id); KMediods PAM(k, (int)used_filaments.size(), distance_evaluator, ctx.machine_info.master_extruder_id);
@@ -369,7 +368,7 @@ namespace Slic3r
} }
PAM.set_cluster_group_size(cluster_size_limit); PAM.set_cluster_group_size(cluster_size_limit);
PAM.do_clustering(ctx, timeout_ms, 30); PAM.do_clustering(ctx, m_clustering_budget);
m_memoryed_heap = PAM.get_memoryed_groups(); m_memoryed_heap = PAM.get_memoryed_groups();
@@ -793,7 +792,7 @@ namespace Slic3r
2.1 In each cluster, make the point that minimizes the sum of distances within the cluster the medoid 2.1 In each cluster, make the point that minimizes the sum of distances within the cluster the medoid
2.2 Reassign each point to the cluster defined by the closest medoid determined in the previous step 2.2 Reassign each point to the cluster defined by the closest medoid determined in the previous step
*/ */
void KMediods::do_clustering(const FilamentGroupContext &context, int timeout_ms, int retry) void KMediods::do_clustering(const FilamentGroupContext& context, const ClusteringBudget& budget)
{ {
FlushTimeMachine T; FlushTimeMachine T;
T.time_machine_start(); T.time_machine_start();
@@ -817,7 +816,11 @@ namespace Slic3r
double best_cluster_cost = std::numeric_limits<double>::max(); double best_cluster_cost = std::numeric_limits<double>::max();
int retry_count = 0; int retry_count = 0;
while (retry_count < retry && T.time_machine_end() < timeout_ms) { // Run at least one restart; otherwise every filament would stay in the default group.
const int retry = std::max(1, budget.max_restarts);
auto within_budget = [&]() { return budget.timeout_ms <= 0 || T.time_machine_end() < budget.timeout_ms; };
while (retry_count < retry && within_budget()) {
std::vector<int> curr_cluster_centers = init_cluster_center(m_placeable_limits, m_unplaceable_limits, m_max_cluster_size, m_cluster_group_size, retry_count); std::vector<int> curr_cluster_centers = init_cluster_center(m_placeable_limits, m_unplaceable_limits, m_max_cluster_size, m_cluster_group_size, retry_count);
std::vector<int> curr_cluster_labels = assign_cluster_label(curr_cluster_centers, m_placeable_limits, m_unplaceable_limits, m_max_cluster_size, m_cluster_group_size); std::vector<int> curr_cluster_labels = assign_cluster_label(curr_cluster_centers, m_placeable_limits, m_unplaceable_limits, m_max_cluster_size, m_cluster_group_size);
double curr_cluster_cost = evaluate_labels(curr_cluster_labels); double curr_cluster_cost = evaluate_labels(curr_cluster_labels);
@@ -826,7 +829,7 @@ namespace Slic3r
update_memoryed_groups(g, memory_threshold, memoryed_groups); update_memoryed_groups(g, memory_threshold, memoryed_groups);
bool mediods_changed = true; bool mediods_changed = true;
while (mediods_changed && T.time_machine_end() < timeout_ms) { while (mediods_changed && within_budget()) {
mediods_changed = false; mediods_changed = false;
double best_swap_cost = curr_cluster_cost; double best_swap_cost = curr_cluster_cost;
int best_swap_cluster = -1; int best_swap_cluster = -1;
@@ -889,7 +892,7 @@ namespace Slic3r
if (estimated < ENUM_THRESHOLD) if (estimated < ENUM_THRESHOLD)
result = calc_group_by_enum(k, used_filaments, unplaceable_limits, cost); result = calc_group_by_enum(k, used_filaments, unplaceable_limits, cost);
else else
result = calc_group_by_kmedoids(k, used_filaments, unplaceable_limits, cost, 3000); result = calc_group_by_kmedoids(k, used_filaments, unplaceable_limits, cost);
change_memoryed_heaps_to_arrays(m_memoryed_heap, ctx.group_info.total_filament_num, used_filaments, m_memoryed_groups); change_memoryed_heaps_to_arrays(m_memoryed_heap, ctx.group_info.total_filament_num, used_filaments, m_memoryed_groups);
+15 -2
View File
@@ -142,6 +142,16 @@ namespace Slic3r
FilamentGroupContext::SpeedInfo m_speed_info; FilamentGroupContext::SpeedInfo m_speed_info;
}; };
// Search budget for the k-medoids clustering, an anytime search. Each restart is seeded from its
// own index, so what it returns depends on how many restarts complete before the clock expires,
// and therefore on the speed of the machine. A timeout_ms <= 0 removes the clock and bounds the
// search by max_restarts alone.
struct ClusteringBudget
{
int timeout_ms = 3000;
int max_restarts = 30;
};
class FilamentGroup class FilamentGroup
{ {
using MemoryedGroup = FilamentGroupUtils::MemoryedGroup; using MemoryedGroup = FilamentGroupUtils::MemoryedGroup;
@@ -149,6 +159,8 @@ namespace Slic3r
public: public:
explicit FilamentGroup(const FilamentGroupContext& ctx_) :ctx(ctx_) {} explicit FilamentGroup(const FilamentGroupContext& ctx_) :ctx(ctx_) {}
public: public:
void set_clustering_budget(const ClusteringBudget& budget) { m_clustering_budget = budget; }
std::vector<int> calc_filament_group(int * cost = nullptr); std::vector<int> calc_filament_group(int * cost = nullptr);
std::vector<std::vector<int>> get_memoryed_groups()const { return m_memoryed_groups; } std::vector<std::vector<int>> get_memoryed_groups()const { return m_memoryed_groups; }
@@ -162,7 +174,7 @@ namespace Slic3r
std::vector<int> calc_group_by_enum(int k, const std::vector<unsigned int>& used_filaments, std::vector<int> calc_group_by_enum(int k, const std::vector<unsigned int>& used_filaments,
const std::unordered_map<int, std::vector<int>>& unplaceable_limits, int* cost = nullptr); const std::unordered_map<int, std::vector<int>>& unplaceable_limits, int* cost = nullptr);
std::vector<int> calc_group_by_kmedoids(int k, const std::vector<unsigned int>& used_filaments, std::vector<int> calc_group_by_kmedoids(int k, const std::vector<unsigned int>& used_filaments,
const std::unordered_map<int, std::vector<int>>& unplaceable_limits, int* cost = nullptr, int timeout_ms = 500); const std::unordered_map<int, std::vector<int>>& unplaceable_limits, int* cost = nullptr);
std::map<int, int> rebuild_unprintables(const std::vector<unsigned int>& used_filaments, const std::map<int,int>& extruder_unprintables); std::map<int, int> rebuild_unprintables(const std::vector<unsigned int>& used_filaments, const std::map<int,int>& extruder_unprintables);
std::unordered_map<int, std::vector<int>> rebuild_nozzle_unprintables(const std::vector<unsigned int>& used_filaments, const std::unordered_map<int, std::vector<int>>& extruder_unprintables, const std::vector<int>& filament_volume_map); std::unordered_map<int, std::vector<int>> rebuild_nozzle_unprintables(const std::vector<unsigned int>& used_filaments, const std::unordered_map<int, std::vector<int>>& extruder_unprintables, const std::vector<int>& filament_volume_map);
@@ -175,6 +187,7 @@ namespace Slic3r
FilamentGroupContext ctx; FilamentGroupContext ctx;
MemoryedGroupHeap m_memoryed_heap; MemoryedGroupHeap m_memoryed_heap;
std::vector<std::vector<int>> m_memoryed_groups; std::vector<std::vector<int>> m_memoryed_groups;
ClusteringBudget m_clustering_budget;
public: public:
std::optional<std::function<bool(int, std::vector<int>&)>> get_custom_seq; std::optional<std::function<bool(int, std::vector<int>&)>> get_custom_seq;
}; };
@@ -220,7 +233,7 @@ namespace Slic3r
void set_memory_threshold(double threshold) { memory_threshold = threshold; } void set_memory_threshold(double threshold) { memory_threshold = threshold; }
MemoryedGroupHeap get_memoryed_groups()const { return memoryed_groups; } MemoryedGroupHeap get_memoryed_groups()const { return memoryed_groups; }
void do_clustering(const FilamentGroupContext& context, int timeout_ms = 100, int retry = 10); void do_clustering(const FilamentGroupContext& context, const ClusteringBudget& budget);
std::vector<int> get_cluster_labels()const { return m_cluster_labels; } std::vector<int> get_cluster_labels()const { return m_cluster_labels; }
protected: protected:
+15 -6
View File
@@ -272,10 +272,12 @@ struct SurfaceFillParams
// For Gyroid: when true, use the parameterized "optimized" wave. // For Gyroid: when true, use the parameterized "optimized" wave.
bool gyroid_optimized = false; bool gyroid_optimized = false;
bool anisotropic_surfaces{false};
CenterOfSurfacePattern center_of_surface_pattern{CenterOfSurfacePattern::Each_Surface}; CenterOfSurfacePattern center_of_surface_pattern{CenterOfSurfacePattern::Each_Surface};
bool separated_infills{false}; bool separated_infills{false};
// Orca: forced print order of surface fill loops/fragments for center-based patterns.
SurfaceFillOrder fill_order = SurfaceFillOrder::Default;
bool operator<(const SurfaceFillParams &rhs) const { bool operator<(const SurfaceFillParams &rhs) const {
#define RETURN_COMPARE_NON_EQUAL(KEY) if (this->KEY < rhs.KEY) return true; if (this->KEY > rhs.KEY) return false; #define RETURN_COMPARE_NON_EQUAL(KEY) if (this->KEY < rhs.KEY) return true; if (this->KEY > rhs.KEY) return false;
#define RETURN_COMPARE_NON_EQUAL_TYPED(TYPE, KEY) if (TYPE(this->KEY) < TYPE(rhs.KEY)) return true; if (TYPE(this->KEY) > TYPE(rhs.KEY)) return false; #define RETURN_COMPARE_NON_EQUAL_TYPED(TYPE, KEY) if (TYPE(this->KEY) < TYPE(rhs.KEY)) return true; if (TYPE(this->KEY) > TYPE(rhs.KEY)) return false;
@@ -308,9 +310,9 @@ struct SurfaceFillParams
RETURN_COMPARE_NON_EQUAL(skin_infill_depth); RETURN_COMPARE_NON_EQUAL(skin_infill_depth);
RETURN_COMPARE_NON_EQUAL(infill_overhang_angle); RETURN_COMPARE_NON_EQUAL(infill_overhang_angle);
RETURN_COMPARE_NON_EQUAL(gyroid_optimized); RETURN_COMPARE_NON_EQUAL(gyroid_optimized);
RETURN_COMPARE_NON_EQUAL(anisotropic_surfaces);
RETURN_COMPARE_NON_EQUAL(center_of_surface_pattern); RETURN_COMPARE_NON_EQUAL(center_of_surface_pattern);
RETURN_COMPARE_NON_EQUAL(separated_infills); RETURN_COMPARE_NON_EQUAL(separated_infills);
RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, fill_order);
return false; return false;
} }
@@ -337,10 +339,10 @@ struct SurfaceFillParams
this->infill_lock_depth == rhs.infill_lock_depth && this->infill_lock_depth == rhs.infill_lock_depth &&
this->skin_infill_depth == rhs.skin_infill_depth && this->skin_infill_depth == rhs.skin_infill_depth &&
this->infill_overhang_angle == rhs.infill_overhang_angle && this->infill_overhang_angle == rhs.infill_overhang_angle &&
this->anisotropic_surfaces == rhs.anisotropic_surfaces &&
this->center_of_surface_pattern == rhs.center_of_surface_pattern && this->center_of_surface_pattern == rhs.center_of_surface_pattern &&
this->separated_infills == rhs.separated_infills && this->separated_infills == rhs.separated_infills &&
this->gyroid_optimized == rhs.gyroid_optimized; this->gyroid_optimized == rhs.gyroid_optimized &&
this->fill_order == rhs.fill_order;
} }
}; };
@@ -879,7 +881,6 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
params.lateral_lattice_angle_1 = region_config.lateral_lattice_angle_1; params.lateral_lattice_angle_1 = region_config.lateral_lattice_angle_1;
params.lateral_lattice_angle_2 = region_config.lateral_lattice_angle_2; params.lateral_lattice_angle_2 = region_config.lateral_lattice_angle_2;
params.infill_overhang_angle = region_config.infill_overhang_angle; params.infill_overhang_angle = region_config.infill_overhang_angle;
params.anisotropic_surfaces = region_config.anisotropic_surfaces;
params.center_of_surface_pattern = region_config.center_of_surface_pattern; params.center_of_surface_pattern = region_config.center_of_surface_pattern;
params.separated_infills = region_config.separated_infills; params.separated_infills = region_config.separated_infills;
if (params.pattern == ipLockedZag) { if (params.pattern == ipLockedZag) {
@@ -936,6 +937,14 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
params.extruder = region_config.bottom_surface_filament_id; params.extruder = region_config.bottom_surface_filament_id;
else if (params.extrusion_role == erSolidInfill) else if (params.extrusion_role == erSolidInfill)
params.extruder = region_config.internal_solid_filament_id; params.extruder = region_config.internal_solid_filament_id;
// Orca: forced fill order applies only to top/bottom surfaces filled with a
// center-based pattern; everything else stays at Default to keep batching together.
if (params.pattern == ipConcentric || params.pattern == ipArchimedeanChords || params.pattern == ipOctagramSpiral) {
if (params.extrusion_role == erTopSolidInfill)
params.fill_order = region_config.top_surface_fill_order.value;
else if (params.extrusion_role == erBottomSurface)
params.fill_order = region_config.bottom_surface_fill_order.value;
}
// Orca: apply fill multiline only for sparse infill // Orca: apply fill multiline only for sparse infill
params.multiline = params.extrusion_role == erInternalInfill ? int(region_config.fill_multiline) : 1; params.multiline = params.extrusion_role == erInternalInfill ? int(region_config.fill_multiline) : 1;
@@ -1322,12 +1331,12 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
auto &region_config = layerm->region().config(); auto &region_config = layerm->region().config();
params.config = &region_config; params.config = &region_config;
params.pattern = surface_fill.params.pattern; params.pattern = surface_fill.params.pattern;
params.fill_order = surface_fill.params.fill_order;
// Orca: Checking the filling of a centered surface by drawing for each model parts // Orca: Checking the filling of a centered surface by drawing for each model parts
bool is_top_or_bottom = params.extrusion_role == erTopSolidInfill || params.extrusion_role == erBottomSurface; bool is_top_or_bottom = params.extrusion_role == erTopSolidInfill || params.extrusion_role == erBottomSurface;
bool is_centered_infill = surface_fill.params.pattern == ipArchimedeanChords || surface_fill.params.pattern == ipOctagramSpiral; bool is_centered_infill = surface_fill.params.pattern == ipArchimedeanChords || surface_fill.params.pattern == ipOctagramSpiral;
if (is_top_or_bottom) { if (is_top_or_bottom) {
params.is_anisotropic = surface_fill.params.anisotropic_surfaces; // Orca: anisotropic surfaces
params.center_of_surface_pattern = surface_fill.params.center_of_surface_pattern; // Orca: center of surface pattern params.center_of_surface_pattern = surface_fill.params.center_of_surface_pattern; // Orca: center of surface pattern
} }
// Orca: Each_Model centers the pattern on each model part's bbox; Each_Surface / Each_Assembly // Orca: Each_Model centers the pattern on each model part's bbox; Each_Surface / Each_Assembly
+6 -6
View File
@@ -162,10 +162,11 @@ void Fill::fill_surface_extrusion(const Surface* surface, const FillParams& para
out.push_back(eec = new ExtrusionEntityCollection()); out.push_back(eec = new ExtrusionEntityCollection());
// Only concentric fills are not sorted. // Only concentric fills are not sorted.
eec->no_sort = this->no_sort(); eec->no_sort = this->no_sort();
// ORCA: special flag for flow rate calibration // Orca: a forced surface fill order must survive the G-code path planner, which would
auto is_flow_calib = params.extrusion_role == erTopSolidInfill && this->print_object_config->has("calib_flowrate_topinfill_special_order") && // otherwise re-chain and possibly reverse the paths. This also covers the flow rate
this->print_object_config->option("calib_flowrate_topinfill_special_order")->getBool(); // calibration, which forces an outward fill order on its top surfaces.
if (is_flow_calib || params.is_anisotropic) { // Orca: disable sorting while anisotropic surfaces const bool keep_fill_order = params.fill_order != SurfaceFillOrder::Default;
if (keep_fill_order) {
eec->no_sort = true; eec->no_sort = true;
} }
size_t idx = eec->entities.size(); size_t idx = eec->entities.size();
@@ -180,13 +181,12 @@ void Fill::fill_surface_extrusion(const Surface* surface, const FillParams& para
params.extrusion_role, params.extrusion_role,
flow_mm3_per_mm, float(flow_width), params.flow.height()); flow_mm3_per_mm, float(flow_width), params.flow.height());
} }
if (!params.can_reverse || is_flow_calib) { if (!params.can_reverse || keep_fill_order) {
for (size_t i = idx; i < eec->entities.size(); i++) for (size_t i = idx; i < eec->entities.size(); i++)
eec->entities[i]->set_reverse(); eec->entities[i]->set_reverse();
} }
// Orca: run gap fill // Orca: run gap fill
if (!(params.is_anisotropic)) // Orca: Disable gap filling while anisotropic
this->_create_gap_fill(surface, params, eec); this->_create_gap_fill(surface, params, eec);
} }
} }
+4 -1
View File
@@ -100,13 +100,16 @@ struct FillParams
bool dont_sort{ false }; // do not sort the lines, just simply connect them bool dont_sort{ false }; // do not sort the lines, just simply connect them
bool can_reverse{true}; bool can_reverse{true};
// Orca: forced print order of surface fill loops/fragments for center-based patterns
// (Concentric, Archimedean Chords, Octagram Spiral). Default keeps shortest-path ordering.
SurfaceFillOrder fill_order { SurfaceFillOrder::Default };
float horiz_move{0.0}; //move infill to get cross zag pattern float horiz_move{0.0}; //move infill to get cross zag pattern
bool symmetric_infill_y_axis{false}; bool symmetric_infill_y_axis{false};
coord_t symmetric_y_axis{0}; coord_t symmetric_y_axis{0};
bool locked_zag{false}; bool locked_zag{false};
float infill_lock_depth{0.0}; float infill_lock_depth{0.0};
float skin_infill_depth{0.0}; float skin_infill_depth{0.0};
bool is_anisotropic{false};
CenterOfSurfacePattern center_of_surface_pattern{CenterOfSurfacePattern::Each_Surface}; CenterOfSurfacePattern center_of_surface_pattern{CenterOfSurfacePattern::Each_Surface};
}; };
static_assert(IsTriviallyCopyable<FillParams>::value, "FillParams class is not POD (and it should be - see constructor)."); static_assert(IsTriviallyCopyable<FillParams>::value, "FillParams class is not POD (and it should be - see constructor).");
+16
View File
@@ -42,6 +42,10 @@ void FillConcentric::_fill_surface_single(
// adhesion problems of the first central tiny loops // adhesion problems of the first central tiny loops
loops = union_pt_chained_outside_in(loops); loops = union_pt_chained_outside_in(loops);
// Orca: an outward fill order prints the innermost loops first instead.
if (params.fill_order == SurfaceFillOrder::Outward)
std::reverse(loops.begin(), loops.end());
// split paths using a nearest neighbor search // split paths using a nearest neighbor search
size_t iPathFirst = polylines_out.size(); size_t iPathFirst = polylines_out.size();
Point last_pos(0, 0); Point last_pos(0, 0);
@@ -108,6 +112,17 @@ void FillConcentric::_fill_surface_single(const FillParams& params,
all_extrusions.emplace_back(&wall); all_extrusions.emplace_back(&wall);
} }
// Orca: a forced fill order prints the loops in strictly monotonic depth order so
// that surfaces broken up by holes or slots cannot hop outward and back inward.
const bool forced_fill_order = params.fill_order != SurfaceFillOrder::Default;
if (forced_fill_order) {
const bool outward = params.fill_order == SurfaceFillOrder::Outward;
std::stable_sort(all_extrusions.begin(), all_extrusions.end(),
[outward](const Arachne::ExtrusionLine *a, const Arachne::ExtrusionLine *b) {
return outward ? a->inset_idx > b->inset_idx : a->inset_idx < b->inset_idx;
});
}
// Split paths using a nearest neighbor search. // Split paths using a nearest neighbor search.
size_t firts_poly_idx = thick_polylines_out.size(); size_t firts_poly_idx = thick_polylines_out.size();
Point last_pos(0, 0); Point last_pos(0, 0);
@@ -136,6 +151,7 @@ void FillConcentric::_fill_surface_single(const FillParams& params,
if (j < thick_polylines_out.size()) if (j < thick_polylines_out.size())
thick_polylines_out.erase(thick_polylines_out.begin() + int(j), thick_polylines_out.end()); thick_polylines_out.erase(thick_polylines_out.begin() + int(j), thick_polylines_out.end());
if (!forced_fill_order)
reorder_by_shortest_traverse(thick_polylines_out); reorder_by_shortest_traverse(thick_polylines_out);
} }
else { else {
+14 -37
View File
@@ -133,49 +133,26 @@ void FillPlanePath::_fill_surface_single(
polylines = intersection_pl(std::move(polylines), expolygon); polylines = intersection_pl(std::move(polylines), expolygon);
if (!polylines.empty()) { if (!polylines.empty()) {
Polylines chained; Polylines chained;
if (!params.is_anisotropic) { // Orca: not anisotropic surface if (params.dont_connect() || params.density > 0.5) {
if ((params.dont_connect() || params.density > 0.5)) { if (params.fill_order != SurfaceFillOrder::Default) {
// ORCA: special flag for flow rate calibration // Orca: print the fragments in the order they appear along the generated
auto is_flow_calib = params.extrusion_role == erTopSolidInfill && // path, which runs from the center outwards. The Euclidean distance from
this->print_object_config->has("calib_flowrate_topinfill_special_order") && // the center cannot be used for this: along the Octagram Spiral the radius
this->print_object_config->option("calib_flowrate_topinfill_special_order")->getBool() && // oscillates by far more than the ring spacing, so fragments of different
dynamic_cast<FillArchimedeanChords*>(this); // rings would interleave.
if (is_flow_calib) { restore_source_path_order(polyline, polylines);
// We want the spiral part to be printed inside-out chained = std::move(polylines);
// Find the center spiral line first, by looking for the longest one if (params.fill_order == SurfaceFillOrder::Inward) {
auto it = std::max_element(polylines.begin(), polylines.end(), // The source path runs from the center outwards; flip everything for inward.
[](const Polyline& a, const Polyline& b) { return a.length() < b.length(); }); std::reverse(chained.begin(), chained.end());
Polyline center_spiral = std::move(*it); for (Polyline &pl : chained)
pl.reverse();
// Ensure the spiral is printed from inside to out
if ((center_spiral.first_point().squaredNorm() > center_spiral.last_point().squaredNorm())) {
center_spiral.reverse();
} }
// Chain the other polylines
polylines.erase(it);
chained = chain_polylines(std::move(polylines), nullptr);
// Then add the center spiral back
chained.push_back(std::move(center_spiral));
} else { } else {
chained = chain_polylines(std::move(polylines), nullptr); chained = chain_polylines(std::move(polylines), nullptr);
} }
} else } else
connect_infill(std::move(polylines), expolygon, chained, this->spacing, params); connect_infill(std::move(polylines), expolygon, chained, this->spacing, params);
} else { // Orca: anisotropic surface
const Point _center(0., 0.);
for (Polyline& segment : polylines) { // sort paths by its direction
if (segment.size() > 1) { // need at least two points to evaluate direction
if (segment.first_point().ccw(segment.points[1], _center) < 0)
segment.reverse();
}
chained.emplace_back(std::move(segment));
}
std::sort(chained.begin(), chained.end(), [&_center](const Polyline& a, const Polyline& b) { // just sort polylines from center to outside
return a.distance_to(_center) < b.distance_to(_center);
});
}
// paths must be repositioned and rotated back // paths must be repositioned and rotated back
for (Polyline& pl : chained) { for (Polyline& pl : chained) {
pl.translate(shift.x(), shift.y()); pl.translate(shift.x(), shift.y());
+39 -20
View File
@@ -441,22 +441,30 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
// Declare & initialize retraction lengths // Declare & initialize retraction lengths
double retraction_length_remaining = 0, double retraction_length_remaining = 0,
retractionBeforeWipe = 0, retraction_length_before_wipe = 0,
retractionDuringWipe = 0; retraction_length_during_wipe = 0,
retraction_length_after_wipe = 0;
// initialise the remaining retraction amount with the full retraction amount. // Initialise the remaining retraction amount with the full retraction amount.
retraction_length_remaining = toolchange ? extruder->retract_length_toolchange() : extruder->retraction_length(); retraction_length_remaining = toolchange ?
extruder->retract_length_toolchange() : extruder->retraction_length();
// nothing to retract - return early // Nothing to retract - return early
if(retraction_length_remaining <=EPSILON) return {0.f,0.f}; if (retraction_length_remaining <= EPSILON)
return { 0.f, 0.f, 0.f };
// calculate retraction before wipe distance from the user setting. Keep adding to this variable any excess retraction needed // Calculate retraction before and after wipe distances from the user setting.
// to be performed before the wipe. // Keep adding to the for retraction before wipe variable any excess retraction
retractionBeforeWipe = retraction_length_remaining * extruder->retract_before_wipe(); // needed to be performed before the wipe.
retraction_length_remaining -= retractionBeforeWipe; // subtract it from the remaining retraction length retraction_length_before_wipe = retraction_length_remaining * extruder->retract_before_wipe();
retraction_length_after_wipe = retraction_length_remaining * extruder->retract_after_wipe();
// all of the retraction is to be done before the wipe // Subtract it from the remaining retraction length
if(retraction_length_remaining <=EPSILON) return {retractionBeforeWipe,0.f}; retraction_length_remaining -= retraction_length_before_wipe + retraction_length_after_wipe;
// All of the retraction is to be done before the wipe
if (retraction_length_remaining <= EPSILON)
return { retraction_length_before_wipe, 0., retraction_length_after_wipe };
// Calculate wipe speed // Calculate wipe speed
// Orca: resolve the travel_speed slot via the Print-side per-layer resolver; the writer's // Orca: resolve the travel_speed slot via the Print-side per-layer resolver; the writer's
@@ -471,18 +479,25 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
double wipe_path_length = std::min(wipe_path.length(), wipe_dist); double wipe_path_length = std::min(wipe_path.length(), wipe_dist);
// Calculate the maximum retraction amount during wipe // Calculate the maximum retraction amount during wipe
retractionDuringWipe = config.retraction_speed.get_at(extruder_id) * unscale_(wipe_path_length) / wipe_speed; retraction_length_during_wipe = config.retraction_speed.get_at(extruder_id) *
// If the maximum retraction amount during wipe is too small, return 0 and retract everything prior to the wipe. unscale_(wipe_path_length) / wipe_speed;
if(retractionDuringWipe <= EPSILON) return {retractionBeforeWipe,0.f};
// If the maximum retraction amount during wipe is too small,
// disable wipe-time retraction and leave any remaining retract amount
// to the subsequent standard retract flow.
if (retraction_length_during_wipe <= EPSILON)
return { retraction_length_before_wipe, 0., retraction_length_after_wipe };
// If the maximum retraction amount during wipe is greater than any remaining retraction length // If the maximum retraction amount during wipe is greater than any remaining retraction length
// return the remaining retraction length to be retracted during the wipe // return the remaining retraction length to be retracted during the wipe
if (retractionDuringWipe - retraction_length_remaining > EPSILON) return {retractionBeforeWipe,retraction_length_remaining}; if (retraction_length_during_wipe - retraction_length_remaining > EPSILON)
return { retraction_length_before_wipe, retraction_length_remaining, retraction_length_after_wipe };
// We will always proceed with incrementing the retraction amount before wiping with the difference // We will always proceed with incrementing the retraction amount before wiping with the difference
// and return the maximum allowed wipe amount to be retracted during the wipe move // and return the maximum allowed wipe amount to be retracted during the wipe move
retractionBeforeWipe += retraction_length_remaining - retractionDuringWipe; retraction_length_before_wipe += retraction_length_remaining - retraction_length_during_wipe;
return {retractionBeforeWipe, retractionDuringWipe};
return { retraction_length_before_wipe, retraction_length_during_wipe, retraction_length_after_wipe };
} }
std::string transform_gcode(const std::string &gcode, Vec2f pos, const Vec2f &translation, float angle) std::string transform_gcode(const std::string &gcode, Vec2f pos, const Vec2f &translation, float angle)
@@ -8507,8 +8522,12 @@ std::string GCode::retract(bool toolchange, bool is_last_retraction, LiftType li
// wipe (if it's enabled for this extruder and we have a stored wipe path and no-zero wipe distance) // wipe (if it's enabled for this extruder and we have a stored wipe path and no-zero wipe distance)
if (FILAMENT_CONFIG(wipe) && m_wipe.has_path() && scale_(FILAMENT_CONFIG(wipe_distance)) > SCALED_EPSILON) { if (FILAMENT_CONFIG(wipe) && m_wipe.has_path() && scale_(FILAMENT_CONFIG(wipe_distance)) > SCALED_EPSILON) {
Wipe::RetractionValues wipeRetractions = m_wipe.calculateWipeRetractionLengths(*this, toolchange); Wipe::RetractionValues wipeRetractions = m_wipe.calculateWipeRetractionLengths(*this, toolchange);
gcode += toolchange ? m_writer.retract_for_toolchange(true,wipeRetractions.retractLengthBeforeWipe) : m_writer.retract(true, wipeRetractions.retractLengthBeforeWipe); gcode += toolchange ? m_writer.retract_for_toolchange(true, wipeRetractions.retraction_length_before_wipe) :
gcode += m_wipe.wipe(*this,wipeRetractions.retractLengthDuringWipe, toolchange, is_last_retraction); m_writer.retract(true, wipeRetractions.retraction_length_before_wipe);
gcode += m_wipe.wipe(*this, wipeRetractions.retraction_length_during_wipe, toolchange, is_last_retraction);
// Orca: wipeRetractions.retraction_length_after_wipe is not being used explicitly,
// the remaining retraction after wipe is handled by the subsequent m_writer.retract() call
} }
/* The parent class will decide whether we need to perform an actual retraction /* The parent class will decide whether we need to perform an actual retraction
+7 -2
View File
@@ -60,15 +60,20 @@ class Wipe {
public: public:
bool enable; bool enable;
Polyline path; Polyline path;
// Orca:
struct RetractionValues{ struct RetractionValues{
double retractLengthBeforeWipe; double retraction_length_before_wipe = 0.;
double retractLengthDuringWipe; double retraction_length_during_wipe = 0.;
double retraction_length_after_wipe = 0.;
}; };
Wipe() : enable(false) {} Wipe() : enable(false) {}
bool has_path() const { return !this->path.points.empty(); } bool has_path() const { return !this->path.points.empty(); }
void reset_path() { this->path = Polyline(); } void reset_path() { this->path = Polyline(); }
std::string wipe(GCode &gcodegen, double length, bool toolchange = false, bool is_last = false); std::string wipe(GCode &gcodegen, double length, bool toolchange = false, bool is_last = false);
// Orca:
RetractionValues calculateWipeRetractionLengths(GCode& gcodegen, bool toolchange); RetractionValues calculateWipeRetractionLengths(GCode& gcodegen, bool toolchange);
}; };
+4 -1
View File
@@ -6551,8 +6551,11 @@ void GCodeProcessor::process_T(const std::string_view command, int nozzle_id)
if (command.length() > 1) { if (command.length() > 1) {
if (eid < 0 || eid > 254) { 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 //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?" || 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; return;
// T-1 is a valid gcode line for RepRap Firmwares (used to deselects all tools) // T-1 is a valid gcode line for RepRap Firmwares (used to deselects all tools)
+16 -4
View File
@@ -1048,6 +1048,8 @@ static std::vector<std::string> s_Preset_print_options{
"top_surface_expansion_margin", "top_surface_expansion_margin",
"top_surface_expansion_direction", "top_surface_expansion_direction",
"bottom_surface_pattern", "bottom_surface_pattern",
"top_surface_fill_order",
"bottom_surface_fill_order",
"infill_direction", "infill_direction",
"solid_infill_direction", "solid_infill_direction",
"top_layer_direction", "top_layer_direction",
@@ -1063,7 +1065,6 @@ static std::vector<std::string> s_Preset_print_options{
"skin_infill_density", "skin_infill_density",
"align_infill_direction_to_model", "align_infill_direction_to_model",
"extra_solid_infills", "extra_solid_infills",
"anisotropic_surfaces",
"center_of_surface_pattern", "center_of_surface_pattern",
"separated_infills", "separated_infills",
"minimum_sparse_infill_area", "minimum_sparse_infill_area",
@@ -1309,7 +1310,6 @@ static std::vector<std::string> s_Preset_print_options{
"interlocking_depth", "interlocking_depth",
"interlocking_boundary_avoidance", "interlocking_boundary_avoidance",
"interlocking_beam_width", "interlocking_beam_width",
"calib_flowrate_topinfill_special_order",
// Z Anti-Aliasing (ZAA) // Z Anti-Aliasing (ZAA)
"zaa_enabled", "zaa_enabled",
"zaa_minimize_perimeter_height", "zaa_minimize_perimeter_height",
@@ -1335,8 +1335,20 @@ static std::vector<std::string> s_Preset_filament_options {/*"filament_colour",
//exhaust fan control //exhaust fan control
"activate_air_filtration","activate_air_filtration_during_print","activate_air_filtration_on_completion","during_print_exhaust_fan_speed","complete_print_exhaust_fan_speed", "activate_air_filtration","activate_air_filtration_during_print","activate_air_filtration_on_completion","during_print_exhaust_fan_speed","complete_print_exhaust_fan_speed",
// Retract overrides // Retract overrides
"filament_retraction_length", "filament_z_hop", "filament_z_hop_types", "filament_retract_lift_above", "filament_retract_lift_below", "filament_retract_lift_enforce", "filament_retraction_speed", "filament_deretraction_speed", "filament_retract_restart_extra", "filament_retraction_minimum_travel", "filament_deretraction_speed",
"filament_retract_when_changing_layer", "filament_wipe", "filament_retract_before_wipe", "filament_retract_after_wipe", // Orca
"filament_retract_before_wipe",
"filament_retract_lift_above",
"filament_retract_lift_below",
"filament_retract_lift_enforce",
"filament_retract_restart_extra",
"filament_retract_when_changing_layer",
"filament_retraction_length",
"filament_retraction_minimum_travel",
"filament_retraction_speed",
"filament_wipe",
"filament_z_hop",
"filament_z_hop_types",
// Profile compatibility // Profile compatibility
"filament_vendor", "compatible_prints", "compatible_prints_condition", "compatible_printers", "compatible_printers_condition", "inherits", "filament_vendor", "compatible_prints", "compatible_prints_condition", "compatible_printers", "compatible_printers_condition", "inherits",
//BBS //BBS
+12 -3
View File
@@ -177,6 +177,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
"filename_format", "filename_format",
"retraction_minimum_travel", "retraction_minimum_travel",
"retract_before_wipe", "retract_before_wipe",
// Orca:
"retract_after_wipe",
"retract_when_changing_layer", "retract_when_changing_layer",
"retraction_length", "retraction_length",
"retract_length_toolchange", "retract_length_toolchange",
@@ -210,7 +212,6 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
"chamber_minimal_temperature", "chamber_minimal_temperature",
"thumbnails", "thumbnails",
"thumbnails_format", "thumbnails_format",
"anisotropic_surfaces",
"center_of_surface_pattern", "center_of_surface_pattern",
"separated_infills", "separated_infills",
"seam_gap", "seam_gap",
@@ -3676,6 +3677,8 @@ int Print::get_filament_config_indx(int filament_id, int layer_id)
void Print::update_filament_self_index_cache() 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; std::vector<int> values;
if (m_full_print_config.has("filament_self_index")) { if (m_full_print_config.has("filament_self_index")) {
values = m_full_print_config.option<ConfigOptionInts>("filament_self_index")->values; values = m_full_print_config.option<ConfigOptionInts>("filament_self_index")->values;
@@ -3721,8 +3724,11 @@ int Print::get_config_index(int filament_id, int layer_id, const std::vector<std
return filament_id; return filament_id;
auto nozzle_info = group_result->get_nozzle_for_filament(filament_id, layer_id); auto nozzle_info = group_result->get_nozzle_for_filament(filament_id, layer_id);
if (!nozzle_info.has_value()) { if (!nozzle_info.has_value()) {
// 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_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 % << 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; layer_id;
return 0; return 0;
} }
@@ -3750,8 +3756,11 @@ int Print::get_config_index(int filament_id, int layer_id, const std::vector<std
return (int)get_extruder_id(filament_id); return (int)get_extruder_id(filament_id);
auto nozzle_info = group_result->get_nozzle_for_filament(filament_id, layer_id); auto nozzle_info = group_result->get_nozzle_for_filament(filament_id, layer_id);
if (!nozzle_info.has_value()) { if (!nozzle_info.has_value()) {
// 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_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 % << 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; layer_id;
return 0; return 0;
} }
+4
View File
@@ -1303,6 +1303,10 @@ private:
FilamentIndexMap m_filament_index_map; FilamentIndexMap m_filament_index_map;
// Used to cache printer and process parameter information // Used to cache printer and process parameter information
PrintIndexMap m_nozzle_index_map; 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" // save the config value of "filament_self_index"
std::vector<int> m_filament_self_index; std::vector<int> m_filament_self_index;
+122 -27
View File
@@ -79,6 +79,9 @@ const std::vector<std::string> filament_extruder_override_keys = {
"filament_wipe", "filament_wipe",
// percents // percents
"filament_retract_before_wipe", "filament_retract_before_wipe",
// Orca
"filament_retract_after_wipe",
// BBS
"filament_long_retractions_when_cut", "filament_long_retractions_when_cut",
"filament_retraction_distances_when_cut" "filament_retraction_distances_when_cut"
}; };
@@ -310,6 +313,14 @@ static t_config_enum_values s_keys_map_WallDirection{
}; };
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(WallDirection) CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(WallDirection)
//Orca
static t_config_enum_values s_keys_map_SurfaceFillOrder{
{ "default", int(SurfaceFillOrder::Default) },
{ "outward", int(SurfaceFillOrder::Outward) },
{ "inward", int(SurfaceFillOrder::Inward) },
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SurfaceFillOrder)
//BBS //BBS
static t_config_enum_values s_keys_map_PrintSequence { static t_config_enum_values s_keys_map_PrintSequence {
{ "by layer", int(PrintSequence::ByLayer) }, { "by layer", int(PrintSequence::ByLayer) },
@@ -2319,6 +2330,40 @@ void PrintConfigDef::init_fff_params()
def->max = 100; def->max = 100;
def->set_default_value(new ConfigOptionPercent(100)); def->set_default_value(new ConfigOptionPercent(100));
auto def_top_fill_order = def = this->add("top_surface_fill_order", coEnum);
def->label = L("Top surface fill order");
def->category = L("Strength");
def->tooltip = L("Direction in which top surfaces are filled when using a center-based pattern "
"(Concentric, Archimedean Chords, Octagram Spiral).\n"
"Outward starts at the center of the surface, so any excess material is pushed "
"towards the edge where it is least visible. Inward starts at the edge and ends "
"with the tight curves at the center.\n"
"Default uses shortest-path ordering, which may run in either direction.");
def->enum_keys_map = &ConfigOptionEnum<SurfaceFillOrder>::get_enum_values();
def->enum_values.push_back("default");
def->enum_values.push_back("outward");
def->enum_values.push_back("inward");
def->enum_labels.push_back(L("Default"));
def->enum_labels.push_back(L("Outward"));
def->enum_labels.push_back(L("Inward"));
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum<SurfaceFillOrder>(SurfaceFillOrder::Default));
def = this->add("bottom_surface_fill_order", coEnum);
def->label = L("Bottom surface fill order");
def->category = L("Strength");
def->tooltip = L("Direction in which bottom surfaces are filled when using a center-based pattern "
"(Concentric, Archimedean Chords, Octagram Spiral).\n"
"Inward starts each surface with the wider outer curves, which improves first layer "
"adhesion on build plates where the tight curves at the center may not stick. "
"Outward starts at the center, pushing any excess material towards the edge.\n"
"Default uses shortest-path ordering, which may run in either direction.");
def->enum_keys_map = &ConfigOptionEnum<SurfaceFillOrder>::get_enum_values();
def->enum_values = def_top_fill_order->enum_values;
def->enum_labels = def_top_fill_order->enum_labels;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum<SurfaceFillOrder>(SurfaceFillOrder::Default));
def = this->add("internal_solid_infill_pattern", coEnum); def = this->add("internal_solid_infill_pattern", coEnum);
def->label = L("Internal solid infill pattern"); def->label = L("Internal solid infill pattern");
def->category = L("Strength"); def->category = L("Strength");
@@ -4602,11 +4647,6 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced; def->mode = comAdvanced;
def->set_default_value(new ConfigOptionInt(2)); def->set_default_value(new ConfigOptionInt(2));
// ORCA: special flag for flow rate calibration
def = this->add("calib_flowrate_topinfill_special_order", coBool);
def->mode = comDevelop;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("ironing_type", coEnum); def = this->add("ironing_type", coEnum);
def->label = L("Ironing type"); def->label = L("Ironing type");
def->category = L("Quality"); def->category = L("Quality");
@@ -5546,6 +5586,15 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced; def->mode = comAdvanced;
def->set_default_value(new ConfigOptionPercents { 100 }); def->set_default_value(new ConfigOptionPercents { 100 });
// Orca:
def = this->add("retract_after_wipe", coPercents);
def->label = L("Retract amount after wipe");
def->tooltip = L("The length of fast retraction after wipe, relative to retraction length.\n"
"The value will be clamped by 100% minus the retract amount before the wipe value.");
def->sidetext = "%";
def->mode = comExpert;
def->set_default_value(new ConfigOptionPercents { 0 });
def = this->add("retract_when_changing_layer", coBools); def = this->add("retract_when_changing_layer", coBools);
def->label = L("Retract on layer change"); def->label = L("Retract on layer change");
def->tooltip = L("This forces a retraction on layer changes."); def->tooltip = L("This forces a retraction on layer changes.");
@@ -7186,17 +7235,6 @@ void PrintConfigDef::init_fff_params()
def->min = 0; def->min = 0;
def->set_default_value(new ConfigOptionFloat(0.6)); def->set_default_value(new ConfigOptionFloat(0.6));
def = this->add("anisotropic_surfaces", coBool);
def->label = L("Anisotropic surfaces");
def->category = L("Strength");
def->tooltip = L("Anisotropic patterns on the top and bottom surfaces.\n"
"Co-directional printing mode will be applied. For certain patterns, omni-directional filling provides color "
"dispersion when using multi-colored or silk plastics.\n"
"This option disable the gap fill.\n"
"This option can increase a printing time.");
def->mode = comExpert;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("separated_infills", coBool); def = this->add("separated_infills", coBool);
def->label = L("Separated infills"); def->label = L("Separated infills");
def->category = L("Strength"); def->category = L("Strength");
@@ -7987,17 +8025,44 @@ void PrintConfigDef::init_extruder_option_keys()
{ {
// ConfigOptionFloats, ConfigOptionPercents, ConfigOptionBools, ConfigOptionStrings // ConfigOptionFloats, ConfigOptionPercents, ConfigOptionBools, ConfigOptionStrings
m_extruder_option_keys = { m_extruder_option_keys = {
"extruder_type", "nozzle_diameter", "default_nozzle_volume_type", "min_layer_height", "max_layer_height", "extruder_offset", "default_filament_profile",
"extruder_printable_height", "nozzle_volume", "nozzle_type", "nozzle_flush_dataset", "default_nozzle_volume_type",
"retraction_length", "z_hop", "z_hop_types", "travel_slope", "retract_lift_above", "retract_lift_below", "retract_lift_enforce", "retraction_speed", "deretraction_speed", "deretraction_speed",
"retract_before_wipe", "retract_restart_extra", "retraction_minimum_travel", "wipe", "wipe_distance", "extruder_colour",
"retract_when_changing_layer", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour", "extruder_offset",
"default_filament_profile","retraction_distances_when_cut","long_retractions_when_cut" "extruder_printable_height",
"extruder_type",
"long_retractions_when_cut",
"max_layer_height",
"min_layer_height",
"nozzle_diameter",
"nozzle_flush_dataset",
"nozzle_type",
"nozzle_volume",
"retract_after_wipe",
"retract_before_wipe",
"retract_length_toolchange",
"retract_lift_above",
"retract_lift_below",
"retract_lift_enforce",
"retract_restart_extra",
"retract_restart_extra_toolchange",
"retract_when_changing_layer",
"retraction_distances_when_cut",
"retraction_length",
"retraction_minimum_travel",
"retraction_speed",
"travel_slope",
"wipe",
"wipe_distance",
"z_hop",
"z_hop_types"
}; };
m_extruder_retract_keys = { m_extruder_retract_keys = {
"deretraction_speed", "deretraction_speed",
"long_retractions_when_cut", "long_retractions_when_cut",
"retract_after_wipe",
"retract_before_wipe", "retract_before_wipe",
"retract_lift_above", "retract_lift_above",
"retract_lift_below", "retract_lift_below",
@@ -8020,17 +8085,40 @@ void PrintConfigDef::init_extruder_option_keys()
void PrintConfigDef::init_filament_option_keys() void PrintConfigDef::init_filament_option_keys()
{ {
m_filament_option_keys = { m_filament_option_keys = {
"filament_diameter", "min_layer_height", "max_layer_height","volumetric_speed_coefficients", "default_filament_profile",
"retraction_length", "z_hop", "z_hop_types", "retract_lift_above", "retract_lift_below", "retract_lift_enforce", "retraction_speed", "deretraction_speed", "deretraction_speed",
"retract_before_wipe", "filament_retract_length_nc", "retract_restart_extra", "retraction_minimum_travel", "wipe", "wipe_distance", "filament_colour",
"retract_when_changing_layer", "retract_length_toolchange", "retract_restart_extra_toolchange", "filament_colour", "filament_diameter",
"default_filament_profile","retraction_distances_when_cut","long_retractions_when_cut"/*,"filament_seam_gap"*/ "filament_retract_length_nc",
// "filament_seam_gap",
"long_retractions_when_cut",
"max_layer_height",
"min_layer_height",
"retract_after_wipe",
"retract_before_wipe",
"retract_length_toolchange",
"retract_lift_above",
"retract_lift_below",
"retract_lift_enforce",
"retract_restart_extra",
"retract_restart_extra_toolchange",
"retract_when_changing_layer",
"retraction_distances_when_cut",
"retraction_length",
"retraction_minimum_travel",
"retraction_speed",
"volumetric_speed_coefficients",
"wipe",
"wipe_distance",
"z_hop",
"z_hop_types",
}; };
m_filament_retract_keys = { m_filament_retract_keys = {
"deretraction_speed", "deretraction_speed",
"filament_retract_length_nc", "filament_retract_length_nc",
"long_retractions_when_cut", "long_retractions_when_cut",
"retract_after_wipe",
"retract_before_wipe", "retract_before_wipe",
"retract_lift_above", "retract_lift_above",
"retract_lift_below", "retract_lift_below",
@@ -8928,6 +9016,8 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
"internal_bridge_support_thickness", "top_area_threshold", "reduce_wall_solid_infill","filament_load_time","filament_unload_time", "internal_bridge_support_thickness", "top_area_threshold", "reduce_wall_solid_infill","filament_load_time","filament_unload_time",
"smooth_coefficient", "overhang_totally_speed", "silent_mode", "smooth_coefficient", "overhang_totally_speed", "silent_mode",
"overhang_speed_classic", "filament_prime_volume", "overhang_speed_classic", "filament_prime_volume",
"calib_flowrate_topinfill_special_order",
"anisotropic_surfaces", // superseded by top_surface_fill_order / bottom_surface_fill_order
}; };
if (ignore.find(opt_key) != ignore.end()) { if (ignore.find(opt_key) != ignore.end()) {
@@ -9074,6 +9164,9 @@ std::set<std::string> filament_options_with_variant = {
//BBS //BBS
"filament_wipe_distance", "filament_wipe_distance",
"filament_retract_before_wipe", "filament_retract_before_wipe",
// Orca
"filament_retract_after_wipe",
//BBS
"filament_long_retractions_when_cut", "filament_long_retractions_when_cut",
"filament_retraction_distances_when_cut", "filament_retraction_distances_when_cut",
"long_retractions_when_ec", "long_retractions_when_ec",
@@ -9124,6 +9217,8 @@ std::set<std::string> printer_options_with_variant_1 = {
"wipe", "wipe",
"wipe_distance", "wipe_distance",
"retract_before_wipe", "retract_before_wipe",
// Orca:
"retract_after_wipe",
"retract_length_toolchange", "retract_length_toolchange",
"retract_restart_extra", "retract_restart_extra",
"retract_restart_extra_toolchange", "retract_restart_extra_toolchange",
+15 -4
View File
@@ -193,6 +193,15 @@ enum class WallDirection
Count, Count,
}; };
// Orca: print order of surface fill loops/fragments for center-based fill patterns
// (Concentric, Archimedean Chords, Octagram Spiral).
enum class SurfaceFillOrder {
Default,
Outward,
Inward,
Count,
};
//BBS //BBS
enum class PrintSequence { enum class PrintSequence {
ByLayer, ByLayer,
@@ -660,6 +669,7 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(WipeTowerWallType)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PerimeterGeneratorType) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PerimeterGeneratorType)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ToolChangeOrderingType) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ToolChangeOrderingType)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PowerLossRecoveryMode) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PowerLossRecoveryMode)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SurfaceFillOrder)
#undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS #undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS
@@ -1211,9 +1221,6 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionInt, interlocking_beam_layer_count)) ((ConfigOptionInt, interlocking_beam_layer_count))
((ConfigOptionInt, interlocking_depth)) ((ConfigOptionInt, interlocking_depth))
((ConfigOptionInt, interlocking_boundary_avoidance)) ((ConfigOptionInt, interlocking_boundary_avoidance))
// Orca: internal use only
((ConfigOptionBool, calib_flowrate_topinfill_special_order)) // ORCA: special flag for flow rate calibration
) )
// This object is mapped to Perl as Slic3r::Config::PrintRegion. // This object is mapped to Perl as Slic3r::Config::PrintRegion.
@@ -1237,6 +1244,8 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionPercent, bottom_surface_density)) ((ConfigOptionPercent, bottom_surface_density))
((ConfigOptionEnum<InfillPattern>, top_surface_pattern)) ((ConfigOptionEnum<InfillPattern>, top_surface_pattern))
((ConfigOptionEnum<InfillPattern>, bottom_surface_pattern)) ((ConfigOptionEnum<InfillPattern>, bottom_surface_pattern))
((ConfigOptionEnum<SurfaceFillOrder>, top_surface_fill_order))
((ConfigOptionEnum<SurfaceFillOrder>, bottom_surface_fill_order))
((ConfigOptionEnum<InfillPattern>, internal_solid_infill_pattern)) ((ConfigOptionEnum<InfillPattern>, internal_solid_infill_pattern))
((ConfigOptionFloatOrPercent, outer_wall_line_width)) ((ConfigOptionFloatOrPercent, outer_wall_line_width))
((ConfigOptionFloatsNullable, outer_wall_speed)) ((ConfigOptionFloatsNullable, outer_wall_speed))
@@ -1257,7 +1266,6 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionFloat, lightning_prune_angle)) ((ConfigOptionFloat, lightning_prune_angle))
((ConfigOptionFloat, lightning_straightening_angle)) ((ConfigOptionFloat, lightning_straightening_angle))
((ConfigOptionBool, align_infill_direction_to_model)) ((ConfigOptionBool, align_infill_direction_to_model))
((ConfigOptionBool, anisotropic_surfaces))
((ConfigOptionEnum<CenterOfSurfacePattern>, center_of_surface_pattern)) ((ConfigOptionEnum<CenterOfSurfacePattern>, center_of_surface_pattern))
((ConfigOptionBool, separated_infills)) ((ConfigOptionBool, separated_infills))
((ConfigOptionString, extra_solid_infills)) ((ConfigOptionString, extra_solid_infills))
@@ -1544,6 +1552,9 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionPercents, retract_before_wipe)) ((ConfigOptionPercents, retract_before_wipe))
// Orca
((ConfigOptionPercents, retract_after_wipe))
((ConfigOptionFloats, retraction_length)) ((ConfigOptionFloats, retraction_length))
((ConfigOptionFloats, retract_length_toolchange)) ((ConfigOptionFloats, retract_length_toolchange))
((ConfigOptionInt, enable_long_retraction_when_cut)) ((ConfigOptionInt, enable_long_retraction_when_cut))
+2 -1
View File
@@ -1393,6 +1393,8 @@ bool PrintObject::invalidate_state_by_config_options(
} else if ( } else if (
opt_key == "top_surface_pattern" opt_key == "top_surface_pattern"
|| opt_key == "bottom_surface_pattern" || opt_key == "bottom_surface_pattern"
|| opt_key == "top_surface_fill_order"
|| opt_key == "bottom_surface_fill_order"
|| opt_key == "internal_solid_infill_pattern" || opt_key == "internal_solid_infill_pattern"
|| opt_key == "external_fill_link_max_length" || opt_key == "external_fill_link_max_length"
|| opt_key == "infill_anchor" || opt_key == "infill_anchor"
@@ -1400,7 +1402,6 @@ bool PrintObject::invalidate_state_by_config_options(
|| opt_key == "top_surface_line_width" || opt_key == "top_surface_line_width"
|| opt_key == "top_surface_density" || opt_key == "top_surface_density"
|| opt_key == "bottom_surface_density" || opt_key == "bottom_surface_density"
|| opt_key == "anisotropic_surfaces"
|| opt_key == "center_of_surface_pattern" || opt_key == "center_of_surface_pattern"
|| opt_key == "separated_infills" || opt_key == "separated_infills"
|| opt_key == "initial_layer_line_width" || opt_key == "initial_layer_line_width"
@@ -22,7 +22,7 @@ struct Params
: /*max_acceleration(max_acceleration), */raft_layers_count(raft_layers_count), brim_type(brim_type), brim_width(brim_width) : /*max_acceleration(max_acceleration), */raft_layers_count(raft_layers_count), brim_type(brim_type), brim_width(brim_width)
{ {
if (filament_types.size() > 1) { 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"; << "SupportSpotsGenerator does not currently handle different materials properly, only first will be used";
} }
if (filament_types.empty() || filament_types[0].empty()) { if (filament_types.empty() || filament_types[0].empty()) {
+6 -6
View File
@@ -721,7 +721,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
toggle_field("top_surface_expansion_direction", has_top_surface_expansion); toggle_field("top_surface_expansion_direction", has_top_surface_expansion);
// Orca: Archimedean Chords and Octagram Spiral are the centered surface patterns that the // Orca: Archimedean Chords and Octagram Spiral are the centered surface patterns that the
// pattern-centering, anisotropic-surface and separated-infill features act on. // pattern-centering feature acts on.
auto is_centered_pattern = [](InfillPattern p) { auto is_centered_pattern = [](InfillPattern p) {
return p == InfillPattern::ipArchimedeanChords || p == InfillPattern::ipOctagramSpiral; return p == InfillPattern::ipArchimedeanChords || p == InfillPattern::ipOctagramSpiral;
}; };
@@ -729,9 +729,8 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
bool is_bottom_centered = is_centered_pattern(config->option<ConfigOptionEnum<InfillPattern>>("bottom_surface_pattern")->value); bool is_bottom_centered = is_centered_pattern(config->option<ConfigOptionEnum<InfillPattern>>("bottom_surface_pattern")->value);
bool has_centered_surface = (has_top_shell && is_top_centered) || (has_bottom_shell && is_bottom_centered); bool has_centered_surface = (has_top_shell && is_top_centered) || (has_bottom_shell && is_bottom_centered);
// Orca: center of surface pattern / anisotropic surfaces // Orca: center of surface pattern
toggle_line("center_of_surface_pattern", has_centered_surface); toggle_line("center_of_surface_pattern", has_centered_surface);
toggle_line("anisotropic_surfaces", has_centered_surface);
// Orca: separate infills // Orca: separate infills
bool is_internal_infill_separable = is_separable_infill_pattern(config->option<ConfigOptionEnum<InfillPattern>>("sparse_infill_pattern")->value) || bool is_internal_infill_separable = is_separable_infill_pattern(config->option<ConfigOptionEnum<InfillPattern>>("sparse_infill_pattern")->value) ||
@@ -739,9 +738,10 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
config->opt_string("solid_infill_rotate_template") != ""; config->opt_string("solid_infill_rotate_template") != "";
toggle_line("separated_infills", is_internal_infill_separable); toggle_line("separated_infills", is_internal_infill_separable);
// Orca: no need gaps // Fill order is only meaningful for the center-based surface fill patterns; hide it otherwise.
for (auto el : {"gap_fill_target", "filter_out_gap_fill"}) auto is_centered_fill = [](InfillPattern p) { return p == ipConcentric || p == ipArchimedeanChords || p == ipOctagramSpiral; };
toggle_field(el, !config->opt_bool("anisotropic_surfaces")); toggle_line("top_surface_fill_order", has_top_shell && is_centered_fill(config->opt_enum<InfillPattern>("top_surface_pattern")));
toggle_line("bottom_surface_fill_order", has_bottom_shell && is_centered_fill(config->opt_enum<InfillPattern>("bottom_surface_pattern")));
for (auto el : { "infill_direction", "sparse_infill_line_width", "gap_fill_target","filter_out_gap_fill","infill_wall_overlap", for (auto el : { "infill_direction", "sparse_infill_line_width", "gap_fill_target","filter_out_gap_fill","infill_wall_overlap",
"bridge_angle", "internal_bridge_angle", "relative_bridge_angle", "bridge_angle", "internal_bridge_angle", "relative_bridge_angle",
+1
View File
@@ -353,6 +353,7 @@ bool Mouse3DController::State::apply(const Mouse3DController::Params &params, Ca
rot = Vec3d(rot.x(), -rot.z(), rot.y()); rot = Vec3d(rot.x(), -rot.z(), rot.y());
rot = Vec3d(rot.x() * pitchmult, rot.y() * yawmult, rot.z() * rollmult); rot = Vec3d(rot.x() * pitchmult, rot.y() * yawmult, rot.z() * rollmult);
camera.rotate_local_around_target(Vec3d(rot.x(), - rot.z(), rot.y())); camera.rotate_local_around_target(Vec3d(rot.x(), - rot.z(), rot.y()));
camera.auto_type(Camera::EType::Perspective);
} else { } else {
assert(input_queue_item.is_buttons()); assert(input_queue_item.is_buttons());
switch (input_queue_item.type_or_buttons) { switch (input_queue_item.type_or_buttons) {
+2 -2
View File
@@ -13904,7 +13904,6 @@ void adjust_settings_for_flowrate_calib(ModelObjectPtrs& objects, bool linear, i
_obj->config.set_key_value("top_solid_infill_flow_ratio", new ConfigOptionFloat(1.0f)); _obj->config.set_key_value("top_solid_infill_flow_ratio", new ConfigOptionFloat(1.0f));
_obj->config.set_key_value("infill_direction", new ConfigOptionFloat(45)); _obj->config.set_key_value("infill_direction", new ConfigOptionFloat(45));
_obj->config.set_key_value("solid_infill_direction", new ConfigOptionFloat(135)); _obj->config.set_key_value("solid_infill_direction", new ConfigOptionFloat(135));
_obj->config.set_key_value("anisotropic_surfaces", new ConfigOptionBool(false));
_obj->config.set_key_value("center_of_surface_pattern", new ConfigOptionEnum<CenterOfSurfacePattern>(CenterOfSurfacePattern::Each_Surface)); _obj->config.set_key_value("center_of_surface_pattern", new ConfigOptionEnum<CenterOfSurfacePattern>(CenterOfSurfacePattern::Each_Surface));
_obj->config.set_key_value("separated_infills", new ConfigOptionBool(false)); _obj->config.set_key_value("separated_infills", new ConfigOptionBool(false));
_obj->config.set_key_value("align_infill_direction_to_model", new ConfigOptionBool(true)); _obj->config.set_key_value("align_infill_direction_to_model", new ConfigOptionBool(true));
@@ -13914,7 +13913,8 @@ void adjust_settings_for_flowrate_calib(ModelObjectPtrs& objects, bool linear, i
_obj->config.set_key_value("seam_slope_type", new ConfigOptionEnum<SeamScarfType>(SeamScarfType::None)); _obj->config.set_key_value("seam_slope_type", new ConfigOptionEnum<SeamScarfType>(SeamScarfType::None));
_obj->config.set_key_value("gap_fill_target", new ConfigOptionEnum<GapFillTarget>(GapFillTarget::gftNowhere)); _obj->config.set_key_value("gap_fill_target", new ConfigOptionEnum<GapFillTarget>(GapFillTarget::gftNowhere));
print_config->set_key_value("max_volumetric_extrusion_rate_slope", new ConfigOptionFloat(0)); print_config->set_key_value("max_volumetric_extrusion_rate_slope", new ConfigOptionFloat(0));
_obj->config.set_key_value("calib_flowrate_topinfill_special_order", new ConfigOptionBool(true)); // ORCA: print the top surface spiral from the center outwards, so the tiles are comparable.
_obj->config.set_key_value("top_surface_fill_order", new ConfigOptionEnum<SurfaceFillOrder>(SurfaceFillOrder::Outward));
// extract flowrate from name, filename format: flowrate_xxx // extract flowrate from name, filename format: flowrate_xxx
std::string obj_name = _obj->name; std::string obj_name = _obj->name;
+136 -6
View File
@@ -1772,6 +1772,10 @@ static wxString pad_combo_value_for_config(const DynamicPrintConfig &config)
void Tab::on_value_change(const std::string& opt_key, const boost::any& value) void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
{ {
// Orca:
// TODO: Move filament-specific checks to TabFilament::on_value_change()
// TODO: Move printer-specific checks to TabPrinter::on_value_change()
if (wxGetApp().plater() == nullptr) { if (wxGetApp().plater() == nullptr) {
return; return;
} }
@@ -2762,6 +2766,7 @@ void TabPrint::build()
optgroup->append_single_option_line("top_shell_thickness", "strength_settings_top_bottom_shells#shell-thickness"); optgroup->append_single_option_line("top_shell_thickness", "strength_settings_top_bottom_shells#shell-thickness");
optgroup->append_single_option_line("top_surface_density", "strength_settings_top_bottom_shells#surface-density"); optgroup->append_single_option_line("top_surface_density", "strength_settings_top_bottom_shells#surface-density");
optgroup->append_single_option_line("top_surface_pattern", "strength_settings_top_bottom_shells#surface-pattern"); optgroup->append_single_option_line("top_surface_pattern", "strength_settings_top_bottom_shells#surface-pattern");
optgroup->append_single_option_line("top_surface_fill_order", "strength_settings_top_bottom_shells#fill-order");
optgroup->append_single_option_line("top_layer_direction", "strength_settings_infill#top-bottom-direction"); optgroup->append_single_option_line("top_layer_direction", "strength_settings_infill#top-bottom-direction");
optgroup->append_single_option_line("top_surface_expansion", "strength_settings_top_bottom_shells#surface-expansion"); optgroup->append_single_option_line("top_surface_expansion", "strength_settings_top_bottom_shells#surface-expansion");
optgroup->append_single_option_line("top_surface_expansion_margin", "strength_settings_top_bottom_shells#surface-expansion-margin"); optgroup->append_single_option_line("top_surface_expansion_margin", "strength_settings_top_bottom_shells#surface-expansion-margin");
@@ -2770,9 +2775,9 @@ void TabPrint::build()
optgroup->append_single_option_line("bottom_shell_thickness", "strength_settings_top_bottom_shells#shell-thickness"); optgroup->append_single_option_line("bottom_shell_thickness", "strength_settings_top_bottom_shells#shell-thickness");
optgroup->append_single_option_line("bottom_surface_density", "strength_settings_top_bottom_shells#surface-density"); optgroup->append_single_option_line("bottom_surface_density", "strength_settings_top_bottom_shells#surface-density");
optgroup->append_single_option_line("bottom_surface_pattern", "strength_settings_top_bottom_shells#surface-pattern"); optgroup->append_single_option_line("bottom_surface_pattern", "strength_settings_top_bottom_shells#surface-pattern");
optgroup->append_single_option_line("bottom_surface_fill_order", "strength_settings_top_bottom_shells#fill-order");
optgroup->append_single_option_line("bottom_layer_direction", "strength_settings_infill#top-bottom-direction"); optgroup->append_single_option_line("bottom_layer_direction", "strength_settings_infill#top-bottom-direction");
optgroup->append_single_option_line("center_of_surface_pattern", "strength_settings_top_bottom_shells#center-surface-pattern-on"); optgroup->append_single_option_line("center_of_surface_pattern", "strength_settings_top_bottom_shells#center-surface-pattern-on");
optgroup->append_single_option_line("anisotropic_surfaces", "strength_settings_top_bottom_shells#anisotropic-surfaces");
optgroup->append_single_option_line("top_bottom_infill_wall_overlap", "strength_settings_top_bottom_shells#infillwall-overlap"); optgroup->append_single_option_line("top_bottom_infill_wall_overlap", "strength_settings_top_bottom_shells#infillwall-overlap");
optgroup = page->new_optgroup(L("Infill"), L"param_infill"); optgroup = page->new_optgroup(L("Infill"), L"param_infill");
@@ -3989,6 +3994,9 @@ void TabFilament::add_filament_overrides_page()
// BBS // BBS
"filament_wipe_distance", "filament_wipe_distance",
"filament_retract_before_wipe", "filament_retract_before_wipe",
// Orca
"filament_retract_after_wipe",
// BBS
"filament_long_retractions_when_cut", "filament_long_retractions_when_cut",
"filament_retraction_distances_when_cut" "filament_retraction_distances_when_cut"
//SoftFever //SoftFever
@@ -4115,6 +4123,9 @@ void TabFilament::update_filament_overrides_page(const DynamicPrintConfig* print
// BBS // BBS
"filament_wipe_distance", "filament_wipe_distance",
"filament_retract_before_wipe", "filament_retract_before_wipe",
// Orca
"filament_retract_after_wipe",
// BBS
"filament_long_retractions_when_cut", "filament_long_retractions_when_cut",
"filament_retraction_distances_when_cut" "filament_retraction_distances_when_cut"
//SoftFever //SoftFever
@@ -4766,6 +4777,69 @@ void TabFilament::clear_pages()
m_overrides_options.clear(); m_overrides_options.clear();
} }
// Orca:
void TabFilament::on_value_change(const std::string& opt_key, const boost::any& value)
{
if (wxGetApp().plater() == nullptr || m_config_manipulation.is_applying())
return;
const int pos = opt_key.find("#");
if (pos > 0) {
std::string temp_str = opt_key;
boost::erase_head(temp_str, pos + 1);
int orig_opt_idx = static_cast<size_t>(atoi(temp_str.c_str()));
int opt_idx = orig_opt_idx >= 0 ? orig_opt_idx : 0;
std::string opt_key_pure = opt_key;
boost::erase_tail(opt_key_pure, opt_key_pure.size() - pos);
if (opt_key_pure == "filament_retract_after_wipe" || opt_key_pure == "filament_retract_before_wipe") {
double dvalue = boost::any_cast<double>(value);
auto percent_value_clamp = [](double percent_value) { return std::clamp(percent_value, 0., 100.); };
auto get_value_by_opt_key = [&](const std::string& opt_key) {
if (opt_key_pure == opt_key && !std::isnan(dvalue)) {
// Return the incoming value (if it is valid) if the opt_key equals the key of the changed option.
return percent_value_clamp(dvalue);
} else if (!m_config->option<ConfigOptionPercents>(opt_key)->is_nil(opt_idx)) {
// Return the overridden value if the option value for the opt_key was overridden for the given filament.
return percent_value_clamp(m_config->option<ConfigOptionPercents>(opt_key)->get_at(opt_idx));
} else {
// Return the value of the option value for opt_key from the printer setting if it was not overridden for the filament.
const auto& printer_config = m_preset_bundle->printers.get_edited_preset().config;
const std::string printer_opt_key = opt_key.substr(strlen("filament_"));
return percent_value_clamp(printer_config.option<ConfigOptionPercents>(printer_opt_key)->get_at(opt_idx));
}
return 0.;
};
double retract_before_wipe = get_value_by_opt_key("filament_retract_before_wipe");
double retract_after_wipe = get_value_by_opt_key("filament_retract_after_wipe");
bool need_to_reload_config = false;
if (percent_value_clamp(dvalue) != dvalue) {
change_opt_value(*m_config, opt_key_pure, percent_value_clamp(dvalue), opt_idx);
need_to_reload_config = true;
}
if (retract_after_wipe > 100. - retract_before_wipe) {
change_opt_value(*m_config, "filament_retract_after_wipe", 100. - retract_before_wipe, opt_idx);
need_to_reload_config = true;
}
if (need_to_reload_config && !m_postpone_update_ui) {
update_dirty();
reload_config();
update_tab_ui();
}
}
}
Tab::on_value_change(opt_key, value);
}
wxSizer* Tab::description_line_widget(wxWindow* parent, ogStaticText* *StaticText, wxString text /*= wxEmptyString*/) wxSizer* Tab::description_line_widget(wxWindow* parent, ogStaticText* *StaticText, wxString text /*= wxEmptyString*/)
{ {
*StaticText = new ogStaticText(parent, text); *StaticText = new ogStaticText(parent, text);
@@ -5530,6 +5604,8 @@ if (is_marlin_flavor)
optgroup->append_single_option_line("wipe", "printer_extruder_retraction#wipe-while-retracting", extruder_idx); optgroup->append_single_option_line("wipe", "printer_extruder_retraction#wipe-while-retracting", extruder_idx);
optgroup->append_single_option_line("wipe_distance", "printer_extruder_retraction#wipe-distance", extruder_idx); optgroup->append_single_option_line("wipe_distance", "printer_extruder_retraction#wipe-distance", extruder_idx);
optgroup->append_single_option_line("retract_before_wipe", "printer_extruder_retraction#retract-amount-before-wipe", extruder_idx); optgroup->append_single_option_line("retract_before_wipe", "printer_extruder_retraction#retract-amount-before-wipe", extruder_idx);
// Orca
optgroup->append_single_option_line("retract_after_wipe", "printer_extruder_retraction#retract-amount-after-wipe", extruder_idx);
optgroup = page->new_optgroup(L("Z-Hop"), L"param_extruder_lift_enforcement"); optgroup = page->new_optgroup(L("Z-Hop"), L"param_extruder_lift_enforcement");
optgroup->append_single_option_line("retract_lift_enforce", "printer_extruder_z_hop#on-surfaces", extruder_idx); optgroup->append_single_option_line("retract_lift_enforce", "printer_extruder_z_hop#on-surfaces", extruder_idx);
@@ -5989,16 +6065,20 @@ void TabPrinter::toggle_options()
// some options only apply when not using firmware retraction // some options only apply when not using firmware retraction
vec.resize(0); vec.resize(0);
vec = {"retraction_speed", "deretraction_speed", "retract_before_wipe", vec = {"retraction_speed", "deretraction_speed", "retract_before_wipe", "retract_after_wipe",
"retract_length", "retract_restart_extra", "retract_length", "retract_restart_extra", "wipe_distance"};
"wipe_distance"};
for (auto el : vec) for (auto el : vec)
//BBS //BBS
toggle_option(el, retraction && !use_firmware_retraction, i); toggle_option(el, retraction && !use_firmware_retraction, i);
bool wipe = retraction && m_config->opt_bool("wipe", variant_index); bool wipe = retraction && m_config->opt_bool("wipe", variant_index);
toggle_option("retract_before_wipe", wipe, i);
float retract_before_wipe = static_cast<ConfigOptionPercents*>(m_config->option("retract_before_wipe"))->values[variant_index]; // Orca:
double retract_before_wipe = m_config->option<ConfigOptionPercents>("retract_before_wipe")->get_at(variant_index);
double retract_after_wipe = m_config->option<ConfigOptionPercents>("retract_after_wipe")->get_at(variant_index);
toggle_option("retract_before_wipe", wipe && !is_approx(retract_after_wipe, 100.), i);
toggle_option("retract_after_wipe", wipe && !is_approx(retract_before_wipe, 100.), i);
if (use_firmware_retraction && wipe && retract_before_wipe < 100.0) { if (use_firmware_retraction && wipe && retract_before_wipe < 100.0) {
//wxMessageDialog dialog(parent(), //wxMessageDialog dialog(parent(),
@@ -6101,6 +6181,56 @@ void TabPrinter::toggle_options()
} }
} }
// Orca:
void TabPrinter::on_value_change(const std::string& opt_key, const boost::any& value)
{
if (wxGetApp().plater() == nullptr || m_config_manipulation.is_applying())
return;
const int pos = opt_key.find("#");
if (pos > 0) {
std::string temp_str = opt_key;
boost::erase_head(temp_str, pos + 1);
int orig_opt_idx = static_cast<size_t>(atoi(temp_str.c_str()));
int opt_idx = orig_opt_idx >= 0 ? orig_opt_idx : 0;
std::string opt_key_pure = opt_key;
boost::erase_tail(opt_key_pure, opt_key_pure.size() - pos);
if (opt_key_pure == "retract_after_wipe" || opt_key_pure == "retract_before_wipe") {
auto percent_value_clamp = [](double percent_value) { return std::clamp(percent_value, 0., 100.); };
double dvalue = boost::any_cast<double>(value);
double retract_before_wipe = percent_value_clamp(opt_key_pure == "retract_before_wipe"
? dvalue : m_config->option<ConfigOptionPercents>("retract_before_wipe")->get_at(opt_idx));
double retract_after_wipe = percent_value_clamp(opt_key_pure == "retract_after_wipe"
? dvalue : m_config->option<ConfigOptionPercents>("retract_after_wipe")->get_at(opt_idx));
bool need_to_reload_config = false;
if (percent_value_clamp(dvalue) != dvalue) {
change_opt_value(*m_config, opt_key_pure, percent_value_clamp(dvalue), opt_idx);
need_to_reload_config = true;
}
if (retract_after_wipe > 100. - retract_before_wipe) {
change_opt_value(*m_config, "retract_after_wipe", 100. - retract_before_wipe, opt_idx);
need_to_reload_config = true;
}
if (need_to_reload_config && !m_postpone_update_ui) {
update_dirty();
reload_config();
update_tab_ui();
}
}
}
Tab::on_value_change(opt_key, value);
}
void TabPrinter::update() void TabPrinter::update()
{ {
m_update_cnt++; m_update_cnt++;
+3 -1
View File
@@ -608,6 +608,8 @@ public:
void clear_pages() override; void clear_pages() override;
bool supports_printer_technology(const PrinterTechnology tech) const override { return tech == ptFFF; } bool supports_printer_technology(const PrinterTechnology tech) const override { return tech == ptFFF; }
void on_value_change(const std::string& opt_key, const boost::any& value) override;
const std::string& get_custom_gcode(const t_config_option_key& opt_key) override; const std::string& get_custom_gcode(const t_config_option_key& opt_key) override;
void set_custom_gcode(const t_config_option_key& opt_key, const std::string& value) override; void set_custom_gcode(const t_config_option_key& opt_key, const std::string& value) override;
}; };
@@ -666,11 +668,11 @@ public:
bool supports_printer_technology(const PrinterTechnology /* tech */) const override { return true; } bool supports_printer_technology(const PrinterTechnology /* tech */) const override { return true; }
void set_extruder_volume_type(int extruder_id, NozzleVolumeType type); void set_extruder_volume_type(int extruder_id, NozzleVolumeType type);
void on_value_change(const std::string& opt_key, const boost::any& value) override;
wxSizer* create_bed_shape_widget(wxWindow* parent); wxSizer* create_bed_shape_widget(wxWindow* parent);
void cache_extruder_cnt(const DynamicPrintConfig* config = nullptr); void cache_extruder_cnt(const DynamicPrintConfig* config = nullptr);
bool apply_extruder_cnt_from_cache(); bool apply_extruder_cnt_from_cache();
}; };
class TabSLAMaterial : public Tab class TabSLAMaterial : public Tab
+5 -1
View File
@@ -13,13 +13,17 @@ endif()
set(TEST_DATA_DIR ${CMAKE_CURRENT_SOURCE_DIR}/data) set(TEST_DATA_DIR ${CMAKE_CURRENT_SOURCE_DIR}/data)
file(TO_NATIVE_PATH "${TEST_DATA_DIR}" TEST_DATA_DIR) 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(CTest)
include(Catch) 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. 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) 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}) target_include_directories(test_common INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
if (APPLE) if (APPLE)
+140
View File
@@ -12,6 +12,8 @@
#include "libslic3r/Print.hpp" #include "libslic3r/Print.hpp"
#include "libslic3r/ModelArrange.hpp" #include "libslic3r/ModelArrange.hpp"
#include <boost/filesystem.hpp>
#include "test_helpers.hpp" #include "test_helpers.hpp"
using namespace Slic3r; 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"));
}
}
}
+3 -1
View File
@@ -210,13 +210,15 @@ inline FullEvalResult full_evaluate_map(const FilamentGroupContext& ctx,
return result; return result;
} }
inline TestResult run_and_evaluate(const FilamentGroupContext& ctx) { inline TestResult run_and_evaluate(const FilamentGroupContext& ctx,
const ClusteringBudget& budget = {}) {
TestResult result; TestResult result;
auto start = std::chrono::high_resolution_clock::now(); auto start = std::chrono::high_resolution_clock::now();
int algo_cost = 0; int algo_cost = 0;
FilamentGroup fg(ctx); FilamentGroup fg(ctx);
fg.set_clustering_budget(budget);
result.filament_map = fg.calc_filament_group(&algo_cost); result.filament_map = fg.calc_filament_group(&algo_cost);
auto end = std::chrono::high_resolution_clock::now(); auto end = std::chrono::high_resolution_clock::now();
@@ -211,19 +211,23 @@ static std::vector<PropertySpec>& get_property_specs() {
return specs; return specs;
} }
// Orca: a small number of config_c "stress" goldens run the nozzle-centric kmedoids clustering path, // Under the default wall clock the result depends on how fast the machine is (see ClusteringBudget),
// which is bounded by a 3000 ms wall-clock budget (FilamentGroup.cpp calc_group_by_kmedoids). On // so the goldens are graded under a fixed budget instead. Two restarts is the fewest that reaches
// slower hardware the clustering explores fewer restarts and lands on a deterministically-worse-but- // parity with the reference on every golden, stress_79 being the last to get there. Four leaves
// valid grouping than the stored golden. We regression-lock those against Orca's own deterministic // margin, since the search follows a different path on each standard library (see below).
// score (bit-stable across runs on this machine — verified twice) so the gate stays green while the static constexpr ClusteringBudget FIXED_SEARCH_BUDGET{
// divergence is documented; every other golden is a true parity gate at 3% tolerance. /*timeout_ms*/ 0, // no wall clock
static std::optional<double> orca_locked_base_score(const std::string& stem) { /*max_restarts*/ 4};
if (stem == "stress_66") return 125103.0; // config_c 15-filament kmedoids case; golden 117843
return std::nullopt;
}
// ============ Layer 1: Golden Regression (all configs) ============ // ============ Layer 1: Golden Regression (all configs) ============
// Graded against the BambuStudio golden the harness was ported from, one-directional at 3%.
//
// The tolerance is a parity allowance, and it also covers a small spread across standard libraries.
// The k-medoids search seeds each restart with std::shuffle, whose algorithm the C++ standard leaves
// unspecified, so libstdc++, libc++ and the MSVC STL permute the same seed differently, start from
// different medoids, and settle on slightly different groupings, about 3e-4 apart on either side of
// the reference, and only on the goldens heavy enough to reach the k-medoids search.
TEST_CASE("FilamentGroup golden regression", "[filament_group][golden]") { TEST_CASE("FilamentGroup golden regression", "[filament_group][golden]") {
auto files = get_golden_files(); auto files = get_golden_files();
if (files.empty()) { if (files.empty()) {
@@ -238,29 +242,49 @@ TEST_CASE("FilamentGroup golden regression", "[filament_group][golden]") {
auto tc = load_test_case(file_path); auto tc = load_test_case(file_path);
REQUIRE(tc.base_result.has_value()); REQUIRE(tc.base_result.has_value());
auto result = run_and_evaluate(tc.context); auto result = run_and_evaluate(tc.context, FIXED_SEARCH_BUDGET);
auto eval = full_evaluate_map(tc.context, result.filament_map); auto eval = full_evaluate_map(tc.context, result.filament_map);
auto& base = *tc.base_result; auto& base = *tc.base_result;
// Reference score: the stored golden by default; Orca's deterministic score for the
// documented heuristic-divergent config_c stress golden (see orca_locked_base_score).
std::string stem = fs::path(file_path).stem().string();
double base_score = base.full_score;
if (auto locked = orca_locked_base_score(stem))
base_score = *locked;
INFO("Case: " << tc.metadata.id); INFO("Case: " << tc.metadata.id);
INFO("Reference score: " << base_score << " (BBS golden " << base.full_score << ")"); INFO("Golden score: " << base.full_score);
INFO("Actual score: " << eval.full_score); INFO("Actual score: " << eval.full_score);
INFO("Flush cost: " << eval.flush_cost << " (BBS golden " << base.flush_cost << ")"); INFO("Flush cost: " << eval.flush_cost << " (golden " << base.flush_cost << ")");
INFO("Elapsed: " << result.elapsed_ms << " ms"); INFO("Elapsed: " << result.elapsed_ms << " ms");
int tolerance = std::max(50, (int)(base_score * 0.03)); int tolerance = std::max(50, (int)(base.full_score * 0.03));
REQUIRE(result.constraints_ok); REQUIRE(result.constraints_ok);
REQUIRE(eval.full_score <= base_score + tolerance); REQUIRE(eval.full_score <= base.full_score + tolerance);
// RelWithDebInfo runaway guard; the Release-calibrated 20 s limit is raised for the slower build.
// A slower search still scores the same above, since it searches just as far, but in slicing
// it would mean fewer restarts fit in the wall clock and so worse groupings. Loose on
// purpose, so it never becomes a proxy for how loaded the runner is.
const double throughput_ceiling_ms = 10.0 * ClusteringBudget{}.timeout_ms;
REQUIRE(result.elapsed_ms < throughput_ceiling_ms);
}
}
// Covers the path real slicing takes, under the default wall clock. The score there depends on the
// runner rather than on the code (see FIXED_SEARCH_BUDGET), so the only things worth asserting are
// that the grouping comes back valid and that the search terminates.
TEST_CASE("FilamentGroup returns a valid grouping under the default budget", "[filament_group][budget]") {
auto files = get_golden_files();
REQUIRE(!files.empty());
auto file_path = GENERATE_REF(from_range(files));
DYNAMIC_SECTION("Golden: " << fs::path(file_path).stem().string()) {
auto tc = load_test_case(file_path);
auto result = run_and_evaluate(tc.context); // the default budget, as real slicing runs it
INFO("Case: " << tc.metadata.id);
INFO("Elapsed: " << result.elapsed_ms << " ms");
REQUIRE(result.constraints_ok);
// A hang guard. The clock is only checked between swaps, so a sweep can overshoot.
REQUIRE(result.elapsed_ms < 40000.0); REQUIRE(result.elapsed_ms < 40000.0);
} }
} }