Compare commits

...
Author SHA1 Message Date
Hanif Koh c0822cfaf5 Free macOS Disk Space Only When the Build Might Not Fit 2026-10-11 18:19:23 +08:00
HanifKoh 2df07b7ed8 Verify the TLS Certificate When Downloading a Cloud Plugin (#16253) 2026-10-11 14:25:50 +08:00
Klober81andRodrigo Faselli 295094a594 Pack bed texture and model into printer bundles (#15965)
* feat(preset): pack bed texture and model into printer bundles

Export copies bed_custom_texture and bed_custom_model into the .orca_printer zip under bed/. The manifest lists them in printer_asset. Import extracts each file into the bundle folder and rewrites the preset to that path. A missing file is skipped. A path that leaves the bundle directory is rejected. An old bundle with no printer_asset imports as before.

* Add includes flagged by clang-tidy misc-include-cleaner

* Gate bed asset packing on an export checkbox and key printer_asset per printer

The Export Configs checkbox can leave a custom bed out of a shared printer bundle. printer_asset is keyed by each printer_config zip entry, with files under bed/<n>/, so a later multi-printer bundle does not need a second manifest. The unshipped flat printer_asset object is not read.

---------

Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
2026-10-11 13:32:07 +08:00
Ian Bassi 210bc927bc Revert "Add Thai Localization (Educational / Hybrid Translation)" (#16349) 2026-10-10 22:39:45 -03:00
SoftFever 00cfb5b259 Refresh translation catalogs and fix xgettext warnings (#16346) 2026-10-11 08:45:44 +08:00
chettasit 2bd105b0ce Add Thai Localization (Educational / Hybrid Translation) (#15940)
Hi OrcaSlicer Team,I have completed the Thai localization for OrcaSlicer (over 7,000 lines).Please note: This is a "Hybrid Translation" designed specifically for educational purposes. Instead of direct literal translations (which often confuse beginners due to technical jargon), I have included brief explanations of the mechanical and physics principles directly within the UI strings.Example: For "TPU is not supported by AMS", the translation briefly explains why (because the material is soft, rubber-like, and will jam the long tubes).This approach was developed for the EDrobot project in Khon Kaen, Thailand to help STEM students and beginners understand 3D printing concepts immediately without a steep language barrier.[cite: 8] We focus on empowering students to learn robotics and 3D design effectively.[cite: 8]   I understand this makes some strings longer than standard localizations, but it has proven incredibly helpful for beginners. I would be honored if you consider adding this to the official release as the Thai language option. I am happy to maintain this file for future updates.Thank you for building such an amazing slicer!Best regards,Chetsit Srichan (EDrobot)
2026-10-11 08:34:19 +08:00
Ian BassiandRodrigo Faselli b81d761605 Refresh plate tab after bed type change (#16344)
Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
2026-10-10 21:06:29 -03:00
6922edfc20 Fix Anycubic Vyper start G-code hotend wait (#16216)
* Fix Anycubic Vyper start gcode hotend wait

* Bump Anycubic profile version

---------

Co-authored-by: 陈诗健 <chenshijian@chenshijiandeMacBook-Air.local>
Co-authored-by: SoftFever <softfeverever@gmail.com>
Co-authored-by: SoftFever <103989404+SoftFever@users.noreply.github.com>
2026-10-11 07:34:11 +08:00
Javad Shafique 7ec147603f Default Bambu profiles to 3MF output (#14767) 2026-10-11 06:55:50 +08:00
jorymorrison 8ee9e5915a Fix Prusa CORE One pressure advance condition in filament profiles (#16093) 2026-10-11 06:39:53 +08:00
SoftFever cd5b786704 Add AnkerMake PLA+ filament profiles (Basic, Matte, Metallic, Silk, Glitter) (#16293)
# Description

Adds Anker's own AnkerMake PLA+ filament line to the Anker vendor
bundle: **Basic, Matte, Metallic, Silk, Glitter**. Until now the bundle
only had `Generic PLA+ @Anker`.

Each filament follows the existing Anker layout. A `@Anker base` profile
(`instantiation: false`, inherits `Generic PLA+ @Anker base`,
`filament_vendor: AnkerMake`) holds the settings. An instantiated
`@Anker` profile holds only `compatible_printers`. `filament_id` and
`setting_id` were minted with `scripts/orca_profile_tool.py
generate-id`, and `Anker.json` was re-indexed with `update-index`. The
Anker vendor version goes from `02.04.00.05` to `02.04.00.06`.

## Source of the values

The values come from the AnkerMake filament profiles bundled with
**eufyMake Studio** (Anker's PrusaSlicer 2.6 fork, AGPL-3.0, source at
https://github.com/eufymake/eufyMake-PrusaSlicer-Release), in
`profiles/Anker-ini/AnkerMake base/base.ini`. OrcaSlicer is also
AGPL-3.0.

Conversion notes:
- PrusaSlicer keys are mapped to their Orca equivalents. For example,
`bed_temperature`/`first_layer_bed_temperature` go to `hot_plate_temp*`
and `textured_plate_temp*`, `fan_below_layer_time` goes to
`fan_cooling_layer_time`, `slowdown_below_layer_time` goes to
`slow_down_layer_time`, and `extrusion_multiplier` goes to
`filament_flow_ratio`. Only keys that differ from the inherited `Generic
PLA+ @Anker base` chain are written.
- Where Studio sets retraction but leaves deretraction unset (Metallic,
Silk), `filament_deretraction_speed` is set equal to the retraction
speed. That matches Prusa's "0 = same as retract" meaning.
- **Max volumetric speed is new.** Studio ships
`filament_max_volumetric_speed = 0` (uncapped) for these filaments, and
Orca has no equivalent. The caps were derived from the peak flow that
Studio's own M5C "Fast" process pushes: Basic/Matte/Glitter 22 mm³/s,
Metallic 20, Silk 13. Please treat these as the least-validated numbers
in the PR.
- Studio has pressure advance disabled for these filaments, so no PA
values are carried over.
- Colour variants are not included because they differ only in colour.
`default_filament_colour` uses each family's base colour.
- `compatible_printers` follows Studio's `compatible_printers_condition`
for each family, limited to the 0.4/0.6 printers that `Generic PLA+
@Anker` covers:
  - Basic, Glitter: M5, M5 All-Metal, M5C at 0.4 and 0.6
- Silk: 0.4 on all three, plus M5C 0.6 and M5 All-Metal 0.6 (Studio
excludes M5 0.6)
  - Matte, Metallic: 0.4 only (Studio restricts these to 0.4)
- Studio also allows Basic on 0.2 nozzles. That variant is not added,
because the 0.2 Generic profiles use a separate low-flow cap.

# Screenshots/Recordings/Graphs

N/A (profile-only change).

## Tests

- `scripts/check_profile.sh` (local twin of the CI job, run against the
full profile tree with the nightly macOS validator) passes all five
checks: `profile_tool`, `validate_system`, `validate_slice`,
`validate_filament_subtypes`, `validate_custom` (fixtures v1.9.0 to
v2.4.2).
- `python3 -m unittest discover -s scripts/tests -t scripts`: OK.
- The submitter printed with these filament settings in eufyMake Studio
on the M5C's stock brass 0.4 nozzle without issues. This Orca conversion
has been loaded and sliced in Orca 2.4.2 (as user presets inheriting the
Anker generic PLA+) but not yet printed on brass. The printer now has a
different nozzle fitted.

[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-10-11 06:26:26 +08:00
HanifKoh d715bb760c Let Plugin Windows Be Maximized on X11 (#16335)
wxGTK types every wxDialog as a dialog window, and Mutter offers no
maximize for anything but a normal window, so the maximize button and
shortcut did nothing on GNOME under X11. Modeless plugin windows are now
normal windows, kept off the taskbar as before. Modal ones stay dialogs
so the window manager keeps routing focus from the main window to them.
2026-10-11 04:40:56 +08:00
SoftFever 63918b44a8 Merge branch 'main' into anker-ankermake-pla-plus-filaments 2026-10-10 22:41:16 +08:00
mateuszandClaude Opus 5.5 64d09c9bca Add AnkerMake PLA+ filament profiles for Anker printers
Adds AnkerMake PLA+ Basic, Matte, Metallic, Silk and Glitter, converted from eufyMake Studio's bundled AnkerMake profiles (AGPL-3.0). Bumps the Anker vendor version to 02.04.00.06.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:13:00 -05:00
70 changed files with 24832 additions and 7904 deletions
+7
View File
@@ -179,6 +179,13 @@ jobs:
if: runner.os == 'macOS' && !inputs.macos-combine-only && !vars.SELF_HOSTED
run: |
df -hI /
# Deleting the unused Xcodes takes minutes, so only do it when the
# build might not fit. A cold build uses under 4 GiB after this step.
avail_gib=$(df -g / | awk 'NR == 2 { print $4 }')
if [ "$avail_gib" -ge 15 ]; then
echo "$avail_gib GiB free, enough for the build"
exit 0
fi
# Keep only the selected Xcode
xcode=$(basename "$(cd "$(xcode-select -p)/../.." && pwd -P)")
sudo find /Applications -maxdepth 1 -type d -name "Xcode_*.app" ! -name "$xcode" -exec rm -rf {} +
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
File diff suppressed because it is too large Load Diff
+41 -1
View File
@@ -1,6 +1,6 @@
{
"name": "Anker",
"version": "02.04.00.05",
"version": "02.04.00.06",
"force_update": "0",
"description": "Anker configurations",
"machine_model_list": [
@@ -304,6 +304,26 @@
"name": "Generic PLA Silk @Anker 0.25 nozzle",
"sub_path": "filament/Generic PLA Silk @Anker 0.25 nozzle.json"
},
{
"name": "AnkerMake PLA+ Basic @Anker base",
"sub_path": "filament/AnkerMake PLA+ Basic @Anker base.json"
},
{
"name": "AnkerMake PLA+ Glitter @Anker base",
"sub_path": "filament/AnkerMake PLA+ Glitter @Anker base.json"
},
{
"name": "AnkerMake PLA+ Matte @Anker base",
"sub_path": "filament/AnkerMake PLA+ Matte @Anker base.json"
},
{
"name": "AnkerMake PLA+ Metallic @Anker base",
"sub_path": "filament/AnkerMake PLA+ Metallic @Anker base.json"
},
{
"name": "AnkerMake PLA+ Silk @Anker base",
"sub_path": "filament/AnkerMake PLA+ Silk @Anker base.json"
},
{
"name": "Generic PLA+ @Anker",
"sub_path": "filament/Generic PLA+ @Anker.json"
@@ -327,6 +347,26 @@
{
"name": "Generic TPU @Anker",
"sub_path": "filament/Generic TPU @Anker.json"
},
{
"name": "AnkerMake PLA+ Basic @Anker",
"sub_path": "filament/AnkerMake PLA+ Basic @Anker.json"
},
{
"name": "AnkerMake PLA+ Glitter @Anker",
"sub_path": "filament/AnkerMake PLA+ Glitter @Anker.json"
},
{
"name": "AnkerMake PLA+ Matte @Anker",
"sub_path": "filament/AnkerMake PLA+ Matte @Anker.json"
},
{
"name": "AnkerMake PLA+ Metallic @Anker",
"sub_path": "filament/AnkerMake PLA+ Metallic @Anker.json"
},
{
"name": "AnkerMake PLA+ Silk @Anker",
"sub_path": "filament/AnkerMake PLA+ Silk @Anker.json"
}
],
"machine_list": [
@@ -0,0 +1,50 @@
{
"type": "filament",
"name": "AnkerMake PLA+ Basic @Anker base",
"inherits": "Generic PLA+ @Anker base",
"from": "system",
"filament_id": "OFUuZlQL",
"instantiation": "false",
"filament_vendor": [
"AnkerMake"
],
"nozzle_temperature": [
"200"
],
"nozzle_temperature_initial_layer": [
"230"
],
"hot_plate_temp": [
"65"
],
"textured_plate_temp": [
"65"
],
"hot_plate_temp_initial_layer": [
"65"
],
"textured_plate_temp_initial_layer": [
"65"
],
"filament_flow_ratio": [
"1"
],
"close_fan_the_first_x_layers": [
"3"
],
"fan_cooling_layer_time": [
"60"
],
"slow_down_layer_time": [
"8"
],
"slow_down_min_speed": [
"30"
],
"default_filament_colour": [
"#FFFFFF"
],
"filament_max_volumetric_speed": [
"22"
]
}
@@ -0,0 +1,16 @@
{
"type": "filament",
"name": "AnkerMake PLA+ Basic @Anker",
"inherits": "AnkerMake PLA+ Basic @Anker base",
"from": "system",
"setting_id": "EfM5rmn4w6pMBajC",
"instantiation": "true",
"compatible_printers": [
"Anker M5 0.4 nozzle",
"Anker M5 0.6 nozzle",
"Anker M5 All-Metal 0.4 nozzle",
"Anker M5 All-Metal 0.6 nozzle",
"Anker M5C 0.4 nozzle",
"Anker M5C 0.6 nozzle"
]
}
@@ -0,0 +1,50 @@
{
"type": "filament",
"name": "AnkerMake PLA+ Glitter @Anker base",
"inherits": "Generic PLA+ @Anker base",
"from": "system",
"filament_id": "OFxCURdg",
"instantiation": "false",
"filament_vendor": [
"AnkerMake"
],
"nozzle_temperature": [
"200"
],
"nozzle_temperature_initial_layer": [
"230"
],
"hot_plate_temp": [
"65"
],
"textured_plate_temp": [
"65"
],
"hot_plate_temp_initial_layer": [
"65"
],
"textured_plate_temp_initial_layer": [
"65"
],
"filament_flow_ratio": [
"1"
],
"close_fan_the_first_x_layers": [
"3"
],
"fan_cooling_layer_time": [
"60"
],
"slow_down_layer_time": [
"6"
],
"slow_down_min_speed": [
"30"
],
"default_filament_colour": [
"#00AD66"
],
"filament_max_volumetric_speed": [
"22"
]
}
@@ -0,0 +1,16 @@
{
"type": "filament",
"name": "AnkerMake PLA+ Glitter @Anker",
"inherits": "AnkerMake PLA+ Glitter @Anker base",
"from": "system",
"setting_id": "blGHp0htgHEj7khE",
"instantiation": "true",
"compatible_printers": [
"Anker M5 0.4 nozzle",
"Anker M5 0.6 nozzle",
"Anker M5 All-Metal 0.4 nozzle",
"Anker M5 All-Metal 0.6 nozzle",
"Anker M5C 0.4 nozzle",
"Anker M5C 0.6 nozzle"
]
}
@@ -0,0 +1,50 @@
{
"type": "filament",
"name": "AnkerMake PLA+ Matte @Anker base",
"inherits": "Generic PLA+ @Anker base",
"from": "system",
"filament_id": "OFKDOyNK",
"instantiation": "false",
"filament_vendor": [
"AnkerMake"
],
"nozzle_temperature": [
"210"
],
"nozzle_temperature_initial_layer": [
"230"
],
"hot_plate_temp": [
"65"
],
"textured_plate_temp": [
"65"
],
"hot_plate_temp_initial_layer": [
"65"
],
"textured_plate_temp_initial_layer": [
"65"
],
"filament_flow_ratio": [
"1"
],
"close_fan_the_first_x_layers": [
"3"
],
"fan_cooling_layer_time": [
"60"
],
"slow_down_layer_time": [
"6"
],
"slow_down_min_speed": [
"30"
],
"default_filament_colour": [
"#FFFFFF"
],
"filament_max_volumetric_speed": [
"22"
]
}
@@ -0,0 +1,13 @@
{
"type": "filament",
"name": "AnkerMake PLA+ Matte @Anker",
"inherits": "AnkerMake PLA+ Matte @Anker base",
"from": "system",
"setting_id": "tPgwC1eEXyWPOn9S",
"instantiation": "true",
"compatible_printers": [
"Anker M5 0.4 nozzle",
"Anker M5 All-Metal 0.4 nozzle",
"Anker M5C 0.4 nozzle"
]
}
@@ -0,0 +1,56 @@
{
"type": "filament",
"name": "AnkerMake PLA+ Metallic @Anker base",
"inherits": "Generic PLA+ @Anker base",
"from": "system",
"filament_id": "OFBfbmtb",
"instantiation": "false",
"filament_vendor": [
"AnkerMake"
],
"nozzle_temperature": [
"200"
],
"textured_plate_temp": [
"60"
],
"textured_plate_temp_initial_layer": [
"60"
],
"filament_flow_ratio": [
"1"
],
"close_fan_the_first_x_layers": [
"3"
],
"fan_cooling_layer_time": [
"60"
],
"slow_down_layer_time": [
"6"
],
"slow_down_min_speed": [
"30"
],
"overhang_fan_speed": [
"50"
],
"reduce_fan_stop_start_freq": [
"0"
],
"filament_retraction_length": [
"1"
],
"filament_retraction_speed": [
"50"
],
"filament_deretraction_speed": [
"50"
],
"default_filament_colour": [
"#ACAC8A"
],
"filament_max_volumetric_speed": [
"20"
]
}
@@ -0,0 +1,13 @@
{
"type": "filament",
"name": "AnkerMake PLA+ Metallic @Anker",
"inherits": "AnkerMake PLA+ Metallic @Anker base",
"from": "system",
"setting_id": "jlbAMNNaEacdUASZ",
"instantiation": "true",
"compatible_printers": [
"Anker M5 0.4 nozzle",
"Anker M5 All-Metal 0.4 nozzle",
"Anker M5C 0.4 nozzle"
]
}
@@ -0,0 +1,62 @@
{
"type": "filament",
"name": "AnkerMake PLA+ Silk @Anker base",
"inherits": "Generic PLA+ @Anker base",
"from": "system",
"filament_id": "OFkxTGQc",
"instantiation": "false",
"filament_vendor": [
"AnkerMake"
],
"nozzle_temperature": [
"210"
],
"nozzle_temperature_initial_layer": [
"230"
],
"hot_plate_temp": [
"65"
],
"textured_plate_temp": [
"65"
],
"hot_plate_temp_initial_layer": [
"65"
],
"textured_plate_temp_initial_layer": [
"65"
],
"filament_flow_ratio": [
"1"
],
"close_fan_the_first_x_layers": [
"3"
],
"fan_cooling_layer_time": [
"60"
],
"slow_down_layer_time": [
"2"
],
"slow_down_min_speed": [
"20"
],
"filament_retraction_length": [
"0.5"
],
"filament_retraction_speed": [
"20"
],
"filament_wipe": [
"0"
],
"default_filament_colour": [
"#99ADA3"
],
"filament_deretraction_speed": [
"20"
],
"filament_max_volumetric_speed": [
"13"
]
}
@@ -0,0 +1,15 @@
{
"type": "filament",
"name": "AnkerMake PLA+ Silk @Anker",
"inherits": "AnkerMake PLA+ Silk @Anker base",
"from": "system",
"setting_id": "eVM9Ckwjl5vEvEKN",
"instantiation": "true",
"compatible_printers": [
"Anker M5 0.4 nozzle",
"Anker M5 All-Metal 0.4 nozzle",
"Anker M5 All-Metal 0.6 nozzle",
"Anker M5C 0.4 nozzle",
"Anker M5C 0.6 nozzle"
]
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "Anycubic",
"version": "02.04.00.08",
"version": "02.04.00.09",
"force_update": "0",
"description": "Anycubic configurations",
"machine_model_list": [
@@ -110,7 +110,7 @@
"default_filament_profile": [
"Generic PLA @Anycubic"
],
"machine_start_gcode": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstops\nG0 Z0.2 F1800 ; move nozzle to print position\nG92 E0 ; specify current extruder position as zero\nG1 Y10 X180 E50 F1200 ; extrude a line in front of the printer\nG92 E0 ; specify current extruder position as zero\nG0 Z20 F6000 ; move head up\nG1 E-7 F2400 ; retract\nG04 S2 ; wait 2s\nG0 X0 F6000 ; wipe from oozed filament\nG1 E-1 F2400 ; undo some of the retraction to avoid oozing\nG1 F6000 ; set travel speed to move to start printing point\nM117",
"machine_start_gcode": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstops\nG0 Z0.2 F1800 ; move nozzle to print position\nG92 E0 ; specify current extruder position as zero\nM109 S[nozzle_temperature_initial_layer] ; wait for nozzle temp to stabilize\nG1 Y10 X180 E50 F1200 ; extrude a line in front of the printer\nG92 E0 ; specify current extruder position as zero\nG0 Z20 F6000 ; move head up\nG1 E-7 F2400 ; retract\nG04 S2 ; wait 2s\nG0 X0 F6000 ; wipe from oozed filament\nG1 E-1 F2400 ; undo some of the retraction to avoid oozing\nG1 F6000 ; set travel speed to move to start printing point\nM117",
"machine_end_gcode": "G4 ; wait\nG92 E0\nG1{if max_layer_z < printable_height} Z{z_offset+min(max_layer_z+30, printable_height)}{endif} ; move print head up\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200 F3000 ; home X axis\nM84 ; disable motors",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "Bambulab",
"url": "http://www.bambulab.com/Parameters/vendor/BBL.json",
"version": "02.08.00.13",
"version": "02.08.00.14",
"force_update": "0",
"description": "the initial version of BBL configurations",
"machine_model_list": [
@@ -8,6 +8,7 @@
"0.4"
],
"printer_variant": "0.4",
"use_3mf": "1",
"bed_exclude_area": [
"0x0",
"28x0",
@@ -9,6 +9,7 @@
"0.4"
],
"printer_variant": "0.4",
"use_3mf": "1",
"best_object_pos": "0.3x0.5",
"bed_exclude_area": [],
"bed_temperature_formula": "by_highest_temp",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "Prusa",
"version": "02.04.00.16",
"version": "02.04.00.17",
"force_update": "0",
"description": "Prusa configurations",
"machine_model_list": [
@@ -45,7 +45,7 @@
"35"
],
"filament_start_gcode": [
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.04{elsif nozzle_diameter[filament_extruder_id]==0.25}0.1{elsif nozzle_diameter[filament_extruder_id]==0.3}0.06{elsif nozzle_diameter[filament_extruder_id]==0.35}0.05{elsif nozzle_diameter[filament_extruder_id]==0.5}0.03{elsif nozzle_diameter[filament_extruder_id]==0.6}0.02{elsif nozzle_diameter[filament_extruder_id]==0.8}0.01{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|CORE One).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.02{elsif nozzle_diameter[filament_extruder_id]==0.5}0.018{elsif nozzle_diameter[filament_extruder_id]==0.6}0.012{elsif nozzle_diameter[filament_extruder_id]==0.8}0.01{elsif nozzle_diameter[filament_extruder_id]==0.25}0.09{elsif nozzle_diameter[filament_extruder_id]==0.3}0.065{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S40 ; set heatbreak target temp"
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.04{elsif nozzle_diameter[filament_extruder_id]==0.25}0.1{elsif nozzle_diameter[filament_extruder_id]==0.3}0.06{elsif nozzle_diameter[filament_extruder_id]==0.35}0.05{elsif nozzle_diameter[filament_extruder_id]==0.5}0.03{elsif nozzle_diameter[filament_extruder_id]==0.6}0.02{elsif nozzle_diameter[filament_extruder_id]==0.8}0.01{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|COREONE).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.02{elsif nozzle_diameter[filament_extruder_id]==0.5}0.018{elsif nozzle_diameter[filament_extruder_id]==0.6}0.012{elsif nozzle_diameter[filament_extruder_id]==0.8}0.01{elsif nozzle_diameter[filament_extruder_id]==0.25}0.09{elsif nozzle_diameter[filament_extruder_id]==0.3}0.065{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S40 ; set heatbreak target temp"
],
"hot_plate_temp": [
"110"
@@ -78,7 +78,7 @@
"35"
],
"filament_start_gcode": [
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.04{elsif nozzle_diameter[filament_extruder_id]==0.25}0.1{elsif nozzle_diameter[filament_extruder_id]==0.3}0.06{elsif nozzle_diameter[filament_extruder_id]==0.35}0.05{elsif nozzle_diameter[filament_extruder_id]==0.5}0.03{elsif nozzle_diameter[filament_extruder_id]==0.6}0.02{elsif nozzle_diameter[filament_extruder_id]==0.8}0.01{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|CORE One).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.02{elsif nozzle_diameter[filament_extruder_id]==0.5}0.018{elsif nozzle_diameter[filament_extruder_id]==0.6}0.012{elsif nozzle_diameter[filament_extruder_id]==0.8}0.01{elsif nozzle_diameter[filament_extruder_id]==0.25}0.09{elsif nozzle_diameter[filament_extruder_id]==0.3}0.065{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S40 ; set heatbreak target temp"
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.04{elsif nozzle_diameter[filament_extruder_id]==0.25}0.1{elsif nozzle_diameter[filament_extruder_id]==0.3}0.06{elsif nozzle_diameter[filament_extruder_id]==0.35}0.05{elsif nozzle_diameter[filament_extruder_id]==0.5}0.03{elsif nozzle_diameter[filament_extruder_id]==0.6}0.02{elsif nozzle_diameter[filament_extruder_id]==0.8}0.01{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|COREONE).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.02{elsif nozzle_diameter[filament_extruder_id]==0.5}0.018{elsif nozzle_diameter[filament_extruder_id]==0.6}0.012{elsif nozzle_diameter[filament_extruder_id]==0.8}0.01{elsif nozzle_diameter[filament_extruder_id]==0.25}0.09{elsif nozzle_diameter[filament_extruder_id]==0.3}0.065{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S40 ; set heatbreak target temp"
],
"hot_plate_temp": [
"110"
@@ -50,7 +50,7 @@
"0.8"
],
"filament_start_gcode": [
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.07{elsif nozzle_diameter[filament_extruder_id]==0.25}0.12{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{elsif nozzle_diameter[filament_extruder_id]==0.35}0.08{elsif nozzle_diameter[filament_extruder_id]==0.6}0.04{elsif nozzle_diameter[filament_extruder_id]==0.5}0.05{elsif nozzle_diameter[filament_extruder_id]==0.8}0.02{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|CORE One).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.053{elsif nozzle_diameter[filament_extruder_id]==0.5}0.042{elsif nozzle_diameter[filament_extruder_id]==0.6}0.032{elsif nozzle_diameter[filament_extruder_id]==0.8}0.018{elsif nozzle_diameter[filament_extruder_id]==0.25}0.18{elsif nozzle_diameter[filament_extruder_id]==0.3}0.1{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S36 ; set heatbreak target temp"
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.07{elsif nozzle_diameter[filament_extruder_id]==0.25}0.12{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{elsif nozzle_diameter[filament_extruder_id]==0.35}0.08{elsif nozzle_diameter[filament_extruder_id]==0.6}0.04{elsif nozzle_diameter[filament_extruder_id]==0.5}0.05{elsif nozzle_diameter[filament_extruder_id]==0.8}0.02{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|COREONE).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.053{elsif nozzle_diameter[filament_extruder_id]==0.5}0.042{elsif nozzle_diameter[filament_extruder_id]==0.6}0.032{elsif nozzle_diameter[filament_extruder_id]==0.8}0.018{elsif nozzle_diameter[filament_extruder_id]==0.25}0.18{elsif nozzle_diameter[filament_extruder_id]==0.3}0.1{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S36 ; set heatbreak target temp"
],
"filament_wipe": [
"1"
@@ -38,7 +38,7 @@
"15"
],
"filament_start_gcode": [
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.05{elsif nozzle_diameter[filament_extruder_id]==0.25}0.14{elsif nozzle_diameter[filament_extruder_id]==0.3}0.07{elsif nozzle_diameter[filament_extruder_id]==0.35}0.06{elsif nozzle_diameter[filament_extruder_id]==0.6}0.03{elsif nozzle_diameter[filament_extruder_id]==0.5}0.035{elsif nozzle_diameter[filament_extruder_id]==0.8}0.015{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|CORE One|CORE One L).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.036{elsif nozzle_diameter[filament_extruder_id]==0.5}0.025{elsif nozzle_diameter[filament_extruder_id]==0.6}0.022{elsif nozzle_diameter[filament_extruder_id]==0.8}0.014{elsif nozzle_diameter[filament_extruder_id]==0.25}0.12{elsif nozzle_diameter[filament_extruder_id]==0.3}0.08{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S36 ; set heatbreak target temp"
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.05{elsif nozzle_diameter[filament_extruder_id]==0.25}0.14{elsif nozzle_diameter[filament_extruder_id]==0.3}0.07{elsif nozzle_diameter[filament_extruder_id]==0.35}0.06{elsif nozzle_diameter[filament_extruder_id]==0.6}0.03{elsif nozzle_diameter[filament_extruder_id]==0.5}0.035{elsif nozzle_diameter[filament_extruder_id]==0.8}0.015{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|COREONE).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.036{elsif nozzle_diameter[filament_extruder_id]==0.5}0.025{elsif nozzle_diameter[filament_extruder_id]==0.6}0.022{elsif nozzle_diameter[filament_extruder_id]==0.8}0.014{elsif nozzle_diameter[filament_extruder_id]==0.25}0.12{elsif nozzle_diameter[filament_extruder_id]==0.3}0.08{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S36 ; set heatbreak target temp"
],
"filament_z_hop": [
"0.6"
@@ -58,7 +58,7 @@
"7"
],
"filament_start_gcode": [
"M900 K{if nozzle_diameter[0]==0.4}0.05{elsif nozzle_diameter[0]==0.25}0.14{elsif nozzle_diameter[0]==0.3}0.07{elsif nozzle_diameter[0]==0.35}0.06{elsif nozzle_diameter[0]==0.6}0.03{elsif nozzle_diameter[0]==0.5}0.035{elsif nozzle_diameter[0]==0.8}0.015{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|CORE One).*/}\nM572 S{if nozzle_diameter[0]==0.4}0.03{elsif nozzle_diameter[0]==0.5}0.022{elsif nozzle_diameter[0]==0.6}0.018{elsif nozzle_diameter[0]==0.8}0.012{elsif nozzle_diameter[0]==0.25}0.12{elsif nozzle_diameter[0]==0.3}0.075{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S36 ; set heatbreak target temp"
"M900 K{if nozzle_diameter[0]==0.4}0.05{elsif nozzle_diameter[0]==0.25}0.14{elsif nozzle_diameter[0]==0.3}0.07{elsif nozzle_diameter[0]==0.35}0.06{elsif nozzle_diameter[0]==0.6}0.03{elsif nozzle_diameter[0]==0.5}0.035{elsif nozzle_diameter[0]==0.8}0.015{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|COREONE).*/}\nM572 S{if nozzle_diameter[0]==0.4}0.03{elsif nozzle_diameter[0]==0.5}0.022{elsif nozzle_diameter[0]==0.6}0.018{elsif nozzle_diameter[0]==0.8}0.012{elsif nozzle_diameter[0]==0.25}0.12{elsif nozzle_diameter[0]==0.3}0.075{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S36 ; set heatbreak target temp"
],
"filament_z_hop": [
"0.6",
@@ -63,7 +63,7 @@
"0"
],
"filament_start_gcode": [
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.04{elsif nozzle_diameter[filament_extruder_id]==0.25}0.1{elsif nozzle_diameter[filament_extruder_id]==0.3}0.06{elsif nozzle_diameter[filament_extruder_id]==0.35}0.05{elsif nozzle_diameter[filament_extruder_id]==0.5}0.03{elsif nozzle_diameter[filament_extruder_id]==0.6}0.02{elsif nozzle_diameter[filament_extruder_id]==0.8}0.01{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|CORE One).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.02{elsif nozzle_diameter[filament_extruder_id]==0.5}0.018{elsif nozzle_diameter[filament_extruder_id]==0.6}0.012{elsif nozzle_diameter[filament_extruder_id]==0.8}0.01{elsif nozzle_diameter[filament_extruder_id]==0.25}0.09{elsif nozzle_diameter[filament_extruder_id]==0.3}0.065{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S40 ; set heatbreak target temp"
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.04{elsif nozzle_diameter[filament_extruder_id]==0.25}0.1{elsif nozzle_diameter[filament_extruder_id]==0.3}0.06{elsif nozzle_diameter[filament_extruder_id]==0.35}0.05{elsif nozzle_diameter[filament_extruder_id]==0.5}0.03{elsif nozzle_diameter[filament_extruder_id]==0.6}0.02{elsif nozzle_diameter[filament_extruder_id]==0.8}0.01{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|COREONE).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.02{elsif nozzle_diameter[filament_extruder_id]==0.5}0.018{elsif nozzle_diameter[filament_extruder_id]==0.6}0.012{elsif nozzle_diameter[filament_extruder_id]==0.8}0.01{elsif nozzle_diameter[filament_extruder_id]==0.25}0.09{elsif nozzle_diameter[filament_extruder_id]==0.3}0.065{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S40 ; set heatbreak target temp"
],
"filament_type": [
"ASA"
@@ -65,7 +65,7 @@
"0"
],
"filament_start_gcode": [
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.07{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{elsif nozzle_diameter[filament_extruder_id]==0.35}0.08{elsif nozzle_diameter[filament_extruder_id]==0.6}0.04{elsif nozzle_diameter[filament_extruder_id]==0.5}0.05{elsif nozzle_diameter[filament_extruder_id]==0.8}0.02{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|CORE One).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.05{elsif nozzle_diameter[filament_extruder_id]==0.5}0.035{elsif nozzle_diameter[filament_extruder_id]==0.6}0.025{elsif nozzle_diameter[filament_extruder_id]==0.8}0.016{elsif nozzle_diameter[filament_extruder_id]==0.25}0.14{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S45 ; set heatbreak target temp"
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.07{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{elsif nozzle_diameter[filament_extruder_id]==0.35}0.08{elsif nozzle_diameter[filament_extruder_id]==0.6}0.04{elsif nozzle_diameter[filament_extruder_id]==0.5}0.05{elsif nozzle_diameter[filament_extruder_id]==0.8}0.02{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|COREONE).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.05{elsif nozzle_diameter[filament_extruder_id]==0.5}0.035{elsif nozzle_diameter[filament_extruder_id]==0.6}0.025{elsif nozzle_diameter[filament_extruder_id]==0.8}0.016{elsif nozzle_diameter[filament_extruder_id]==0.25}0.14{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S45 ; set heatbreak target temp"
],
"filament_type": [
"PA11-CF"
@@ -63,7 +63,7 @@
"0"
],
"filament_start_gcode": [
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.07{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{elsif nozzle_diameter[filament_extruder_id]==0.35}0.08{elsif nozzle_diameter[filament_extruder_id]==0.6}0.04{elsif nozzle_diameter[filament_extruder_id]==0.5}0.05{elsif nozzle_diameter[filament_extruder_id]==0.8}0.02{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|CORE One).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.05{elsif nozzle_diameter[filament_extruder_id]==0.5}0.035{elsif nozzle_diameter[filament_extruder_id]==0.6}0.025{elsif nozzle_diameter[filament_extruder_id]==0.8}0.016{elsif nozzle_diameter[filament_extruder_id]==0.25}0.14{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S45 ; set heatbreak target temp"
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.07{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{elsif nozzle_diameter[filament_extruder_id]==0.35}0.08{elsif nozzle_diameter[filament_extruder_id]==0.6}0.04{elsif nozzle_diameter[filament_extruder_id]==0.5}0.05{elsif nozzle_diameter[filament_extruder_id]==0.8}0.02{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|COREONE).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.05{elsif nozzle_diameter[filament_extruder_id]==0.5}0.035{elsif nozzle_diameter[filament_extruder_id]==0.6}0.025{elsif nozzle_diameter[filament_extruder_id]==0.8}0.016{elsif nozzle_diameter[filament_extruder_id]==0.25}0.14{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S45 ; set heatbreak target temp"
],
"filament_type": [
"PC"
@@ -65,7 +65,7 @@
"0"
],
"filament_start_gcode": [
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.07{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{elsif nozzle_diameter[filament_extruder_id]==0.35}0.08{elsif nozzle_diameter[filament_extruder_id]==0.6}0.04{elsif nozzle_diameter[filament_extruder_id]==0.5}0.05{elsif nozzle_diameter[filament_extruder_id]==0.8}0.02{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|CORE One).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.05{elsif nozzle_diameter[filament_extruder_id]==0.5}0.035{elsif nozzle_diameter[filament_extruder_id]==0.6}0.025{elsif nozzle_diameter[filament_extruder_id]==0.8}0.016{elsif nozzle_diameter[filament_extruder_id]==0.25}0.14{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S45 ; set heatbreak target temp"
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.07{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{elsif nozzle_diameter[filament_extruder_id]==0.35}0.08{elsif nozzle_diameter[filament_extruder_id]==0.6}0.04{elsif nozzle_diameter[filament_extruder_id]==0.5}0.05{elsif nozzle_diameter[filament_extruder_id]==0.8}0.02{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|COREONE).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.05{elsif nozzle_diameter[filament_extruder_id]==0.5}0.035{elsif nozzle_diameter[filament_extruder_id]==0.6}0.025{elsif nozzle_diameter[filament_extruder_id]==0.8}0.016{elsif nozzle_diameter[filament_extruder_id]==0.25}0.14{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S45 ; set heatbreak target temp"
],
"filament_type": [
"PC-CF"
@@ -69,7 +69,7 @@
"0"
],
"filament_start_gcode": [
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.07{elsif nozzle_diameter[filament_extruder_id]==0.25}0.12{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{elsif nozzle_diameter[filament_extruder_id]==0.35}0.08{elsif nozzle_diameter[filament_extruder_id]==0.6}0.04{elsif nozzle_diameter[filament_extruder_id]==0.5}0.05{elsif nozzle_diameter[filament_extruder_id]==0.8}0.02{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|CORE One).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.053{elsif nozzle_diameter[filament_extruder_id]==0.5}0.042{elsif nozzle_diameter[filament_extruder_id]==0.6}0.032{elsif nozzle_diameter[filament_extruder_id]==0.8}0.018{elsif nozzle_diameter[filament_extruder_id]==0.25}0.18{elsif nozzle_diameter[filament_extruder_id]==0.3}0.1{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S36 ; set heatbreak target temp"
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.07{elsif nozzle_diameter[filament_extruder_id]==0.25}0.12{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{elsif nozzle_diameter[filament_extruder_id]==0.35}0.08{elsif nozzle_diameter[filament_extruder_id]==0.6}0.04{elsif nozzle_diameter[filament_extruder_id]==0.5}0.05{elsif nozzle_diameter[filament_extruder_id]==0.8}0.02{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|COREONE).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.053{elsif nozzle_diameter[filament_extruder_id]==0.5}0.042{elsif nozzle_diameter[filament_extruder_id]==0.6}0.032{elsif nozzle_diameter[filament_extruder_id]==0.8}0.018{elsif nozzle_diameter[filament_extruder_id]==0.25}0.18{elsif nozzle_diameter[filament_extruder_id]==0.3}0.1{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S36 ; set heatbreak target temp"
],
"filament_type": [
"PETG"
@@ -64,7 +64,7 @@
"0"
],
"filament_start_gcode": [
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.05{elsif nozzle_diameter[filament_extruder_id]==0.25}0.14{elsif nozzle_diameter[filament_extruder_id]==0.3}0.07{elsif nozzle_diameter[filament_extruder_id]==0.35}0.06{elsif nozzle_diameter[filament_extruder_id]==0.6}0.03{elsif nozzle_diameter[filament_extruder_id]==0.5}0.035{elsif nozzle_diameter[filament_extruder_id]==0.8}0.015{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|CORE One|CORE One L).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.036{elsif nozzle_diameter[filament_extruder_id]==0.5}0.025{elsif nozzle_diameter[filament_extruder_id]==0.6}0.022{elsif nozzle_diameter[filament_extruder_id]==0.8}0.014{elsif nozzle_diameter[filament_extruder_id]==0.25}0.12{elsif nozzle_diameter[filament_extruder_id]==0.3}0.08{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S36 ; set heatbreak target temp"
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.05{elsif nozzle_diameter[filament_extruder_id]==0.25}0.14{elsif nozzle_diameter[filament_extruder_id]==0.3}0.07{elsif nozzle_diameter[filament_extruder_id]==0.35}0.06{elsif nozzle_diameter[filament_extruder_id]==0.6}0.03{elsif nozzle_diameter[filament_extruder_id]==0.5}0.035{elsif nozzle_diameter[filament_extruder_id]==0.8}0.015{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|COREONE).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.036{elsif nozzle_diameter[filament_extruder_id]==0.5}0.025{elsif nozzle_diameter[filament_extruder_id]==0.6}0.022{elsif nozzle_diameter[filament_extruder_id]==0.8}0.014{elsif nozzle_diameter[filament_extruder_id]==0.25}0.12{elsif nozzle_diameter[filament_extruder_id]==0.3}0.08{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S36 ; set heatbreak target temp"
],
"filament_type": [
"PLA"
@@ -65,7 +65,7 @@
"1"
],
"filament_start_gcode": [
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.05{elsif nozzle_diameter[filament_extruder_id]==0.25}0.14{elsif nozzle_diameter[filament_extruder_id]==0.3}0.07{elsif nozzle_diameter[filament_extruder_id]==0.35}0.06{elsif nozzle_diameter[filament_extruder_id]==0.6}0.03{elsif nozzle_diameter[filament_extruder_id]==0.5}0.035{elsif nozzle_diameter[filament_extruder_id]==0.8}0.015{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|CORE One).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.036{elsif nozzle_diameter[filament_extruder_id]==0.5}0.025{elsif nozzle_diameter[filament_extruder_id]==0.6}0.022{elsif nozzle_diameter[filament_extruder_id]==0.8}0.014{elsif nozzle_diameter[filament_extruder_id]==0.25}0.12{elsif nozzle_diameter[filament_extruder_id]==0.3}0.08{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S36 ; set heatbreak target temp"
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.05{elsif nozzle_diameter[filament_extruder_id]==0.25}0.14{elsif nozzle_diameter[filament_extruder_id]==0.3}0.07{elsif nozzle_diameter[filament_extruder_id]==0.35}0.06{elsif nozzle_diameter[filament_extruder_id]==0.6}0.03{elsif nozzle_diameter[filament_extruder_id]==0.5}0.035{elsif nozzle_diameter[filament_extruder_id]==0.8}0.015{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|COREONE).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.036{elsif nozzle_diameter[filament_extruder_id]==0.5}0.025{elsif nozzle_diameter[filament_extruder_id]==0.6}0.022{elsif nozzle_diameter[filament_extruder_id]==0.8}0.014{elsif nozzle_diameter[filament_extruder_id]==0.25}0.12{elsif nozzle_diameter[filament_extruder_id]==0.3}0.08{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S36 ; set heatbreak target temp"
],
"filament_type": [
"PVB"
@@ -64,7 +64,7 @@
"0"
],
"filament_start_gcode": [
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.05{elsif nozzle_diameter[filament_extruder_id]==0.25}0.14{elsif nozzle_diameter[filament_extruder_id]==0.3}0.07{elsif nozzle_diameter[filament_extruder_id]==0.35}0.06{elsif nozzle_diameter[filament_extruder_id]==0.6}0.03{elsif nozzle_diameter[filament_extruder_id]==0.5}0.035{elsif nozzle_diameter[filament_extruder_id]==0.8}0.015{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|CORE One).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.036{elsif nozzle_diameter[filament_extruder_id]==0.5}0.025{elsif nozzle_diameter[filament_extruder_id]==0.6}0.022{elsif nozzle_diameter[filament_extruder_id]==0.8}0.014{elsif nozzle_diameter[filament_extruder_id]==0.25}0.12{elsif nozzle_diameter[filament_extruder_id]==0.3}0.08{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S36 ; set heatbreak target temp"
"M900 K{if nozzle_diameter[filament_extruder_id]==0.4}0.05{elsif nozzle_diameter[filament_extruder_id]==0.25}0.14{elsif nozzle_diameter[filament_extruder_id]==0.3}0.07{elsif nozzle_diameter[filament_extruder_id]==0.35}0.06{elsif nozzle_diameter[filament_extruder_id]==0.6}0.03{elsif nozzle_diameter[filament_extruder_id]==0.5}0.035{elsif nozzle_diameter[filament_extruder_id]==0.8}0.015{else}0{endif} ; Filament gcode\n\n{if printer_notes=~/.*(MK4IS|XLIS|MK4S|MK3.9S|COREONE).*/}\nM572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.036{elsif nozzle_diameter[filament_extruder_id]==0.5}0.025{elsif nozzle_diameter[filament_extruder_id]==0.6}0.022{elsif nozzle_diameter[filament_extruder_id]==0.8}0.014{elsif nozzle_diameter[filament_extruder_id]==0.25}0.12{elsif nozzle_diameter[filament_extruder_id]==0.3}0.08{else}0{endif} ; Filament gcode\n{endif}\n\nM142 S36 ; set heatbreak target temp"
],
"filament_type": [
"PLA"
+1
View File
@@ -405,6 +405,7 @@ set(lisbslic3r_sources
Polyline.hpp
PresetBundle.cpp
PresetBundle.hpp
PrinterBedAssets.hpp
PresetCacheFormat.cpp
PresetCacheFormat.hpp
Preset.cpp
+308 -2
View File
@@ -18,6 +18,7 @@
#include <cstdint>
#include <cmath>
#include <ctime>
#include <ios>
#include <optional>
#include <exception>
#include <memory>
@@ -29,6 +30,7 @@
#include <sstream>
#include "PresetBundle.hpp"
#include "PrinterBedAssets.hpp"
#include "Config.hpp"
#include "Exception.hpp"
@@ -64,6 +66,7 @@
#include <boost/algorithm/clamp.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/range/adaptor/transformed.hpp>
#include <boost/nowide/convert.hpp>
#include <boost/nowide/cstdio.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/property_tree/ini_parser.hpp>
@@ -1678,6 +1681,258 @@ bool PresetBundle::apply_vendor_config(
return true;
}
namespace {
// The two bed files a printer bundle can carry. Zip names are bed/<n>/<stem><ext>, never the source basename.
struct BedAssetSlot {
const char *option;
const char *stem;
};
constexpr BedAssetSlot k_bed_asset_slots[] = {
{"bed_custom_texture", "texture"},
{"bed_custom_model", "model"},
};
std::string zip_slashes(std::string name)
{
for (char &c : name)
if (c == '\\')
c = '/';
return name;
}
// The bed dialog loads only png/svg textures and stl models. Anything else is not a bed asset.
bool bed_extension_allowed(const char *option, const std::string &ext)
{
if (std::string(option) == "bed_custom_texture")
return boost::iequals(ext, ".png") || boost::iequals(ext, ".svg");
if (std::string(option) == "bed_custom_model")
return boost::iequals(ext, ".stl");
return false;
}
// Extension including the dot, or empty when this option may not use it.
std::string bed_asset_extension(const std::string &path, const char *option)
{
const auto slash = path.find_last_of("/\\");
const auto base = slash == std::string::npos ? 0 : slash + 1;
const auto dot = path.find_last_of('.');
if (dot == std::string::npos || dot < base || dot + 1 >= path.size())
return {};
const std::string ext = path.substr(dot);
if (!bed_extension_allowed(option, ext))
return {};
return ext;
}
bool utf8_is_file(const std::string &path)
{
if (path.empty())
return false;
boost::system::error_code ec;
#ifdef _WIN32
// A narrow path is the ANSI code page unless nowide_filesystem() has run.
const std::wstring wide = boost::nowide::widen(path);
if (wide.empty())
return false;
return boost::filesystem::is_regular_file(boost::filesystem::path(wide), ec) && !ec;
#else
return boost::filesystem::is_regular_file(path, ec) && !ec;
#endif
}
std::string bed_asset_dest_utf8(const fs::path &dest)
{
#ifdef _WIN32
return boost::nowide::narrow(dest.wstring());
#else
return dest.string();
#endif
}
// True only for bed/<digits>/<stem><ext> with an extension the bed dialog accepts for this field.
bool accepted_bed_zip_name(const std::string &raw, const BedAssetSlot &slot, const fs::path &bundle_dir,
std::string &zip_name, std::string &folder)
{
const std::string name = zip_slashes(raw);
if (name.find("..") != std::string::npos)
return false;
if (name.size() < 8 || name.compare(0, 4, "bed/") != 0)
return false;
const auto slash = name.find('/', 4);
if (slash == std::string::npos || slash == 4 || name.find('/', slash + 1) != std::string::npos)
return false;
const std::string digits = name.substr(4, slash - 4);
for (unsigned char c : digits) {
if (!std::isdigit(c))
return false;
}
const std::string filename = name.substr(slash + 1);
const std::string ext = bed_asset_extension(filename, slot.option);
if (ext.empty() || filename != std::string(slot.stem) + ext)
return false;
if (!is_path_within_root(name, bundle_dir))
return false;
zip_name = name;
folder = digits;
return true;
}
struct PendingBedAsset {
std::string option;
std::string zip_name;
std::string dest;
std::string printer_key;
fs::path final_path;
fs::path staged;
};
// Validated asset names only. A raw manifest value that fails the check is not skipped later,
// so a value like printer/Name.json cannot hide that preset.
// printer_asset is keyed by printer_config zip entry. A flat (unkeyed) object is not read.
std::vector<PendingBedAsset> pending_bed_assets(const json &bundle_json, const fs::path &bundle_dir,
std::unordered_set<std::string> &skip_names,
const std::unordered_set<std::string> &zip_entries)
{
std::vector<PendingBedAsset> pending;
if (!bundle_json.contains("printer_asset") || !bundle_json["printer_asset"].is_object())
return pending;
const json &assets = bundle_json["printer_asset"];
for (auto it = assets.begin(); it != assets.end(); ++it) {
const std::string printer_key = it.key();
if (zip_entries.count(printer_key) == 0) {
BOOST_LOG_TRIVIAL(warning) << "Printer bundle asset entry is ignored, no matching printer in the zip: " << printer_key;
continue;
}
if (!it.value().is_object())
continue;
const json &per_printer = it.value();
for (const BedAssetSlot &slot : k_bed_asset_slots) {
if (!per_printer.contains(slot.option) || !per_printer[slot.option].is_string())
continue;
const std::string raw = per_printer[slot.option].get<std::string>();
std::string zip_name;
std::string folder;
if (!accepted_bed_zip_name(raw, slot, bundle_dir, zip_name, folder)) {
BOOST_LOG_TRIVIAL(warning) << "Printer bundle asset path is rejected: " << raw;
continue;
}
const std::string filename = zip_name.substr(zip_name.rfind('/') + 1);
const fs::path final_path = fs::absolute(bundle_dir / "bed" / folder / filename);
pending.push_back({slot.option, zip_name, bed_asset_dest_utf8(final_path), printer_key, final_path, {}});
skip_names.insert(zip_name);
}
}
return pending;
}
// Stage under temp_folder. The bundle bed/ directory is not touched until a printer preset keeps the path.
bool stage_bed_asset(mz_zip_archive &zip, PendingBedAsset &asset, const fs::path &temp_folder)
{
size_t size = 0;
void *buf = mz_zip_reader_extract_file_to_heap(&zip, asset.zip_name.c_str(), &size, MZ_ZIP_FLAG_CASE_SENSITIVE);
if (buf == nullptr) {
BOOST_LOG_TRIVIAL(warning) << "Printer bundle asset is missing, skipping " << asset.option << ": " << asset.zip_name;
return false;
}
const std::string filename = asset.zip_name.substr(asset.zip_name.rfind('/') + 1);
asset.staged = temp_folder / "bed" / asset.final_path.parent_path().filename() / filename;
boost::system::error_code ec;
fs::create_directories(asset.staged.parent_path(), ec);
const std::string staged_utf8 = bed_asset_dest_utf8(asset.staged);
bool wrote = false;
if (!ec) {
boost::nowide::ofstream out(staged_utf8, std::ios::binary | std::ios::trunc);
wrote = static_cast<bool>(out);
if (wrote && size > 0)
wrote = static_cast<bool>(out.write(static_cast<const char *>(buf), static_cast<std::streamsize>(size)));
}
mz_free(buf);
if (!wrote) {
BOOST_LOG_TRIVIAL(warning) << "Printer bundle asset could not be written, skipping " << asset.option << ": " << staged_utf8;
fs::remove(asset.staged, ec);
return false;
}
return true;
}
bool install_bed_asset(const PendingBedAsset &asset)
{
boost::system::error_code ec;
fs::create_directories(asset.final_path.parent_path(), ec);
if (!ec)
fs::copy_file(asset.staged, asset.final_path, fs::copy_options::overwrite_existing, ec);
if (ec) {
BOOST_LOG_TRIVIAL(warning) << "Printer bundle asset could not be installed: " << asset.dest;
return false;
}
return true;
}
bool is_pending_bed_asset_entry(const std::string &entry, const std::unordered_set<std::string> &names)
{
return names.count(entry) != 0 || names.count(zip_slashes(entry)) != 0;
}
bool printer_kept_bed_paths(const PresetCollection &printers, const std::map<std::string, std::string> &paths)
{
if (paths.empty())
return false;
for (const Preset &preset : printers.get_presets()) {
for (const auto &entry : paths)
if (preset.config.opt_string(entry.first) == entry.second)
return true;
}
return false;
}
} // namespace
bool append_printer_bed_assets(mz_zip_archive &zip, const DynamicPrintConfig &config, json &bundle_structure,
const std::string &printer_config_entry, int printer_index)
{
if (printer_config_entry.empty() || printer_index < 0)
return true;
json assets = json::object();
for (const BedAssetSlot &slot : k_bed_asset_slots) {
if (!config.has(slot.option))
continue;
const std::string stored = config.opt_string(slot.option);
if (stored.empty())
continue;
if (!utf8_is_file(stored)) {
BOOST_LOG_TRIVIAL(warning) << "Bed asset file is missing, skipping " << slot.option << ": " << stored;
continue;
}
const std::string ext = bed_asset_extension(stored, slot.option);
if (ext.empty()) {
BOOST_LOG_TRIVIAL(warning) << "Bed asset extension is not packable, skipping " << slot.option << ": " << stored;
continue;
}
boost::nowide::ifstream in(stored, std::ios::binary);
if (!in) {
BOOST_LOG_TRIVIAL(warning) << "Bed asset file cannot be read, skipping " << slot.option << ": " << stored;
continue;
}
const std::string bytes((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
if (in.bad()) {
BOOST_LOG_TRIVIAL(warning) << "Bed asset file cannot be read, skipping " << slot.option << ": " << stored;
continue;
}
const std::string zip_name = std::string("bed/") + std::to_string(printer_index) + "/" + slot.stem + ext;
const void *data = bytes.empty() ? static_cast<const void *>("") : static_cast<const void *>(bytes.data());
if (mz_zip_writer_add_mem(&zip, zip_name.c_str(), data, bytes.size(), MZ_DEFAULT_COMPRESSION) == MZ_FALSE) {
BOOST_LOG_TRIVIAL(error) << "Failed to add bed asset to printer bundle: " << zip_name;
return false;
}
assets[slot.option] = zip_name;
}
if (!assets.empty())
bundle_structure["printer_asset"][printer_config_entry] = std::move(assets);
return true;
}
// Import presets from UI control
PresetsConfigSubstitutions PresetBundle::import_presets(std::vector<std::string> & files,
std::function<int(std::string const &)> override_confirm,
@@ -1773,14 +2028,44 @@ PresetsConfigSubstitutions PresetBundle::import_presets(std::vector<std::string>
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " No bundle_structure.json found, importing presets into the user preset directory";
}
json bundle_json = json::object();
std::unordered_set<std::string> bed_asset_entries;
std::vector<PendingBedAsset> bed_assets;
std::map<std::string, std::map<std::string, std::string>> bed_paths_by_printer;
bool bed_paths_loaded = false;
if (has_bundle_structure && !bundle_base_dir.empty()) {
try {
boost::nowide::ifstream manifest_in(metadata_path.string());
manifest_in >> bundle_json;
} catch (const std::exception &err) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << " failed to read bundle manifest: " << err.what();
bundle_json = json::object();
}
std::unordered_set<std::string> zip_entries;
const int zip_file_count = mz_zip_reader_get_num_files(&zip_archive);
for (int zi = 0; zi < zip_file_count; ++zi) {
mz_zip_archive_file_stat st;
if (mz_zip_reader_file_stat(&zip_archive, zi, &st))
zip_entries.insert(zip_slashes(st.m_filename));
}
bed_assets = pending_bed_assets(bundle_json, bundle_base_dir, bed_asset_entries, zip_entries);
for (PendingBedAsset &asset : bed_assets)
if (stage_bed_asset(zip_archive, asset, temp_folder))
bed_paths_by_printer[asset.printer_key].emplace(asset.option, asset.dest);
}
// Extract Files
int num_files = mz_zip_reader_get_num_files(&zip_archive);
const std::map<std::string, std::string> no_bed_paths;
for (int i = 0; i < num_files; i++) {
mz_zip_archive_file_stat file_stat;
status = mz_zip_reader_file_stat(&zip_archive, i, &file_stat);
if (status) {
std::string file_name = file_stat.m_filename;
const std::string zip_entry = zip_slashes(file_name);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " From zip file: " << file << ". Read file name: " << file_stat.m_filename;
if (is_pending_bed_asset_entry(file_name, bed_asset_entries))
continue;
size_t index = file_name.find_last_of("/\\");
if (std::string::npos != index) {
file_name = file_name.substr(index + 1);
@@ -1798,13 +2083,26 @@ PresetsConfigSubstitutions PresetBundle::import_presets(std::vector<std::string>
if (MZ_FALSE == status) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " Failed to open target file: " << target_file_path;
} else {
const auto paths_it = bed_paths_by_printer.find(zip_entry);
const std::map<std::string, std::string> &this_bed_paths =
paths_it != bed_paths_by_printer.end() ? paths_it->second : no_bed_paths;
bool is_success = import_json_presets(substitutions, target_file_path, override_confirm, rule, overwrite, result,
has_bundle_structure ? bundle_base_dir.string() : std::string());
has_bundle_structure ? bundle_base_dir.string() : std::string(),
this_bed_paths, &bed_paths_loaded);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " import target file: " << target_file_path << " import result" << is_success;
}
}
}
// Copy into bed/ only after this import loads a printer that still holds the staged path.
// A preset left from an earlier import must not count: declining this import leaves its files alone.
if (bed_paths_loaded) {
for (const PendingBedAsset &asset : bed_assets) {
if (printer_kept_bed_paths(printers, {{asset.option, asset.dest}}))
install_bed_asset(asset);
}
}
// Set imported_time to current time if not already set
if (metadata.imported_time == 0) {
metadata.imported_time = std::time(nullptr);
@@ -1846,7 +2144,9 @@ bool PresetBundle::import_json_presets(PresetsConfigSubstitutions & s
ForwardCompatibilitySubstitutionRule rule,
int & overwrite,
std::vector<std::string> & result,
const std::string & bundle_dir)
const std::string & bundle_dir,
const std::map<std::string, std::string> & bed_asset_paths,
bool * bed_paths_loaded)
{
try {
DynamicPrintConfig config;
@@ -1910,6 +2210,10 @@ bool PresetBundle::import_json_presets(PresetsConfigSubstitutions & s
if (inherit_preset)
inherits_value = inherit_preset->name; // keep the base_id redo below in sync
}
if (collection == &printers) {
for (const auto &entry : bed_asset_paths)
config.option<ConfigOptionString>(entry.first, true)->value = entry.second;
}
if (inherit_preset) {
new_config = inherit_preset->config;
new_config.apply(std::move(config));
@@ -1927,6 +2231,8 @@ bool PresetBundle::import_json_presets(PresetsConfigSubstitutions & s
new_config.apply(std::move(config));
extend_default_config_length(new_config, true, default_preset.config);
}
if (bed_paths_loaded != nullptr && collection == &printers && !bed_asset_paths.empty())
*bed_paths_loaded = true;
Preset &preset = collection->load_preset(collection->path_from_name(name, inherit_preset == nullptr), preset_name, std::move(new_config), false);
preset.bundle_id = load_origin.bundle_id;
+3 -1
View File
@@ -319,7 +319,9 @@ public:
ForwardCompatibilitySubstitutionRule rule,
int& overwrite,
std::vector<std::string>& result,
const std::string& bundle_dir = "");
const std::string& bundle_dir = "",
const std::map<std::string, std::string> &bed_asset_paths = {},
bool *bed_paths_loaded = nullptr);
void save_user_presets(AppConfig& config, std::map<std::string, std::string>& need_to_delete_list);
void check_and_fix_user_presets_syncinfo(const std::string& user_id);
+2
View File
@@ -1148,6 +1148,8 @@ void PrintConfigDef::init_common_params()
def = this->add("print_host", coString);
def->label = L("Hostname, IP or URL");
// The URL is an example of the format to type, not a link, so it stays in the text.
// xgettext:no-url-check
def->tooltip = L("Orca Slicer can upload G-code files to a printer host. This field should contain "
"the hostname, IP address or URL of the printer host instance. "
"Print host behind HAProxy with basic auth enabled can be accessed by putting the user name and password into the URL "
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include "miniz_extension.hpp"
#include <miniz.h>
#include <nlohmann/json_fwd.hpp>
#include <string>
namespace Slic3r {
class DynamicPrintConfig;
// Not on PresetBundle.hpp: miniz.h's zlib-compat macros rename GUI methods (GLTexture::Compressor::compress).
// printer_config_entry is the zip name also pushed onto bundle_structure["printer_config"].
// printer_index is that entry's 0-based index in the exported printer_config list.
bool append_printer_bed_assets(mz_zip_archive &zip, const DynamicPrintConfig &config, nlohmann::json &bundle_structure,
const std::string &printer_config_entry, int printer_index);
} // namespace Slic3r
+32 -2
View File
@@ -65,6 +65,7 @@
#include <boost/nowide/cstdio.hpp>
#include <wx/valtext.h>
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/PrinterBedAssets.hpp"
#include "I18N.hpp"
#include "GUI_App.hpp"
#include "MsgDialog.hpp"
@@ -3674,9 +3675,20 @@ ExportConfigsDialog::~ExportConfigsDialog()
}
void ExportConfigsDialog::on_dpi_changed(const wxRect &suggested_rect) {
if (m_include_bed_assets)
m_include_bed_assets->Rescale();
Layout();
}
void ExportConfigsDialog::update_include_bed_assets_enabled()
{
const bool on = get_curr_radio_type(m_export_type_btns) == m_exprot_type.preset_bundle;
if (m_include_bed_assets)
m_include_bed_assets->Enable(on);
if (m_include_bed_assets_label)
m_include_bed_assets_label->Enable(on);
}
void ExportConfigsDialog::show_export_result(const ExportCase &export_case)
{
MessageDialog *msg_dlg = nullptr;
@@ -3819,6 +3831,17 @@ wxBoxSizer *ExportConfigsDialog::create_export_config_item(wxWindow *parent)
static_export_printer_preset_bundle_text->SetFont(Label::Body_12);
static_export_printer_preset_bundle_text->SetForegroundColour(wxColour("#6B6B6B"));
radioBoxSizer->Add(static_export_printer_preset_bundle_text, 0, wxEXPAND | wxLEFT, FromDIP(22));
wxBoxSizer *include_bed_sizer = new wxBoxSizer(wxHORIZONTAL);
m_include_bed_assets = new ::CheckBox(parent);
m_include_bed_assets->SetValue(true);
include_bed_sizer->Add(m_include_bed_assets, 0, wxALIGN_CENTER_VERTICAL, 0);
m_include_bed_assets_label = new wxStaticText(parent, wxID_ANY, _L("Include custom bed texture and model"));
m_include_bed_assets_label->SetFont(Label::Body_12);
m_include_bed_assets_label->SetForegroundColour(wxColour("#6B6B6B"));
include_bed_sizer->Add(m_include_bed_assets_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(5));
radioBoxSizer->Add(0, 0, 0, wxTOP, FromDIP(6));
radioBoxSizer->Add(include_bed_sizer, 0, wxEXPAND | wxLEFT, FromDIP(22));
update_include_bed_assets_enabled();
radioBoxSizer->Add(create_radio_item(m_exprot_type.filament_bundle, parent, wxEmptyString, m_export_type_btns), 0, wxEXPAND | wxTOP, FromDIP(10));
wxStaticText *static_export_filament_preset_bundle_text = new wxStaticText(parent, wxID_ANY, _L("User's filament preset set.\nCan be shared with others."),
wxDefaultPosition, wxDefaultSize);
@@ -3988,9 +4011,10 @@ void ExportConfigsDialog::select_curr_radiobox(std::vector<std::pair<RadioBox *,
radiobox_list[i].first->SetValue(false);
}
}
update_include_bed_assets_enabled();
}
ExportConfigsDialog::ExportCase ExportConfigsDialog::archive_preset_bundle_to_file(const wxString &path)
ExportConfigsDialog::ExportCase ExportConfigsDialog::archive_preset_bundle_to_file(const wxString &path, bool include_bed_assets)
{
std::string export_path = initial_file_path(path, "");
if (export_path.empty() || "initial_failed" == export_path) return ExportCase::EXPORT_CANCEL;
@@ -4092,6 +4116,12 @@ ExportConfigsDialog::ExportCase ExportConfigsDialog::archive_preset_bundle_to_fi
bundle_structure["filament_config"] = filament_configs;
bundle_structure["process_config"] = process_configs;
if (include_bed_assets &&
!append_printer_bed_assets(zip_archive, printer_preset->config, bundle_structure, printer_config_file_name, 0)) {
mz_zip_writer_end(&zip_archive);
return ExportCase::ADD_FILE_FAIL;
}
std::string bundle_structure_str = bundle_structure.dump();
status = mz_zip_writer_add_mem(&zip_archive, BUNDLE_STRUCTURE_JSON_NAME, bundle_structure_str.data(), bundle_structure_str.size(), MZ_DEFAULT_COMPRESSION);
if (MZ_FALSE == status) {
@@ -4339,7 +4369,7 @@ wxWindow *ExportConfigsDialog::create_dialog_buttons(wxWindow* parent)
const wxString curr_radio_type = get_curr_radio_type(m_export_type_btns);
if (curr_radio_type == m_exprot_type.preset_bundle) {
export_case = archive_preset_bundle_to_file(path);
export_case = archive_preset_bundle_to_file(path, m_include_bed_assets->GetValue());
} else if (curr_radio_type == m_exprot_type.filament_bundle) {
export_case = archive_filament_bundle_to_file(path);
} else if (curr_radio_type == m_exprot_type.printer_preset) {
+4 -1
View File
@@ -271,6 +271,7 @@ protected:
private:
void data_init();
void select_curr_radiobox(std::vector<std::pair<RadioBox *, wxString>> &radiobox_list, int btn_idx);
void update_include_bed_assets_enabled();
void on_dpi_changed(const wxRect &suggested_rect) override;
void show_export_result(const ExportCase &export_case);
bool has_check_box_selected();
@@ -284,7 +285,7 @@ private:
int initial_zip_archive(mz_zip_archive &zip_archive, const std::string &file_path);
ExportCase save_zip_archive_to_file(mz_zip_archive &zip_archive);
ExportCase save_presets_to_zip(const std::string &export_file, const std::vector<std::pair<std::string, std::string>> &config_paths);
ExportCase archive_preset_bundle_to_file(const wxString &path);
ExportCase archive_preset_bundle_to_file(const wxString &path, bool include_bed_assets);
ExportCase archive_filament_bundle_to_file(const wxString &path);
ExportCase archive_printer_preset_to_file(const wxString &path);
ExportCase archive_filament_preset_to_file(const wxString &path);
@@ -304,6 +305,8 @@ private:
wxGridSizer * m_preset_sizer = nullptr;
wxPanel * m_presets_window = nullptr;
wxStaticText * m_serial_text = nullptr;
::CheckBox * m_include_bed_assets = nullptr;
wxStaticText * m_include_bed_assets_label = nullptr;
};
class CreatePresetForPrinterDialog : public DPIDialog
+4 -3
View File
@@ -2654,9 +2654,10 @@ void GUI_App::init_webview_runtime()
BOOST_LOG_TRIVIAL(info) << "WebView2 runtime installed successfully.";
} else {
BOOST_LOG_TRIVIAL(error) << "WebView2 runtime installation failed or still not detected.";
wxMessageBox(_L("The Microsoft WebView2 Runtime could not be installed.\n"
"Some features, including the setup wizard, may appear blank until it is installed.\n"
"Please install it manually from https://developer.microsoft.com/microsoft-edge/webview2/ and restart Orca Slicer."),
wxMessageBox(format_wxstr(_L("The Microsoft WebView2 Runtime could not be installed.\n"
"Some features, including the setup wizard, may appear blank until it is installed.\n"
"Please install it manually from %1% and restart Orca Slicer."),
"https://developer.microsoft.com/microsoft-edge/webview2/"),
_L("WebView2 Runtime"), wxOK | wxICON_WARNING);
}
}
+2 -2
View File
@@ -12820,8 +12820,8 @@ void Plater::priv::on_select_bed_type(wxCommandEvent &evt)
// update plater with new config
q->on_config_change(wxGetApp().preset_bundle->full_config());
if (auto *plate_tab = dynamic_cast<TabPrintPlate *>(wxGetApp().get_plate_tab()))
plate_tab->update_model_config();
// Rebuild the plate tab from the current selection; its cached plates may be gone after a reset.
wxGetApp().obj_list()->update_and_show_object_settings_item();
// update app_config
AppConfig* app_config = wxGetApp().app_config;
+1 -1
View File
@@ -5957,7 +5957,7 @@ if (is_marlin_flavor)
{
// In the configuration group rather than a group of its own: a full-width widget line
// records no mode, so a group holding only this line would show in every mode.
auto line = Line{ L("Modes"), L("") };
auto line = Line{ L("Modes"), "" };
line.full_width = 1;
line.widget = [this](wxWindow* parent) -> wxSizer* {
int n_cols = 0, n_rows = 0, layout = 0;
+15
View File
@@ -13,6 +13,10 @@
#include <wx/gdicmn.h>
#include <wx/webview.h>
#ifdef __WXGTK__
#include <gtk/gtk.h>
#endif
namespace Slic3r { namespace GUI {
WebDialog::WebDialog(wxWindow* parent,
@@ -52,6 +56,17 @@ WebDialog::WebDialog(wxWindow* parent,
Bind(wxEVT_CLOSE_WINDOW, &WebDialog::on_close_window, this);
}
void WebDialog::use_normal_window_type()
{
#ifdef __WXGTK__
// wxGTK types every wxDialog as a dialog, and Mutter offers no maximize for anything but a
// normal window. Mutter also stops hiding a normal window from the taskbar, so keep it hidden
// as a dialog with a parent is everywhere else.
gtk_window_set_type_hint(GTK_WINDOW(m_widget), GDK_WINDOW_TYPE_HINT_NORMAL);
gtk_window_set_skip_taskbar_hint(GTK_WINDOW(m_widget), TRUE);
#endif
}
void WebDialog::add_user_scripts()
{
if (wxWebView* wv = browser()) {
+4
View File
@@ -62,6 +62,10 @@ public:
bool is_open() const { return m_open; }
// Lets a modeless window be maximized on X11; call before Show(). Modal ones stay dialogs so the
// window manager keeps routing focus from the parent to them.
void use_normal_window_type();
// The payload submitted via window.orca.submit() (modal use), if any.
const std::optional<nlohmann::json>& result() const { return m_result; }
+1
View File
@@ -197,6 +197,7 @@ bool CloudPluginService::download_cloud_plugin(PluginDescriptor& entry,
Http http = Http::get(download_url);
http.timeout_connect(30)
.timeout_max(300)
.tls_verify(true)
.on_complete([&body, &http_status](std::string response_body, unsigned status) {
body = std::move(response_body);
http_status = status;
+1
View File
@@ -399,6 +399,7 @@ py::object ui_create_window(const std::string& html, const std::string& title, i
if (UiRegistry::instance().is_open(new_id))
dlg->Destroy();
} else {
dlg->use_normal_window_type();
dlg->Show();
}
});
@@ -6,6 +6,8 @@
#include <algorithm>
#include <boost/filesystem.hpp>
#include <boost/nowide/convert.hpp>
#include <boost/nowide/fstream.hpp>
#include <cstddef>
#include <fstream>
@@ -22,6 +24,7 @@
#include "libslic3r/TriangleMesh.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/miniz_extension.hpp"
#include "libslic3r/PrinterBedAssets.hpp"
#include "test_utils.hpp"
@@ -5853,6 +5856,609 @@ TEST_CASE("Config import confines zip entries, preset names and bundle ids to th
}
}
namespace {
const std::string kTextureBytes("\x89PNG\x00tex", 8);
const std::string kModelBytes("stl\x00model", 9);
const std::string kOtherBytes("\x89PNG\x00other", 10);
void write_bytes(const std::string &utf8_path, const std::string &bytes)
{
const auto slash = utf8_path.find_last_of("/\\");
if (slash != std::string::npos) {
#ifdef _WIN32
fs::create_directories(fs::path(boost::nowide::widen(utf8_path.substr(0, slash))));
#else
fs::create_directories(utf8_path.substr(0, slash));
#endif
}
boost::nowide::ofstream out(utf8_path, std::ios::binary | std::ios::trunc);
REQUIRE(out);
out.write(bytes.data(), static_cast<std::streamsize>(bytes.size()));
REQUIRE(out);
}
fs::path write_printer_json(const DynamicPrintConfig &defaults, const fs::path &file, const std::string &name,
const std::string &texture, const std::string &model, const std::string &inherits = {})
{
DynamicPrintConfig config(defaults);
config.option<ConfigOptionString>("printer_settings_id", true)->value = name;
config.option<ConfigOptionString>("inherits", true)->value = inherits;
if (texture.empty())
config.erase("bed_custom_texture");
else
config.option<ConfigOptionString>("bed_custom_texture", true)->value = texture;
if (model.empty())
config.erase("bed_custom_model");
else
config.option<ConfigOptionString>("bed_custom_model", true)->value = model;
fs::create_directories(file.parent_path());
config.save_to_json(file.string(), name, "User", "1.0.0");
return file;
}
nlohmann::json bed_manifest(const std::string &bundle_id)
{
nlohmann::json structure = nlohmann::json::object();
structure["id"] = bundle_id;
structure["bundle_type"] = "printer config bundle";
structure["printer_config"] = nlohmann::json::array({"printer/BedPrinter.json"});
return structure;
}
void set_printer_bed_asset(nlohmann::json &structure, const std::string &option, const std::string &zip_name,
const std::string &printer_entry = "printer/BedPrinter.json")
{
structure["printer_asset"][printer_entry][option] = zip_name;
}
nlohmann::json pack_bed_zip(const fs::path &zip_file, const fs::path &printer_json, const DynamicPrintConfig &defaults,
const std::string &texture, const std::string &model, const std::string &bundle_id,
bool include_bed_assets = true)
{
DynamicPrintConfig config(defaults);
if (!texture.empty())
config.option<ConfigOptionString>("bed_custom_texture", true)->value = texture;
if (!model.empty())
config.option<ConfigOptionString>("bed_custom_model", true)->value = model;
nlohmann::json structure = bed_manifest(bundle_id);
mz_zip_archive zip;
mz_zip_zero_struct(&zip);
const std::string printer_bytes = read_file(printer_json);
REQUIRE(open_zip_writer(&zip, zip_file.string()));
REQUIRE(mz_zip_writer_add_mem(&zip, "printer/BedPrinter.json", printer_bytes.data(), printer_bytes.size(),
MZ_DEFAULT_COMPRESSION));
if (include_bed_assets)
REQUIRE(append_printer_bed_assets(zip, config, structure, "printer/BedPrinter.json", 0));
const std::string manifest = structure.dump();
REQUIRE(mz_zip_writer_add_mem(&zip, BUNDLE_STRUCTURE_JSON_NAME, manifest.data(), manifest.size(),
MZ_DEFAULT_COMPRESSION));
REQUIRE(mz_zip_writer_finalize_archive(&zip));
REQUIRE(close_zip_writer(&zip));
return structure;
}
std::vector<std::string> zip_entry_names(const fs::path &zip_file)
{
mz_zip_archive zip;
mz_zip_zero_struct(&zip);
REQUIRE(open_zip_reader(&zip, zip_file.string()));
std::vector<std::string> names;
const int n = static_cast<int>(mz_zip_reader_get_num_files(&zip));
for (int i = 0; i < n; ++i) {
mz_zip_archive_file_stat st;
REQUIRE(mz_zip_reader_file_stat(&zip, i, &st));
names.emplace_back(st.m_filename);
}
REQUIRE(close_zip_reader(&zip));
return names;
}
const Preset *import_named_printer(PresetBundle &bundle, const fs::path &zip_file, const fs::path &data_root,
const std::string &bare_name, int confirm = 1)
{
ScopedDataDir scoped(data_root);
AppConfig config;
std::vector<std::string> files{zip_file.string()};
bundle.import_presets(files, [confirm](std::string const &) { return confirm; },
ForwardCompatibilitySubstitutionRule::Disable, config);
for (const Preset &preset : bundle.printers.get_presets())
if (get_preset_bare_name(preset.name) == bare_name && !preset.file.empty())
return &preset;
return nullptr;
}
const Preset *import_bed(PresetBundle &bundle, const fs::path &zip_file, const fs::path &data_root, int confirm = 1)
{
return import_named_printer(bundle, zip_file, data_root, "BedPrinter", confirm);
}
struct BedRoots {
ScopedTemporaryDir temp_dir;
fs::path src;
fs::path data_root;
PresetBundle bundle;
BedRoots()
: src(temp_dir.path() / "src")
, data_root(temp_dir.path() / "datadir")
{}
};
} // namespace
TEST_CASE("Printer bundle round trip packs bed texture and model and rewrites both keys", "[Preset][Bundle]")
{
// tex_rel "*" builds a non-ASCII source path under the temp directory.
const auto pack_import = [](const std::string &bundle_id, const std::string &tex_rel, const std::string &mod_rel,
const std::string &tex_bytes, const std::string &mod_bytes, bool tex_exists,
bool mod_exists, int imports) {
ScopedTemporaryDir temp_dir;
const fs::path src = temp_dir.path() / "src";
const fs::path data_root = temp_dir.path() / "datadir";
std::string texture = tex_rel.empty() ? std::string() : (src / tex_rel).make_preferred().string();
std::string model = mod_rel.empty() ? std::string() : (src / mod_rel).make_preferred().string();
if (tex_rel == "*") {
const fs::path dir = fs::path(src.wstring()) / fs::path(boost::nowide::widen("\xE5\xBA\x8A"));
texture = boost::nowide::narrow((dir / fs::path(boost::nowide::widen("\xE7\xBA\xB9\xE7\x90\x86.png"))).wstring());
}
if (tex_exists)
write_bytes(texture, tex_bytes);
if (mod_exists)
write_bytes(model, mod_bytes);
PresetBundle bundle;
const fs::path printer_json = write_printer_json(bundle.printers.default_preset().config, src / "BedPrinter.json",
"BedPrinter", texture, model);
const fs::path zip_file = src / "bundle.orca_printer";
const nlohmann::json structure = pack_bed_zip(zip_file, printer_json, bundle.printers.default_preset().config,
texture, model, bundle_id);
const Preset *saved = nullptr;
for (int i = 0; i < imports; ++i)
saved = import_bed(bundle, zip_file, data_root);
REQUIRE(saved != nullptr);
const std::string got[] = {saved->config.opt_string("bed_custom_texture"), saved->config.opt_string("bed_custom_model")};
const std::string rel[] = {tex_rel, mod_rel};
const std::string bytes[] = {tex_bytes, mod_bytes};
const std::string path[] = {texture, model};
const bool exists[] = {tex_exists, mod_exists};
const char *option[] = {"bed_custom_texture", "bed_custom_model"};
const char *stem[] = {"texture", "model"};
for (int slot = 0; slot < 2; ++slot) {
if (exists[slot]) {
const std::string ext = fs::path(rel[slot] == "*" ? "x.png" : rel[slot]).extension().string();
const std::string zip_name = std::string("bed/0/") + stem[slot] + ext;
REQUIRE(structure["printer_asset"]["printer/BedPrinter.json"][option[slot]] == zip_name);
REQUIRE(fs::path(got[slot]).filename().string() == std::string(stem[slot]) + fs::path(zip_name).extension().string());
REQUIRE(fs::path(got[slot]).parent_path().filename() == "0");
REQUIRE(fs::path(got[slot]).parent_path().parent_path().filename() == "bed");
REQUIRE(got[slot].find(bundle_id) != std::string::npos);
REQUIRE(read_file(got[slot]) == bytes[slot]);
REQUIRE(got[slot] != path[slot]);
} else if (!rel[slot].empty() && rel[slot] != "*") {
REQUIRE_FALSE(structure.value("printer_asset", nlohmann::json::object())
.value("printer/BedPrinter.json", nlohmann::json::object())
.contains(option[slot]));
REQUIRE(got[slot] == path[slot]);
} else if (rel[slot].empty()) {
REQUIRE(got[slot].empty());
}
}
if (imports > 1) {
int copies = 0;
for (fs::recursive_directory_iterator it(data_root), end; it != end; ++it)
if (it->path().filename() == "texture.png" || it->path().filename() == "model.stl")
++copies;
REQUIRE(copies == (tex_exists ? 1 : 0) + (mod_exists ? 1 : 0));
}
};
SECTION("both assets are packed from the preset config and rewritten on import") {
pack_import("bed-roundtrip", "fixture/baseline_bed_texture.png", "fixture/baseline_bed_model.stl",
kTextureBytes, kModelBytes, true, true, 1);
}
SECTION("texture only") {
pack_import("texture-only", "only.png", "", kTextureBytes, "", true, false, 1);
}
SECTION("model only") {
pack_import("model-only", "", "only.stl", "", kModelBytes, false, true, 1);
}
SECTION("texture and model share a source basename") {
pack_import("shared-basename", "a/plate.png", "b/plate.stl", kTextureBytes, kOtherBytes, true, true, 1);
}
SECTION("importing the same bundle twice keeps one copy of the assets") {
// First import installs kTextureBytes / kModelBytes. The second pack uses different bytes so
// the Yes-overwrite path (copy_file overwrite_existing) is actually proven.
BedRoots bed;
const std::string texture = (bed.src / "twice.png").make_preferred().string();
const std::string model = (bed.src / "twice.stl").make_preferred().string();
write_bytes(texture, kTextureBytes);
write_bytes(model, kModelBytes);
const fs::path printer_json = write_printer_json(bed.bundle.printers.default_preset().config,
bed.src / "BedPrinter.json", "BedPrinter", texture, model);
const fs::path zip_file = bed.src / "bundle.orca_printer";
pack_bed_zip(zip_file, printer_json, bed.bundle.printers.default_preset().config, texture, model, "reimport");
const Preset *saved = import_bed(bed.bundle, zip_file, bed.data_root);
REQUIRE(saved != nullptr);
const std::string got_tex = saved->config.opt_string("bed_custom_texture");
const std::string got_mod = saved->config.opt_string("bed_custom_model");
REQUIRE(read_file(got_tex) == kTextureBytes);
REQUIRE(read_file(got_mod) == kModelBytes);
write_bytes(texture, kOtherBytes);
write_bytes(model, std::string("stl\x00other", 9));
pack_bed_zip(zip_file, printer_json, bed.bundle.printers.default_preset().config, texture, model, "reimport");
saved = import_bed(bed.bundle, zip_file, bed.data_root);
REQUIRE(saved != nullptr);
REQUIRE(saved->config.opt_string("bed_custom_texture") == got_tex);
REQUIRE(saved->config.opt_string("bed_custom_model") == got_mod);
REQUIRE(read_file(got_tex) == kOtherBytes);
REQUIRE(read_file(got_mod) == std::string("stl\x00other", 9));
}
SECTION("a non-ASCII source path is packed") {
pack_import("non-ascii", "*", "", kTextureBytes, "", true, false, 1);
}
}
TEST_CASE("An old printer bundle without printer_asset imports unchanged", "[Preset][Bundle]")
{
BedRoots bed;
const std::string texture = (bed.src / "old.png").make_preferred().string();
const std::string model = (bed.src / "old.stl").make_preferred().string();
write_bytes(texture, kTextureBytes);
write_bytes(model, kModelBytes);
const fs::path printer_json = write_printer_json(bed.bundle.printers.default_preset().config, bed.src / "BedPrinter.json",
"BedPrinter", texture, model);
nlohmann::json structure = bed_manifest("old-bundle");
REQUIRE_FALSE(structure.contains("printer_asset"));
const fs::path zip_file = bed.src / "old.orca_printer";
write_zip(zip_file, {{"printer/BedPrinter.json", read_file(printer_json)}, {BUNDLE_STRUCTURE_JSON_NAME, structure.dump()}});
// A bed/ folder left by an earlier import into the same bundle folder must survive an asset-less import.
const fs::path old_bed = bed.data_root / "user" / "default" / "_local" / "old-bundle" / "bed";
write_bytes((old_bed / "texture.png").string(), kTextureBytes);
const Preset *saved = import_bed(bed.bundle, zip_file, bed.data_root);
REQUIRE(saved != nullptr);
REQUIRE(saved->config.opt_string("bed_custom_texture") == texture);
REQUIRE(saved->config.opt_string("bed_custom_model") == model);
CHECK(fs::exists(old_bed / "texture.png"));
}
TEST_CASE("Printer bundle asset names that escape the bundle directory are rejected", "[Preset][Bundle]")
{
const char *bad_names[] = {"../../pwned.png", "bed/../../pwned.png", "..\\..\\pwned.png", "C:/Windows/pwned.png",
"machine/texture.png", "/bed/texture.png"};
const std::string marker = "PWNED-BED-ASSET-MARKER";
for (const char *name : bad_names) {
DYNAMIC_SECTION("rejects " << name) {
BedRoots bed;
const std::string texture = (bed.src / "kept.png").make_preferred().string();
const std::string model = (bed.src / "kept.stl").make_preferred().string();
write_bytes(texture, kTextureBytes);
write_bytes(model, kModelBytes);
const fs::path printer_json = write_printer_json(bed.bundle.printers.default_preset().config,
bed.src / "BedPrinter.json", "BedPrinter", texture, model);
nlohmann::json structure = bed_manifest("traverse");
set_printer_bed_asset(structure, "bed_custom_texture", name);
std::vector<std::pair<std::string, std::string>> entries = {
{"printer/BedPrinter.json", read_file(printer_json)}, {BUNDLE_STRUCTURE_JSON_NAME, structure.dump()}};
if (name[0] != '/' && name[0] != '\\')
entries.emplace_back(name, marker);
const fs::path zip_file = bed.src / "bad.orca_printer";
write_zip(zip_file, entries);
const Preset *saved = import_bed(bed.bundle, zip_file, bed.data_root);
REQUIRE(saved != nullptr);
REQUIRE(saved->config.opt_string("bed_custom_texture") == texture);
REQUIRE(saved->config.opt_string("bed_custom_model") == model);
CHECK_FALSE(any_filename_contains(bed.data_root, fs::path(name).filename().string()));
if (fs::exists(bed.data_root))
for (fs::recursive_directory_iterator it(bed.data_root), end; it != end; ++it)
if (fs::is_regular_file(*it))
CHECK(read_file(*it).find(marker) == std::string::npos);
}
}
}
TEST_CASE("A printer bundle asset that is missing is skipped", "[Preset][Bundle]")
{
SECTION("a manifest entry with no zip bytes leaves the original path") {
BedRoots bed;
const std::string texture = (bed.src / "gone.png").make_preferred().string();
write_bytes(texture, kTextureBytes);
const fs::path printer_json = write_printer_json(bed.bundle.printers.default_preset().config, bed.src / "BedPrinter.json",
"BedPrinter", texture, {});
nlohmann::json structure = bed_manifest("missing-zip");
set_printer_bed_asset(structure, "bed_custom_texture", "bed/0/texture.png");
const fs::path zip_file = bed.src / "missing.orca_printer";
write_zip(zip_file, {{"printer/BedPrinter.json", read_file(printer_json)}, {BUNDLE_STRUCTURE_JSON_NAME, structure.dump()}});
const Preset *saved = import_bed(bed.bundle, zip_file, bed.data_root);
REQUIRE(saved != nullptr);
REQUIRE(saved->config.opt_string("bed_custom_texture") == texture);
CHECK_FALSE(any_filename_contains(bed.data_root, "texture.png"));
}
SECTION("a missing file at export is omitted and the export still succeeds") {
BedRoots bed;
const std::string missing = (bed.src / "missing.png").make_preferred().string();
const std::string model = (bed.src / "present.stl").make_preferred().string();
write_bytes(model, kModelBytes);
const fs::path printer_json = write_printer_json(bed.bundle.printers.default_preset().config, bed.src / "BedPrinter.json",
"BedPrinter", missing, model);
const fs::path zip_file = bed.src / "partial.orca_printer";
const nlohmann::json structure = pack_bed_zip(zip_file, printer_json, bed.bundle.printers.default_preset().config,
missing, model, "missing-export");
REQUIRE_FALSE(structure["printer_asset"]["printer/BedPrinter.json"].contains("bed_custom_texture"));
REQUIRE(structure["printer_asset"]["printer/BedPrinter.json"]["bed_custom_model"] == "bed/0/model.stl");
const Preset *saved = import_bed(bed.bundle, zip_file, bed.data_root);
REQUIRE(saved != nullptr);
REQUIRE(saved->config.opt_string("bed_custom_texture") == missing);
REQUIRE(read_file(saved->config.opt_string("bed_custom_model")) == kModelBytes);
REQUIRE(fs::path(saved->config.opt_string("bed_custom_model")).filename() == "model.stl");
CHECK_FALSE(any_filename_contains(bed.data_root, "texture.png"));
}
}
TEST_CASE("An inherited printer bundle rewrites bed paths taken from the parent", "[Preset][Bundle]")
{
BedRoots bed;
const std::string texture = (bed.src / "parent.png").make_preferred().string();
const std::string model = (bed.src / "parent.stl").make_preferred().string();
write_bytes(texture, kTextureBytes);
write_bytes(model, kModelBytes);
DynamicPrintConfig parent(bed.bundle.printers.default_preset().config);
parent.option<ConfigOptionString>("printer_settings_id", true)->value = "ParentPrinter";
parent.option<ConfigOptionString>("inherits", true)->value.clear();
parent.option<ConfigOptionString>("bed_custom_texture", true)->value = texture;
parent.option<ConfigOptionString>("bed_custom_model", true)->value = model;
bed.bundle.printers.load_preset((bed.src / "ParentPrinter.json").string(), "ParentPrinter", std::move(parent), false);
const fs::path child_json = write_printer_json(bed.bundle.printers.default_preset().config, bed.src / "BedPrinter.json",
"BedPrinter", {}, {}, "ParentPrinter");
const nlohmann::json written = nlohmann::json::parse(read_file(child_json));
REQUIRE_FALSE(written.contains("bed_custom_texture"));
REQUIRE_FALSE(written.contains("bed_custom_model"));
nlohmann::json structure = bed_manifest("inherit-bed");
set_printer_bed_asset(structure, "bed_custom_texture", "bed/0/texture.png");
set_printer_bed_asset(structure, "bed_custom_model", "bed/0/model.stl");
const fs::path zip_file = bed.src / "inherit.orca_printer";
write_zip(zip_file, {{"printer/BedPrinter.json", read_file(child_json)}, {BUNDLE_STRUCTURE_JSON_NAME, structure.dump()},
{"bed/0/texture.png", kTextureBytes}, {"bed/0/model.stl", kModelBytes}});
const Preset *saved = import_bed(bed.bundle, zip_file, bed.data_root);
REQUIRE(saved != nullptr);
REQUIRE(saved->config.opt_string("bed_custom_texture") != texture);
REQUIRE(saved->config.opt_string("bed_custom_model") != model);
REQUIRE(read_file(saved->config.opt_string("bed_custom_texture")) == kTextureBytes);
REQUIRE(read_file(saved->config.opt_string("bed_custom_model")) == kModelBytes);
REQUIRE(fs::path(saved->config.opt_string("bed_custom_texture")).filename() == "texture.png");
REQUIRE(fs::path(saved->config.opt_string("bed_custom_model")).filename() == "model.stl");
}
TEST_CASE("A printer bundle rejects bed assets whose extension the bed dialog does not allow", "[Preset][Bundle]")
{
BedRoots bed;
const fs::path printer_json = write_printer_json(bed.bundle.printers.default_preset().config, bed.src / "BedPrinter.json",
"BedPrinter", {}, {});
nlohmann::json structure = bed_manifest("bad-ext");
set_printer_bed_asset(structure, "bed_custom_texture", "bed/0/texture.stl");
set_printer_bed_asset(structure, "bed_custom_model", "bed/0/model.exe");
const fs::path zip_file = bed.src / "bad-ext.orca_printer";
write_zip(zip_file, {{"printer/BedPrinter.json", read_file(printer_json)}, {BUNDLE_STRUCTURE_JSON_NAME, structure.dump()},
{"bed/0/texture.stl", kTextureBytes}, {"bed/0/model.exe", std::string("MZ")}});
REQUIRE(import_bed(bed.bundle, zip_file, bed.data_root) != nullptr);
CHECK_FALSE(any_filename_contains(bed.data_root, "model.exe"));
CHECK_FALSE(any_filename_contains(bed.data_root, "texture.stl"));
}
TEST_CASE("A manifest asset name that points at a preset does not drop that preset", "[Preset][Bundle]")
{
BedRoots bed;
const fs::path printer_json = write_printer_json(bed.bundle.printers.default_preset().config, bed.src / "BedPrinter.json",
"BedPrinter", {}, {});
nlohmann::json structure = bed_manifest("name-clash");
set_printer_bed_asset(structure, "bed_custom_texture", "printer/BedPrinter.json");
const fs::path zip_file = bed.src / "clash.orca_printer";
write_zip(zip_file, {{"printer/BedPrinter.json", read_file(printer_json)}, {BUNDLE_STRUCTURE_JSON_NAME, structure.dump()}});
REQUIRE(import_bed(bed.bundle, zip_file, bed.data_root) != nullptr);
CHECK_FALSE(any_filename_contains(bed.data_root, "texture.png"));
}
TEST_CASE("A declined printer bundle import leaves existing bed files alone", "[Preset][Bundle]")
{
BedRoots bed;
const fs::path kept = bed.data_root / "user" / "default" / "_local" / "keep-bed" / "bed" / "texture.png";
const std::string original("kept-texture");
write_bytes(kept.string(), original);
DynamicPrintConfig existing(bed.bundle.printers.default_preset().config);
existing.option<ConfigOptionString>("printer_settings_id", true)->value = "BedPrinter";
existing.option<ConfigOptionString>("inherits", true)->value.clear();
bed.bundle.printers.load_preset((bed.src / "existing.json").string(), "_local/keep-bed/BedPrinter", std::move(existing),
false);
const fs::path printer_json = write_printer_json(bed.bundle.printers.default_preset().config, bed.src / "BedPrinter.json",
"BedPrinter", {}, {});
nlohmann::json structure = bed_manifest("keep-bed");
set_printer_bed_asset(structure, "bed_custom_texture", "bed/0/texture.png");
set_printer_bed_asset(structure, "bed_custom_model", "bed/0/model.stl");
const fs::path zip_file = bed.src / "keep.orca_printer";
write_zip(zip_file, {{"printer/BedPrinter.json", read_file(printer_json)}, {BUNDLE_STRUCTURE_JSON_NAME, structure.dump()},
{"bed/0/texture.png", kTextureBytes}, {"bed/0/model.stl", kModelBytes}});
import_bed(bed.bundle, zip_file, bed.data_root, 0);
// This import wrote nothing: the existing texture is unchanged and model.stl was not added.
REQUIRE(fs::exists(kept));
REQUIRE(read_file(kept) == original);
CHECK_FALSE(fs::exists(kept.parent_path() / "model.stl"));
CHECK_FALSE(fs::exists(kept.parent_path() / "0" / "model.stl"));
}
TEST_CASE("A declined re-import does not replace bed bytes an earlier import wrote", "[Preset][Bundle]")
{
BedRoots bed;
const fs::path printer_json = write_printer_json(bed.bundle.printers.default_preset().config, bed.src / "BedPrinter.json",
"BedPrinter", {}, {});
nlohmann::json structure = bed_manifest("reimport-no");
set_printer_bed_asset(structure, "bed_custom_texture", "bed/0/texture.png");
const std::string manifest = structure.dump();
const std::string printer_bytes = read_file(printer_json);
const fs::path first_zip = bed.src / "first.orca_printer";
const fs::path second_zip = bed.src / "second.orca_printer";
write_zip(first_zip, {{"printer/BedPrinter.json", printer_bytes}, {BUNDLE_STRUCTURE_JSON_NAME, manifest},
{"bed/0/texture.png", kTextureBytes}});
write_zip(second_zip, {{"printer/BedPrinter.json", printer_bytes}, {BUNDLE_STRUCTURE_JSON_NAME, manifest},
{"bed/0/texture.png", kOtherBytes}});
const Preset *saved = import_bed(bed.bundle, first_zip, bed.data_root);
REQUIRE(saved != nullptr);
const std::string installed = saved->config.opt_string("bed_custom_texture");
REQUIRE(read_file(installed) == kTextureBytes);
import_bed(bed.bundle, second_zip, bed.data_root, 0);
REQUIRE(fs::exists(installed));
REQUIRE(read_file(installed) == kTextureBytes);
}
TEST_CASE("Export with bed assets disabled packs no bed files and writes no printer_asset", "[Preset][Bundle]")
{
BedRoots bed;
const std::string texture = (bed.src / "off.png").make_preferred().string();
const std::string model = (bed.src / "off.stl").make_preferred().string();
write_bytes(texture, kTextureBytes);
write_bytes(model, kModelBytes);
const fs::path printer_json = write_printer_json(bed.bundle.printers.default_preset().config, bed.src / "BedPrinter.json",
"BedPrinter", texture, model);
const fs::path zip_file = bed.src / "off.orca_printer";
const nlohmann::json structure = pack_bed_zip(zip_file, printer_json, bed.bundle.printers.default_preset().config,
texture, model, "bed-off", false);
REQUIRE_FALSE(structure.contains("printer_asset"));
for (const std::string &name : zip_entry_names(zip_file))
REQUIRE(name.compare(0, 4, "bed/") != 0);
}
TEST_CASE("Two printers in one bundle keep separate bed folders", "[Preset][Bundle]")
{
BedRoots bed;
const std::string tex_a = (bed.src / "a.png").make_preferred().string();
const std::string mod_a = (bed.src / "a.stl").make_preferred().string();
const std::string tex_b = (bed.src / "b.png").make_preferred().string();
const std::string mod_b = (bed.src / "b.stl").make_preferred().string();
write_bytes(tex_a, kTextureBytes);
write_bytes(mod_a, kModelBytes);
write_bytes(tex_b, kOtherBytes);
write_bytes(mod_b, std::string("stl\x00other", 9));
const fs::path json_a = write_printer_json(bed.bundle.printers.default_preset().config, bed.src / "PrinterA.json",
"PrinterA", tex_a, mod_a);
const fs::path json_b = write_printer_json(bed.bundle.printers.default_preset().config, bed.src / "PrinterB.json",
"PrinterB", tex_b, mod_b);
nlohmann::json structure = nlohmann::json::object();
structure["id"] = "two-printers";
structure["bundle_type"] = "printer config bundle";
structure["printer_config"] = nlohmann::json::array({"printer/PrinterA.json", "printer/PrinterB.json"});
DynamicPrintConfig config_a(bed.bundle.printers.default_preset().config);
config_a.option<ConfigOptionString>("bed_custom_texture", true)->value = tex_a;
config_a.option<ConfigOptionString>("bed_custom_model", true)->value = mod_a;
DynamicPrintConfig config_b(bed.bundle.printers.default_preset().config);
config_b.option<ConfigOptionString>("bed_custom_texture", true)->value = tex_b;
config_b.option<ConfigOptionString>("bed_custom_model", true)->value = mod_b;
const fs::path zip_file = bed.src / "two.orca_printer";
mz_zip_archive zip;
mz_zip_zero_struct(&zip);
const std::string bytes_a = read_file(json_a);
const std::string bytes_b = read_file(json_b);
REQUIRE(open_zip_writer(&zip, zip_file.string()));
REQUIRE(mz_zip_writer_add_mem(&zip, "printer/PrinterA.json", bytes_a.data(), bytes_a.size(), MZ_DEFAULT_COMPRESSION));
REQUIRE(mz_zip_writer_add_mem(&zip, "printer/PrinterB.json", bytes_b.data(), bytes_b.size(), MZ_DEFAULT_COMPRESSION));
REQUIRE(append_printer_bed_assets(zip, config_a, structure, "printer/PrinterA.json", 0));
REQUIRE(append_printer_bed_assets(zip, config_b, structure, "printer/PrinterB.json", 1));
const std::string manifest = structure.dump();
REQUIRE(mz_zip_writer_add_mem(&zip, BUNDLE_STRUCTURE_JSON_NAME, manifest.data(), manifest.size(),
MZ_DEFAULT_COMPRESSION));
REQUIRE(mz_zip_writer_finalize_archive(&zip));
REQUIRE(close_zip_writer(&zip));
REQUIRE(structure["printer_asset"]["printer/PrinterA.json"]["bed_custom_texture"] == "bed/0/texture.png");
REQUIRE(structure["printer_asset"]["printer/PrinterB.json"]["bed_custom_texture"] == "bed/1/texture.png");
ScopedDataDir scoped(bed.data_root);
AppConfig app_config;
std::vector<std::string> files{zip_file.string()};
bed.bundle.import_presets(files, [](std::string const &) { return 1; },
ForwardCompatibilitySubstitutionRule::Disable, app_config);
const Preset *saved_a = nullptr;
const Preset *saved_b = nullptr;
for (const Preset &preset : bed.bundle.printers.get_presets()) {
if (preset.file.empty())
continue;
if (get_preset_bare_name(preset.name) == "PrinterA")
saved_a = &preset;
if (get_preset_bare_name(preset.name) == "PrinterB")
saved_b = &preset;
}
REQUIRE(saved_a != nullptr);
REQUIRE(saved_b != nullptr);
const std::string got_tex_a = saved_a->config.opt_string("bed_custom_texture");
const std::string got_tex_b = saved_b->config.opt_string("bed_custom_texture");
REQUIRE(fs::path(got_tex_a).parent_path().filename() == "0");
REQUIRE(fs::path(got_tex_b).parent_path().filename() == "1");
REQUIRE(got_tex_a != got_tex_b);
REQUIRE(read_file(got_tex_a) == kTextureBytes);
REQUIRE(read_file(got_tex_b) == kOtherBytes);
REQUIRE(read_file(saved_a->config.opt_string("bed_custom_model")) == kModelBytes);
REQUIRE(read_file(saved_b->config.opt_string("bed_custom_model")) == std::string("stl\x00other", 9));
}
TEST_CASE("A printer_asset entry keyed to a printer not in the zip is ignored", "[Preset][Bundle]")
{
BedRoots bed;
const std::string texture = (bed.src / "kept.png").make_preferred().string();
write_bytes(texture, kTextureBytes);
const fs::path printer_json = write_printer_json(bed.bundle.printers.default_preset().config, bed.src / "BedPrinter.json",
"BedPrinter", texture, {});
nlohmann::json structure = bed_manifest("ghost-key");
set_printer_bed_asset(structure, "bed_custom_texture", "bed/0/texture.png", "printer/Ghost.json");
const fs::path zip_file = bed.src / "ghost.orca_printer";
write_zip(zip_file, {{"printer/BedPrinter.json", read_file(printer_json)}, {BUNDLE_STRUCTURE_JSON_NAME, structure.dump()},
{"bed/0/texture.png", kOtherBytes}});
const Preset *saved = import_bed(bed.bundle, zip_file, bed.data_root);
REQUIRE(saved != nullptr);
REQUIRE(saved->config.opt_string("bed_custom_texture") == texture);
CHECK_FALSE(any_filename_contains(bed.data_root, "texture.png"));
}
TEST_CASE("Keyed printer_asset rejects malformed bed paths", "[Preset][Bundle]")
{
const char *bad_names[] = {"bed/texture.png", "bed/x/texture.png", "bed/0/../texture.png", "bed/0/model.png"};
const std::string marker = "PWNED-BED-ASSET-MARKER";
for (const char *name : bad_names) {
DYNAMIC_SECTION("rejects " << name) {
BedRoots bed;
const std::string texture = (bed.src / "kept.png").make_preferred().string();
write_bytes(texture, kTextureBytes);
const fs::path printer_json = write_printer_json(bed.bundle.printers.default_preset().config,
bed.src / "BedPrinter.json", "BedPrinter", texture, {});
nlohmann::json structure = bed_manifest("bad-keyed");
set_printer_bed_asset(structure, "bed_custom_texture", name);
std::vector<std::pair<std::string, std::string>> entries = {
{"printer/BedPrinter.json", read_file(printer_json)}, {BUNDLE_STRUCTURE_JSON_NAME, structure.dump()},
{name, marker}};
const fs::path zip_file = bed.src / "bad-keyed.orca_printer";
write_zip(zip_file, entries);
const Preset *saved = import_bed(bed.bundle, zip_file, bed.data_root);
REQUIRE(saved != nullptr);
REQUIRE(saved->config.opt_string("bed_custom_texture") == texture);
if (fs::exists(bed.data_root))
for (fs::recursive_directory_iterator it(bed.data_root), end; it != end; ++it)
if (fs::is_regular_file(*it))
CHECK(read_file(*it).find(marker) == std::string::npos);
}
}
SECTION("field name does not match file kind") {
BedRoots bed;
const std::string texture = (bed.src / "kept.png").make_preferred().string();
write_bytes(texture, kTextureBytes);
const fs::path printer_json = write_printer_json(bed.bundle.printers.default_preset().config, bed.src / "BedPrinter.json",
"BedPrinter", texture, {});
nlohmann::json structure = bed_manifest("kind-mismatch");
set_printer_bed_asset(structure, "bed_custom_texture", "bed/0/model.stl");
const fs::path zip_file = bed.src / "kind.orca_printer";
write_zip(zip_file, {{"printer/BedPrinter.json", read_file(printer_json)}, {BUNDLE_STRUCTURE_JSON_NAME, structure.dump()},
{"bed/0/model.stl", kModelBytes}});
const Preset *saved = import_bed(bed.bundle, zip_file, bed.data_root);
REQUIRE(saved != nullptr);
REQUIRE(saved->config.opt_string("bed_custom_texture") == texture);
CHECK_FALSE(any_filename_contains(bed.data_root, "model.stl"));
}
}
TEST_CASE("A saved printer preset reloads the tool layout it was saved with", "[Preset][Bundle][IMEX]")
{
ScopedTemporaryDir temp_dir;