Compare commits

...

119 Commits

Author SHA1 Message Date
SoftFever
3546dafc18 update doc 2026-06-03 22:41:30 +08:00
SoftFever
2e4cbd4511 fix(automation): view.select 'device' falls back to m_printer_view for third-party printers 2026-06-03 21:45:44 +08:00
SoftFever
411787afb2 docs(automation): add view.select client wrapper, example usage, and docs 2026-06-03 21:37:51 +08:00
SoftFever
00bb52bcd7 fix(automation): select_tab_by_name resolves prepare/preview by page window (viewer-mode safe) 2026-06-03 21:36:03 +08:00
SoftFever
1a960b59ea feat(automation): implement view.select via MainFrame::select_tab_by_name 2026-06-03 21:26:40 +08:00
SoftFever
9a16fb7c2e feat(automation): add view.select dispatcher handler + tests 2026-06-03 21:18:50 +08:00
SoftFever
7ef89fdb9d docs(automation): document file.open method and error 1007 2026-06-03 20:22:17 +08:00
SoftFever
3d813d529e docs(automation): example_slice.py loads model at runtime via file.open 2026-06-03 20:20:25 +08:00
SoftFever
151927ac00 feat(automation): add OrcaClient.open() wrapper for file.open 2026-06-03 20:18:31 +08:00
SoftFever
3f1a2a71bd feat(automation): implement WxUiBackend::open_files via Plater::load_files 2026-06-03 20:14:29 +08:00
SoftFever
b87dd23c74 feat(automation): advertise file.open in automation.version capabilities 2026-06-03 20:06:44 +08:00
SoftFever
cea46ddc7f feat(automation): add file.open dispatcher handler with validation + tests 2026-06-03 20:01:23 +08:00
SoftFever
b3d7a732c5 feat(automation): add open_files to backend interface + kErrLoadFailed (1007) 2026-06-03 19:55:46 +08:00
SoftFever
b70be9178e docs(automation): design spec for file.open runtime model-loading method
Adds the approved design for a `file.open` JSON-RPC method that loads
files into a running OrcaSlicer via Plater::load_files (synchronous, GUI
thread). Dialog-driving approaches deferred.
2026-06-03 18:57:20 +08:00
SoftFever
892b33bac5 refactor(automation): drop screenshot.viewport3d, keep only screenshot.window
The on-screen window capture is composited from the desktop framebuffer, so it
already includes the GL 3D viewport as currently shown (model in the editor,
toolpaths in Preview). The offscreen render_thumbnail path only ever drew the
model GLVolumeCollection — never the gcode toolpaths — and produced a blank image
after slicing because the app switches to the Preview panel. Rather than maintain a
second, more limited capture method, remove it entirely.

Removes the JSON-RPC method, IUiBackend/WxUiBackend implementation, dispatcher
route + capability entry, the now-dead opt_int/thumbnail_to_wximage helpers and
ThumbnailData include, the mock override + unit test, and the Python
screenshot_3d client method. Docs updated accordingly.
2026-06-03 18:05:23 +08:00
SoftFever
952696fd1f fix(automation): capture screenshot.window from the composited screen
Blitting from the MainFrame's own wxClientDC clips out child HWNDs, so all of
OrcaSlicer's custom child-window controls (sidebar buttons/combos/panels) and the
GL canvas came back as uninitialized black bitmap memory. Read the window's
on-screen rectangle from the DWM-composited desktop via wxScreenDC instead, which
includes every child window, the OpenGL surface, and ImGui overlays.

Document the visible/unobscured requirement and the HiDPI logical-vs-physical
pixel caveat; clarify how screenshot.viewport3d differs and why it stays.
2026-06-03 17:19:36 +08:00
SoftFever
6980d9c327 plan 2026-06-03 09:50:34 +08:00
SoftFever
45e93951c1 test(automation): regression verification — unit suites green + disabled-path no-op audit 2026-06-03 04:26:25 +08:00
SoftFever
a4cedde163 docs(automation): protocol reference, ids, and platform caveats 2026-06-03 04:19:20 +08:00
SoftFever
61b4131aee feat(automation): runnable e2e slice example / smoke test 2026-06-03 04:14:24 +08:00
SoftFever
6eb479243d feat(automation): instrument core widgets with stable automation ids 2026-06-03 04:09:42 +08:00
SoftFever
622272e674 feat(automation): GUI_App owns automation server lifecycle (opt-in) 2026-06-03 03:58:01 +08:00
SoftFever
b54cc75362 feat(automation): --automation-server CLI flag plumbed into GUI 2026-06-03 03:48:47 +08:00
SoftFever
9d915c4e76 feat(automation): WxUiBackend input + window/viewport screenshots 2026-06-03 03:35:40 +08:00
SoftFever
d742b10c50 feat(automation): WxUiBackend marshaller + dump_tree + app_state 2026-06-03 03:19:29 +08:00
SoftFever
47467b626c feat(automation): guarded ImGui item/window recording hooks 2026-06-03 03:01:58 +08:00
SoftFever
c0d37bff3a feat(automation): double-buffered ImGui item table 2026-06-03 02:52:52 +08:00
SoftFever
39a29cf865 feat(automation): wxWindow automation-id registry 2026-06-03 02:43:41 +08:00
SoftFever
487e1cb205 feat(automation): localhost beast POST /jsonrpc server 2026-06-03 02:32:51 +08:00
SoftFever
a2e8a90052 feat(automation): Python reference client 2026-06-03 02:23:10 +08:00
SoftFever
8dcbc582fa feat(automation): sync.wait_for poll loop 2026-06-03 02:13:35 +08:00
SoftFever
b0325c999a feat(automation): app.state + screenshot handlers with base64 2026-06-03 02:07:30 +08:00
SoftFever
5a2f03adee feat(automation): input.click / input.type / input.key handlers 2026-06-03 02:01:40 +08:00
SoftFever
a8ed2b8dd5 feat(automation): tree.dump / tree.find / widget.get handlers 2026-06-03 01:55:48 +08:00
SoftFever
aac14ae161 feat(automation): JSON-RPC dispatcher envelope + version + error model 2026-06-03 01:49:49 +08:00
SoftFever
94c356845e test(automation): MockUiBackend recording test double 2026-06-03 01:45:46 +08:00
SoftFever
e449a0b618 feat(automation): resolve_unique + wait-state evaluation 2026-06-03 01:42:38 +08:00
SoftFever
ddd1967bff feat(automation): pure locator (id/path/predicate) with unit tests 2026-06-03 01:38:13 +08:00
SoftFever
02140d2a1e test(automation): lock serializer children/value/imgui/app_state shapes 2026-06-03 01:34:13 +08:00
SoftFever
0be138b981 feat(automation): pure UI node model + JSON serializer with unit test 2026-06-03 01:28:43 +08:00
SoftFever
11301086a7 docs: add UI automation design spec
Design for an opt-in localhost JSON-RPC server that lets an external script/AI agent drive and observe the OrcaSlicer GUI (wxWidgets + 3D viewport + ImGui) via wxUIActionSimulator.
2026-06-03 00:48:27 +08:00
Kiss Lorand
1b72dbf6fa Fix inconsistent ordering of support base outline and fill (#11761)
* Preserve support base outline/fill order

Honor no_sort when emitting support toolpaths to keep outline-first order.
Group tree support base paths (including lightning) into per-area no_sort collections to prevent interleaving across islands.
Keep lightning layer lookup side-effect free.

* Tag Orca specific changes

Tag Orca specific changes vs. Bambu using the comment //ORCA: . This helps when reviewing merge commits from upstream Bambu so we don't end up causing regressions when pulling in commits from upstream
2026-06-02 14:39:27 +08:00
Kiss Lorand
ac92125012 Fix counterbore hole bridge (#13956)
Fix counterbore hole partial bridge
2026-06-02 14:34:43 +08:00
Bingo2023
d6a49ace15 Update Bambu Lab X2D 0.4 nozzle.json (#13985)
* Update Bambu Lab X2D 0.4 nozzle.json

corrected mistake from 31.5.2026
" is correct in machine code.

* Update Bambu Lab X2D 0.4 nozzle.json

fixed error with " -> \"

* Merge branch 'main' into patch-2
2026-06-01 22:15:34 +08:00
yw4z
71eebc2332 Merge code base of Setup Guide and Standalone versions of Printer / Filament Selection Dialogs (#13579) 2026-06-01 21:00:42 +08:00
Ian Bassi
7a0c149701 lightning infill angles Wiki (#13984) 2026-06-01 09:24:00 -03:00
Alexandre Folle de Menezes
737c684a93 Improve and complement pt-BR translation (#13973) 2026-06-01 09:12:06 -03:00
Terasit Juntarasombut
91ce821959 i18n: Complete Thai (th) localization and enable Thai in GUI (#13916)
* i18n: complete Thai (th) localization

* feat: fix thi translation

* feat: fix Thai language localization file

---------

Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-06-01 18:11:14 +08:00
Kiss Lorand
752551292b Fix modifiers bridge speed not respected (#13971)
* Respect modifiers bridge speed
2026-06-01 16:00:00 +08:00
Calcousin55
86ad4d141a Preserve Z scale and apply correct offset when Ctrl‑dragging XY corner (#13840) 2026-06-01 00:16:06 +08:00
Vovodroid
30e83d790c Enable wipe with firmware retractions is set 100% before wipe. (#13812)
Enable wipe with firmware retractions
2026-06-01 00:14:42 +08:00
LH
7f7e7dff3a Update LH Stinger profile (#13865)
- Adding default speed for the new initial_layer_travel_acceleration
2026-06-01 00:13:35 +08:00
Rodrigo Faselli
8548e5ca96 Expose lightning infill angles (#13848)
* expose lightning infill angles

Update PrintObject.cpp

* Update src/libslic3r/PrintConfig.cpp

* Apply suggestions from code review

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Update GUI_Factories.cpp

* Fix lightning infill angles to 45 degrees for supports

Updated lightning infill angles to fixed values for consistency.

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-01 00:12:49 +08:00
anjis
4d05ba0d02 Support GIGA multi-printhead configuration options (#13901)
* Support GIGA multi-printhead configuration options

* Removed unused G-code comments.

* Added parallel_printheads_count option to configure parallel printhead count.

---------

Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-06-01 00:11:07 +08:00
Noisyfox
3cce9b09ed Cut: Make sure x offset is considered when checking multi dovetail (#13945)
Make sure x offset is considered when checking multi dovetail (OrcaSlicer/OrcaSlicer#13940)
2026-05-31 22:34:32 +08:00
Matias Fernandez
8362eba19d Remove redundant toolchange retraction/un-retraction during filament change in CC1 profile (#13456)
* Fix: Disable redundant toolchange retraction for Elegoo Centauri Carbon
Sets `retract_length_toolchange` to 0 in the Elegoo Centauri Carbon (ECC) machine profile.
This resolves an issue where a massive filament blob would form on the prime tower immediately after resuming a manual filament change (M600). The blob was caused by a conflict between OrcaSlicer's default toolchange logic and Elegoo's hardcoded firmware behavior:
- Elegoo's firmware (specifically the `cmd_PAUSE` and `cmd_RESUME` sequences) completely takes over pressure management during an M600. It performs its own initial 2mm retraction, a 120mm purge, and a silicone brush wipe, returning the print head to the prime tower perfectly primed.
- Previously, Slicer was unaware of the firmware's priming and would issue a redundant 2mm un-retract (`G1 E2`) upon resume. Forcing 2mm of filament out of an already-full nozzle created the blob.
By disabling the toolchange retraction (`0`), Slicer correctly hands off filament pressure management during an M600 entirely to the Elegoo firmware, preventing double-retractions and eliminating the blob.

* fix errors after merging main

---------

Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-05-31 20:35:35 +08:00
yw4z
1644d49ae1 Fix non functional OBJ import dialog on linux (#13914) 2026-05-31 20:16:29 +08:00
Kiss Lorand
3a8dfeaa08 Preview sliders overhaul (#13919)
* Make preview slider labels draggable

Add label hit testing and delta-based dragging for the vertical preview slider labels. Keep label drags tied to the selected handle, prevent slider hover/timeline/menu handling from stealing label interactions, and keep value setters from changing the active selection implicitly.

* Refresh preview slider visuals

Update preview slider rails, handles, and labels for the refreshed light and dark theme appearance. Apply the same visual language to the horizontal slider, align single-layer and multi-layer labels, and remove obsolete triangle label geometry.
2026-05-31 18:55:14 +08:00
Bingo2023
c714935596 Update Bambu Lab X2D 0.4 nozzle.json - updade Maschine Startcode (#13944)
* Update Bambu Lab X2D 0.4 nozzle.json - updade Maschine Startcode

bump in changes from BBL

update:
===== 2026/05/08 =====

old:
===== 2026/03/26 =====

* Update Bambu Lab X2D 0.4 nozzle.json

corrected:
- " " -> ' '
- {Tab} -> deleted

* Update Bambu Lab X2D 0.4 nozzle.json

replaced " -> '
2026-05-31 18:46:46 +08:00
SoftFever
372f7823ac Feature/tweak stealth mode (#13963)
* Update the stealth mode description to reflect the current code changes in 2.4.

* disable HMS if bambu network plugin is not installed or in stealth mode

* fix build err

* add hide_login_side_panel to control whether to show login panel in home page
2026-05-31 18:04:06 +08:00
Ian Chua
535911fcfe fix: 409 conflicts resolution in notifications (#13900)
* fix: 409 conflicts resolution in notifications

* fix: silently log other http errors

* fix: pass force push flag to start_sync_user_preset

* remove formatting churn

* fix: propagate force push down put_setting

* refactor render_hyperlink_action to PopNotification for reuse

* fix an issue that hold status should be cleared before force pushing.

---------

Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-05-31 16:23:10 +08:00
Kiss Lorand
6a26284ba6 Fix air filtration gcode emitted even if not not supported by the printer (#13868)
* Fix air filtration gcode emitted even if not not supported

- do not emit air filtration gcode if not supported by the printer
- removed redundant  "add_eol" parameter from "set_exhaust_fan()" function
2026-05-31 12:11:40 +08:00
SoftFever
b78d5b94dc Add SeeMeCNC printer profiles (#13924)
* Add SeeMeCNC printer profiles

Adds machine, filament, and process profiles for SeeMeCNC printers:
Artemis, BOSSdelta 300, BOSSdelta 500 (0505/0510/0521), and
RostockMAX v3.2/v4 — covering 0.4mm, 0.5mm, 0.7mm, and 1.0mm nozzles.
Includes PLA, ABS, PETG, PETG-CF, PA-CF, and TPU filament profiles.

Co-authored-by: Sam <47287571+Sewbot@users.noreply.github.com>

* bump version

---------

Co-authored-by: Sam <47287571+Sewbot@users.noreply.github.com>
2026-05-30 22:06:54 +08:00
Wegerich
61e2abfb2b Enhance G92 E0 case sensitivity check (#13933) 2026-05-30 21:57:54 +08:00
Tobias Gloth
9d8c7cc495 use /bigobj when building Eigen with MSVC (#13922) 2026-05-30 16:51:31 +08:00
Ian Bassi
9a01df4a80 Filament for features for SEMM (#13937)
* Remove limit for SEMM

* Remove legacy check

* Revert "Remove legacy check"

This reverts commit c6e5074114.
2026-05-30 14:16:59 +08:00
Kiss Lorand
83946f3df8 Fix division by zero in organic tree support generation (#13936) 2026-05-29 18:09:47 -03:00
Ian Chua
6b55e324c9 fix: temporary wxstring going out of scope causing crash on linux (#13925) 2026-05-29 15:49:36 +08:00
Jingxi-Polymaker
ee8bb54ca8 Add more Polymaker filament presets (#13858)
* add new presets

* fix inhernce

* fix rename

* update

* apply fix

* update index
2026-05-29 15:46:41 +08:00
Ian Bassi
3db37d004a Fix + Support 'Default' filament option (index 0) (#13887)
* Support 'Default' filament option (index 0)

Treat filament index 0 as the new "Default" (use active object/part filament) instead of using 1. Update config defaults and tooltips for wall/sparse/solid infill filament options (min/default -> 0, tooltip explains "Default"). Adjust normalization and propagation logic to respect explicit feature overrides and only apply base extruder when feature values are zero; only copy sparse->solid infill when sparse > 0. Introduce FeatureFilamentOverrideMask and clamp_feature_filament_to_valid to resolve and clamp feature filaments. Update UI lists and selection behavior to expose a "Default" entry and handle zero-based indices in PartPlate and Plater.

* enable_filament_for_features option

Co-Authored-By: LixNix <105106115+lixnix@users.noreply.github.com>

* \n

* Allow wipe_tower_filament to equal nozzle count

Relax the assertion in Print::extruders to permit wipe_tower_filament == config().nozzle_diameter.size(). The configuration value is 1-based and the code subtracts 1 when pushing the extruder index, so equality should be valid and selecting the last nozzle should not trigger an assertion.

* Revert "Allow wipe_tower_filament to equal nozzle count"

This reverts commit 2c97657432.

* Revert "enable_filament_for_features option"

This reverts commit 01c13baedd.

* Migrate legacy feature filament defaults

Add migration logic to convert legacy feature filament selections from 1 to 0 for older 3mf files. Introduces a local migrate_legacy_feature_filament_defaults lambda in src/OrcaSlicer.cpp and src/slic3r/GUI/Plater.cpp that scans keys (wall_filament, sparse_infill_filament, solid_infill_filament, support_filament, support_interface_filament) on configs/objects/volumes, updates values, counts conversions and logs the result. Also adds a Semver check for "2.4.0-dev" in OrcaSlicer to trigger the migration for files older than that version. This preserves expected default filament selections when loading older project files.

* Update OrcaSlicer.cpp

* Extract migration helper to ConfigMigrations

Centralize legacy feature-filament default migration by moving the duplicated lambda into ConfigMigrations::migrate_legacy_feature_filament_defaults (src/libslic3r/Config.cpp) and declaring it in Config.hpp. Update OrcaSlicer.cpp and slic3r/GUI/Plater.cpp to call the new function instead of inline lambdas. The helper converts specific feature filament keys (wall_filament, sparse_infill_filament, solid_infill_filament, support_filament, support_interface_filament) from int 1 to 0 and returns the count of conversions to avoid duplicated migration logic.

* Remove DynamicFilamentList1Based and consolidate lists

Delete the specialized DynamicFilamentList1Based struct and its global instance. Update Choice registrations to use the single dynamic_filament_list for wall, sparse_infill and solid_infill filaments, and remove the extra update call for the removed instance. This consolidates filament choice handling and removes duplicated logic in Plater.cpp.

* move it

* fix objects

* Update Config.hpp

* Update profiles
2026-05-29 10:54:26 +08:00
TheLegendTubaGuy
d3b110ebf6 Remove hardcoded Qidi Max 4 input shaper (#13864) 2026-05-28 20:25:09 -03:00
Ian Bassi
e514b60ea6 Slow down for curled perimeters Desc fix (#13918) 2026-05-28 17:15:21 -03:00
TheLegendTubaGuy
d279e241f6 Fix Kobra 3 Max bed exclusion zones (#13879) 2026-05-29 00:26:34 +08:00
Mitchell Mashburn
7999bbd819 Fix naming of re:3D cover pictures to match printer model. (#13907)
* Fix naming of cover pictures to match printer model.

* Rename buildplate textures.
2026-05-29 00:25:47 +08:00
mrmees
67b9f07655 Fix built-in placeholders missing from custom G-code and output filenames (#13892)
* fix: restore version placeholder in custom G-code

PlaceholderParser sets "version" in its constructor, but Print::apply() calls clear_config() which wipes it. Unlike timestamp/user (restored during G-code export), version was never restored, so [version]/{version} threw "Variable does not exist" in custom G-code while working in output filenames.

Re-set version after both clear_config() calls so it resolves everywhere.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: resolve timestamp and user placeholders in File header G-code

file_start_gcode is processed via print.placeholder_parser() directly, before the G-code parser integration copy that restores timestamp/user. As a result {timestamp}, {year}..{second} and {user} threw "Variable does not exist" in the File header G-code field while working in Machine start/end G-code.

Inject fresh timestamp and user into the file_start_gcode config so they resolve, matching the other custom G-code fields.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: expose initial_extruder and extruded_*_total placeholders in output filenames

PrintStatistics exposed initial_tool (not its documented alias initial_extruder) and total_weight/extruded_volume (not the documented extruded_weight_total/extruded_volume_total). Filename formats using the missing names failed with "not a variable name".

Add the missing aliases to PrintStatistics::config() and placeholders().

Fixes #12436

Fixes #10708

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 22:29:16 +08:00
SoftFever
3275bb709b Revert "Add indentation check to profile workflow (#13417)" 2026-05-28 22:27:54 +08:00
mrmees
4000445345 Fix total_toolchanges placeholder being 0 without a wipe tower (#13895)
* fix: populate total_toolchanges without a wipe tower

total_toolchanges is documented as available while change_filament_gcode (and the wipe-tower toolchange flow) is evaluated, but it was sourced only from WipeTowerData::number_of_toolchanges, which stays -1 (clamped to 0) when no wipe tower is generated. Manual filament swaps and toolchanger/IDEX setups without a wipe tower therefore always saw total_toolchanges = 0 in custom G-code and output filenames, despite real tool changes occurring -- breaking the placeholder's documented contract.

Add a tool-ordering fallback: when number_of_toolchanges < 0, count tool changes from the print's tool ordering (the transitions in the per-layer extruder sequence). Wipe-tower prints are untouched -- number_of_toolchanges >= 0 still wins -- so their reported count does not change.

Limitation: sequential (by-object) prints without a wipe tower leave Print::tool_ordering() empty, so total_toolchanges stays 0 there (unchanged from before).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 22:09:15 +08:00
Ioannis Giannakas
f593d97f31 Disable slowdown for curled perimeters as a default setting. Expand tooltip with tuning recommendations. (#13870) 2026-05-28 20:50:44 +08:00
DeathKhan
417bea04df fix(ElegooLink): pass printer SN to CC2 device panel URL (#13878)
* fix(ElegooLink): pass printer SN to CC2 device panel URL

The CC2 panel subscribes to MQTT topics keyed by the printer serial number.
Without sn= in the URL it uses a wrong hardcoded fallback SN, subscribes to
the wrong topics, and shows Offline permanently even though the printer is
reachable.

- Cache the SN in elegoo_cc2_test() (already fetches it, was discarding it)
- Look up cache in get_print_host_webui(); fall back to a short LAN HTTP
  call on first use before the test has run
- Append sn= to the panel URL
- Clear the wrong hardcoded fallback SN/IP from the panel bundle
- Add a small synchronous boot script to the panel that fetches the SN
  from the printer before the bundle reads URLSearchParams, as a fallback
  for unpatched binaries

* fix(ElegooLink): persist CC2 serial number in AppConfig dev_sn section

Store the printer SN under [dev_sn] keyed by normalized print_host after
a successful connection test or system/info fetch. Reuse it on later
sessions before hitting the network, matching how access_code is keyed by
dev_id for other LAN printers.

* fix(ElegooLink): answer get_sn IPC instantly from dev_sn cache

The CC2 panel always calls get_sn with a 10s timeout. Remove the HTTP
fallback from get_sn() and resolve IPC from dev_sn/memory only so Device
tab load is not blocked after sn= is already in the URL.

* fix(ElegooLink): skip get_sn IPC when URL already has sn

The CC2 device panel calls get_sn with a 10s timeout on every MQTT
connect even when Orca passes sn= in the query string. Use the URL
serial immediately and only fall back to IPC when it is missing.

* refactor(ElegooLink): resolve CC2 SN via PrintHost::get_sn in GUI

Drop the ElegooLink.hpp include from PrinterWebViewHandler; the webview
IPC handler uses the existing PrintHost virtual instead. Keep CC2 serial
lookup helpers file-local in ElegooLink.cpp and share them between
get_sn() and get_print_host_webui().

* chore: drop redundant <memory> include in PrinterWebViewHandler

---------

Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-05-28 19:45:03 +08:00
SoftFever
8d6ba17aac ci: dedupe profile-validation PR comments, clean up on success 2026-05-28 19:05:51 +08:00
Noisyfox
ba4d2eeae4 Fix crash when you have custom profile set to multi-extruder but the base printer is semm (#13896) 2026-05-28 18:11:36 +08:00
SoftFever
b239d3ac6c fix: allow unchecking URL association in Preferences (#13884) (#13899)
Wire the existing disassociate_url path into the Associate-tab
checkbox so users can revert prusaslicer/bambustudio/cura
registrations they previously enabled.
2026-05-28 18:01:41 +08:00
SoftFever
460e248aed privacy: disable Bambu cloud telemetry before DLL start() (#13898)
* privacy: disable Bambu cloud telemetry before DLL start()
2026-05-28 17:57:11 +08:00
d4not
69e16cd7ef fix: prevent CPU-spin in Sidebar leave handler on inactive Wayland workspace (#13897)
When the OrcaSlicer window is on an inactive Hyprland (or any Wayland
compositor that keeps surfaces mapped while hidden) workspace, GTK
keeps delivering synthetic leave-notify events to the printer-preset
row. The wxEVT_LEAVE_WINDOW handler at Plater.cpp:1855 calls
wxFindWindowAtPoint(), which walks the entire wxWidgets window tree
calling IsShown() / gtk_widget_get_child_visible() on each widget,
then Hide()s the edit button and triggers a Layout() of the parent
panel. The Hide()+Layout() re-fires more leave events, creating a
feedback loop that pegs a CPU core at 100% indefinitely.

GDB attached to a frozen process confirmed the main thread stuck in:

  wxFindWindowAtPoint (recursing through widget tree)
    -> wxWindow::IsShown
      -> gtk_widget_get_child_visible
  ...
  Sidebar::Sidebar(Plater*)::$_14   <- the leave handler lambda
  wxEvtHandler::SafelyProcessEvent
  wxGTKImpl::WindowLeaveCallback
  gtk_main_do_event
  ...

IsShownOnScreen() can't be used as a guard here because GTK on Wayland
reports widgets as visible even when the toplevel surface is on an
inactive workspace (see existing comment at Plater.cpp:9304).

Fix: state-based short-circuit. If btn_edit_printer is already hidden,
the handler has no transition to perform - skip the expensive tree walk
and the Hide()+Layout() that would re-trigger the feedback loop. After
the first leave event, every subsequent leave event is O(1).

Refs:
- #12387 (open issue with matching setup: Arch + Hyprland + RTX 3060 + Bambu A1)
- #11196 (introduced the hover-edit-button feature in Nov 2025)
2026-05-28 14:20:41 +08:00
Mitchell Mashburn
6f79b63959 Update re:3D profiles. (#13750)
* Update/add re:3D profiles.

* Fix encoding issue with UTF-8 BOM

* Change spaces to tabs.

* Fix alignment-based space indentation issues.

* Test: rename_from property

* Test 2: rename_from property

* Test 3: use 'renamed' instead of 'rename'

* Add renamed property for each conflicting profile.

* Revert to optimized assets improved on [#13149](https://github.com/OrcaSlicer/OrcaSlicer/pull/13149)
2026-05-28 12:08:19 +08:00
Ian Chua
2d09b7aefb fix: load 3mf project after sync (#13834)
# Description

Resolves https://github.com/OrcaSlicer/OrcaSlicer/issues/13830

The issue was that when OrcaSlicer was open with a 3mf file, the project
is first loaded, then when the sync finishes, it overrides the project
settings. This occurs when are working on a 3mf file and you click the
sync presets button as well.

The fix was to snapshot the current state of the settings, and then
restore whatever was marked as dirty to it's original state, preserving
the 3mf project settings.

[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
2026-05-28 10:34:09 +08:00
Mariano Dupont
f118b6b337 Add Flashforge Adventurer 5 series local send workflow with IFS mapping (#12991)
* Add Flashforge AD5X local send dialog, IFS mapping, and LAN discovery

* Refine Flashforge AD5X IFS dialog behavior

* Refine Flashforge IFS slot selection dialog

* Fix Flashforge printer selection and print mapping

* Use 3MF for Flashforge local uploads

* Generalize Flashforge local API handling

* Handle Flashforge local API IFS support more robustly

* Use selected plate filament info for Flashforge IFS mapping

* Fix Flashforge current-plate mapping and widget sizing

* Improve Flashforge IFS contrast and color matching

* Fix Flashforge legacy plate export and upload naming

Resolve PLATE_CURRENT_IDX before the legacy send-to-printhost path calls send_gcode so single-plate Flashforge 3MF exports target the selected plate instead of leaking the sentinel into export_3mf.

Sanitize Flashforge upload names in one shared utility reused by both the dialog and the backend client. This keeps the UI-visible filename and the actual uploaded filename consistent and replaces printer-problematic characters such as '=' without scattering Flashforge-specific logic through the generic Plater flow.

* Keep Flashforge upload filename sanitization in the backend only

Drop the PrintHostSendDialog API changes and keep filename sanitization inside the Flashforge backend paths that actually talk to the printer. This keeps the generic send dialog flow untouched while still normalizing problematic upload names for both serial and local API uploads.

* Only use the Flashforge IFS dialog for local API uploads

* Use reported Flashforge IFS support without model fallback

* Remove unused Flashforge slot uniqueness tracking

* Include <array> for Flashforge discovery message
2026-05-27 23:03:44 +08:00
anjis
9c63aee9f8 Fix ScriptMessageHandler invalidation after RemoveAllUserScripts on Windows. (#13829) 2026-05-27 22:59:43 +08:00
wujie
5b071d5013 Sync Elegoo profiles (#13790)
* Sync Elegoo profiles from ElegooSlicer

Update vendor Elegoo.json, filament/machine/process trees, and OrcaFilamentLibrary
Elegoo entries. Align machine default material names with existing filament preset names.

* feat: expose filament_name for G-code export filename format

Derive from filament_settings_id for the first active extruder and strip the suffix after @, matching ElegooSlicer so filename_format can use {filament_name}.

* chore: reorder Elegoo entries in OrcaFilamentLibrary

Group Elegoo @base profiles and bump library version to 02.03.02.62.

* sync OrcaFilamentLibrary.json with Elegoo filament profiles

* fix: clean up Elegoo process renamed_from for profile validation

Add single renamed_from only where preset names changed from legacy Orca
names; remove duplicate Rapid @System library entries that conflicted with
ECC2 vendor presets.

* fix(profiles): add missing Elegoo renamed_from for profile validation

CI custom-preset tests still inherit legacy Orca preset names that no
longer exist after the Elegoo bundle update. Add renamed_from on process,
Neptune 4 machines, OrcaFilamentLibrary filaments, and Giga profiles so
inherits resolve again, without changing print parameters.
2026-05-27 22:58:39 +08:00
Kuzuri
b230a97a50 Fix Snapmaker U1 "Print by Object" collisions (#13854)
Update Snapmaker U1 (0.4 nozzle).json

Updated Snapmaker U1 0.4 nozzle "change_filament_gcode" and "machine_end_gcode" to fix the issue of collisions when using "Print by Object"
2026-05-27 22:29:40 +08:00
Frenshape
4f162b9058 Add Point3 return type getters for first and last point to ExtrusionEntity (#13855)
Fix compile error in Debug mode. Adds getters for Point3 types in ExtrusionEntity

ZAA changed ExtrusionPath::polyline from Polyline to Polyline3, preserving the existing interfaces by converting first_point and last_point to return a Point copy constructed from the underlying Point3 type.

ExtrusionLoop::validate function was not updated and is broken in debug configurations as it's currently comparing Point to Point3

This change promotes ExtrusionPath::first_point3/last_point3 to the ExtrusionEntity base class as a  pure virtual function, implements them on derived classes, and fixes ExtrusionLoop::validate
2026-05-27 22:07:15 +08:00
SoftFever
3a53d3c85b Fix nozzle diameter mismatch error in manual calibration (#13882) 2026-05-27 20:42:56 +08:00
Clifford
04aa26da9a Fix nozzle diameter guards for printers that don't report nozzle info (#13255)
Fix nozzle diameter guards for printers that don't report nozzle info (#13236)

PR #12814 changed DevNozzle::m_diameter default from 0.4f to 0.0f to
mean "unknown" when firmware doesn't push nozzle info, and guarded two
call sites in SelectMachine.cpp. PR #13330 introduced
DevExtderSystem::NozzleDiameterMatchesOrUnknown() and adopted it in
get_printer_preset / CalibUtils / CalibrationWizardPresetPage. A few
reachable sites were still left out and now report "mismatch" / fail
silently for every non-BBL printer (Klipper/Moonraker, RRF, Marlin,
etc.) that doesn't push BBL nozzle data.

The most visible symptom: the "Sync filament colors from AMS" button on
Moonraker printers with AMS/AFC silently does nothing, because
get_printer_preset() couldn't find a matching system preset (fixed in
#13330, but the lookup-string sites below kept the bug visible
elsewhere).

Apply NozzleDiameterMatchesOrUnknown at the two remaining comparison
sites:

  src/slic3r/GUI/Plater.cpp
    - file-load printer-mismatch dialog — don't prompt on every load
    - on_select_preset sync_extruder_list gate — skip 0.0 extruders

For the three filament-lookup string-builder sites, fall back to the
currently-selected printer preset's nozzle diameter so the dropdown
isn't empty when firmware hasn't reported a diameter:

  src/slic3r/GUI/AMSMaterialsSetting.cpp (Popup + on_select_filament)
  src/slic3r/GUI/CaliHistoryDialog.cpp (get_all_filaments)

Also remove the dead SyncAmsInfoDialog::is_same_nozzle_diameters method
surfaced while auditing the affected sites — it was introduced
2024-12-30 in commit ad79ed6d93 ("ENH:add SyncAmsInfoDialog",
cherry-picked from Bambu's internal branch) but a caller was never
wired up on the OrcaSlicer side. Dead since introduction.

Fixes #13236
Refs #12814 #13330

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-05-27 20:41:15 +08:00
Ioannis Giannakas
957d3017b4 Fix bridges appearing on top surfaces when "Extra bridge layers" is enabled. Threading fixes for extra bridge layers and lightning infill. (#13860)
* Fix data race in extra bridge layer generation causing spurious bridges on top surfaces
* Guard second bridge layer against top most surfaces
* CoPilot review comments & lighting infill threading fix.
2026-05-27 10:46:57 +01:00
dremc
0ef7715019 Add DREMC materials profiles such as PLA, ABS, ASA filament profiles (#13783)
* Create DREMC PLA+ @base.json

* Create DREMC

* Delete resources/profiles/OrcaFilamentLibrary/filament/DREMC

* Create

* Delete resources/profiles/OrcaFilamentLibrary/filament/dremc directory

* Delete resources/profiles/OrcaFilamentLibrary/filament/DREMC

* Create DREMC PLA+ @base.json

* Added Initial DREMC Profiles

* Update OrcaFilamentLibrary.json

* Update OrcaFilamentLibrary.json

* Update DREMC PPA-CF @base.json

* Add files via upload

* Replace Space with Indentation

* Update OrcaFilamentLibrary.json

* Fixed ID Duplication Issue

* Update OrcaFilamentLibrary.json

Fixed Formatting/Ordering

* Update OrcaFilamentLibrary.json

Fixed Formatting

* Update OrcaFilamentLibrary.json

* Update OrcaFilamentLibrary.json

* Add files via upload

* Add files via upload

* Update DREMC ASA CF @base.json

* Update DREMC ASA GF @base.json

* remove duplicated profiles

---------

Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-05-27 13:00:24 +08:00
Noisyfox
e0c4d11bae Fix display of non-ascii orca cloud user name (#13856) 2026-05-26 09:30:39 -03:00
Ian Bassi
398e007f2e OrcaSliced Handy Model (#13727)
* Ad Colourful Orca Handy model

* Model

Co-Authored-By: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
Co-Authored-By: yw4z <yw4z@outlook.com>

* Autoarrange after import

* Multifile import

* Current

* Cleaned

* Update Colourful_Orca.3mf

* Cleaning

* Update Orca cube v2 to DRC and autoarrange

* Rename orca to OrcaSliced

---------

Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
Co-authored-by: yw4z <yw4z@outlook.com>
2026-05-26 17:19:34 +08:00
Kiss Lorand
496bd2babc HU language tweak (#13841)
Hu language tweak

Typo, semantics, grammar adjustments.
2026-05-26 17:17:55 +08:00
Kiss Lorand
b0334325f8 Fix overhang preview ignoring support threshold fallbacks when angle set to 0 (#12650)
* Fix overhang preview not using fallbacks when angle is 0

The overhang visualization in Preview ("show overhangs based on support settings")
used the raw `support_threshold_angle` value from the configuration.

When `support_threshold_angle` was set to 0, Orca internally falls back to:
- 30° for tree supports
- an angle derived from `support_threshold_overlap` for normal supports

However, the preview logic ignored these fallbacks and used the raw value (0°),
leading to incorrect overhang highlighting that did not match the actual
support generation behavior.

This patch computes the effective overhang threshold used for preview:

• If `support_threshold_angle > 0`, use it directly
• If `support_threshold_angle == 0` and tree supports are used, fall back to 30°
• If `support_threshold_angle == 0` and normal supports are used, derive the
  equivalent angle from `support_threshold_overlap`, `layer_height`, and the
  external perimeter width.

The function now returns `normal_z` directly so the preview uses the same
effective slope threshold as the support generator.

As a result, the overhang highlight in Preview now correctly matches the
supports that will actually be generated.

* Apply Copilot suggestions
2026-05-26 17:17:19 +08:00
Ian Chua
2854c78069 Merge branch 'main' into fix/load-3mf-after-sync 2026-05-26 10:56:29 +08:00
Ian Chua
004bf6ff72 save and restore 3mf settings after sync 2026-05-26 10:55:11 +08:00
Rodrigo Faselli
a57e0f500f Add Fill_multiline to GUI_Factories.cpp (#13852)
Add multiline to gui_factories.cpp
2026-05-25 23:41:25 -03:00
Ian Bassi
1925bdfc7a Optimized gyroid link fix (#13839) 2026-05-25 13:28:35 -03:00
Noisyfox
f899d5a35d Fix issue that filament group popup is dismissed too quickly on macOS (#13837) 2026-05-26 00:09:21 +08:00
Alexandre Folle de Menezes
5820e5d3fd Improve e complement the pt-BR translation (#13823) 2026-05-25 12:18:30 +08:00
Kiss Lorand
2afc99e6c7 HU language update (#13825) 2026-05-25 12:18:03 +08:00
Heiko Liebscher
5351adf9b3 localization(de): fill in missing German translations (#13819)
- Translate 18 previously untranslated strings in OrcaSlicer_de.po
- Covers cloud errors, context menu, sync presets, loading states, and UI labels
2026-05-25 12:17:28 +08:00
Robert J Audas
c383587a3e Bump printers version.txt to propagate X2D (N6.json) to existing users (#13806)
PR #13388 added resources/printers/N6.json for X2D support but did not
bump resources/printers/version.txt. PresetUpdater only copies files
from the install's resources/printers/ to the user's data_dir/printers/
when the resources version is newer than the user's stored version, so
every existing install stays at 02.00.00.29 and never receives N6.json.

At runtime, json_diff::load_compatible_settings("N6", "") reads from
data_dir/printers/N6.json; the silent file-missing failure leaves
is_support_bed_leveling, is_support_pa_calibration, and
SupportCalibrationNozzleOffset at their defaults, hiding the Bed
Leveling and Nozzle Offset Calibration checkboxes in the Send Print
Job dialog for the X2D.

Bumping the patch version triggers the existing propagation logic on
next startup.

Fixes #13780
Fixes #13794

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 18:12:14 +08:00
SoftFever
e7e9e06c9c attemp to enhance network share experience for flatpak (#13809)
* attemp to enhance network share  experience for flatpak
2026-05-23 17:15:47 +08:00
SoftFever
f71a79550b fix flatpak crash on start issue (#13808)
fix flatpak crash issues
2026-05-23 16:19:26 +08:00
yw4z
27d7d5602c Fix glitches around TextInput & ComboBox controls after scrolling on scaled displays (#13805)
Update StaticBox.cpp

Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-05-23 11:24:12 +08:00
SoftFever
f717b46435 Feature/update flatpak 2.4 (#13799)
* update flatpak to reflect recent deps changes as well as upgrade runtime to 50

* support building from worktree
2026-05-22 23:52:36 +08:00
SoftFever
6f6fc6ddfe Fixes a possible null dereference in DeviceManager::check_pushing() when the selected machine is missing or stale during timer refresh. (#13802)
* Fix selected-machine null deref in device pushing check
2026-05-22 23:52:00 +08:00
Zuhaib Siddique
ffde56ccba Ignore SIGPIPE at startup to prevent crashes on dropped printer connections (#13788)
Ignore SIGPIPE at startup to prevent crash on dropped printer connection

Writing to a closed printer network socket raised SIGPIPE, whose default
action terminated the whole process (exit 141, no crash report). Set
SIGPIPE to SIG_IGN once at main() entry (POSIX only) so such writes return
EPIPE to the existing networking error handling instead of killing the app.

Fixes #13787
2026-05-22 22:18:53 +08:00
Ian Chua
464ca4c765 fix: detached presets not showing up (#13793)
* fix: detached presets not showing up

* slightly better code clarity

* remove cloud_prefix
2026-05-22 19:05:28 +08:00
yw4z
def47f8959 Mode button fixes / improvements (#13795)
* init

* update thumb color
2026-05-22 19:04:03 +08:00
Ian Chua
19ada707da fix: 409 sync push on app start (#13796)
revert prev commits
2026-05-22 19:03:20 +08:00
SoftFever
3d250dc52c Fix crash for preset sync during startup (#13797) 2026-05-22 19:02:49 +08:00
Ioannis Giannakas
1388dc5da8 Reduce Spiral Z generation segment density (#12564) 2026-05-22 10:46:00 +01:00
1638 changed files with 79382 additions and 32148 deletions

View File

@@ -142,7 +142,7 @@ jobs:
flatpak:
name: "Flatpak"
container:
image: ghcr.io/flathub-infra/flatpak-github-actions:gnome-49
image: ghcr.io/flathub-infra/flatpak-github-actions:gnome-50
options: --privileged
volumes:
- /usr/local/lib/android:/usr/local/lib/android

View File

@@ -34,46 +34,6 @@ jobs:
python3 ./scripts/orca_extra_profile_check.py 2>&1 | tee ${{ runner.temp }}/extra_json_check.log
exit ${PIPESTATUS[0]}
- name: Check profile indentation
id: indentation_check
continue-on-error: true
run: |
set +e
python3 - <<'PY' 2>&1 | tee ${{ runner.temp }}/indentation_check.log
import re
from pathlib import Path
import sys
profiles_root = Path("resources/profiles")
invalid_files = []
for file_path in sorted(profiles_root.rglob("*.json")):
try:
for line_number, line in enumerate(file_path.read_text(encoding="utf-8").splitlines(), start=1):
if not line.strip():
continue
leading_ws = re.match(r"^[ \t]*", line).group(0)
if " " in leading_ws:
invalid_files.append((file_path, line_number))
break
except Exception as exc:
print(f"[ERROR] Unable to read {file_path}: {exc}")
invalid_files.append((file_path, 0))
if invalid_files:
for path, line in invalid_files:
if line > 0:
print(f"[ERROR] Space indentation found in {path}:{line}")
else:
print(f"[ERROR] Could not validate indentation in {path}")
print("Use tab indentation in profile JSON files (1 tab per indentation level).")
print("Tip: run python3 ./scripts/orca_filament_lib.py --fix --force to normalize formatting.")
sys.exit(1)
print("All profile JSON files use tab-only indentation.")
PY
exit ${PIPESTATUS[0]}
# download
- name: Download
working-directory: ${{ github.workspace }}
@@ -101,12 +61,18 @@ jobs:
./OrcaSlicer_profile_validator -p ${{ github.workspace }}/resources/profiles -l 2 2>&1 | tee ${{ runner.temp }}/validate_custom.log
exit ${PIPESTATUS[0]}
- name: Prepare comment artifact
if: ${{ always() && github.event_name == 'pull_request' && (steps.extra_json_check.outcome == 'failure' || steps.indentation_check.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }}
- name: Prepare PR number for comment workflow
if: ${{ always() && github.event_name == 'pull_request' }}
run: |
mkdir -p ${{ runner.temp }}/profile-check-results
echo "${{ github.event.pull_request.number }}" > ${{ runner.temp }}/profile-check-results/pr_number.txt
- name: Prepare comment artifact
if: ${{ always() && github.event_name == 'pull_request' && (steps.extra_json_check.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }}
run: |
{
# Marker matched by check_profiles_comment.yml to delete prior comments.
echo "<!-- profile-validation-comment -->"
echo "## :x: Profile Validation Errors"
echo ""
@@ -119,15 +85,6 @@ jobs:
echo ""
fi
if [ "${{ steps.indentation_check.outcome }}" = "failure" ]; then
echo "### Indentation Check Failed"
echo ""
echo '```'
head -c 30000 ${{ runner.temp }}/indentation_check.log || echo "No output captured"
echo '```'
echo ""
fi
if [ "${{ steps.validate_system.outcome }}" = "failure" ]; then
echo "### System Profile Validation Failed"
echo ""
@@ -150,10 +107,8 @@ jobs:
echo "*Please fix the above errors and push a new commit.*"
} > ${{ runner.temp }}/profile-check-results/pr_comment.md
echo "${{ github.event.pull_request.number }}" > ${{ runner.temp }}/profile-check-results/pr_number.txt
- name: Upload comment artifact
if: ${{ always() && github.event_name == 'pull_request' && (steps.extra_json_check.outcome == 'failure' || steps.indentation_check.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }}
if: ${{ always() && github.event_name == 'pull_request' }}
uses: actions/upload-artifact@v7
with:
name: profile-check-results
@@ -161,7 +116,7 @@ jobs:
retention-days: 1
- name: Fail if any check failed
if: ${{ always() && (steps.extra_json_check.outcome == 'failure' || steps.indentation_check.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }}
if: ${{ always() && (steps.extra_json_check.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }}
run: |
echo "One or more profile checks failed. See above for details."
exit 1

View File

@@ -10,12 +10,19 @@ on:
permissions:
pull-requests: write
# Needed to delete outdated bot comments via the issues/comments endpoint.
issues: write
# Serialize handlers per source branch so parallel runs don't race delete-and-post.
concurrency:
group: ${{ github.workflow }}-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }}
cancel-in-progress: false
jobs:
post_comment:
name: Post PR comment
runs-on: ubuntu-24.04
if: ${{ github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'failure' }}
if: ${{ github.event.workflow_run.event == 'pull_request' && (github.event.workflow_run.conclusion == 'success' || github.event.workflow_run.conclusion == 'failure') }}
steps:
- name: Download artifact
id: download
@@ -26,14 +33,14 @@ jobs:
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ github.token }}
- name: Post comment on PR
- name: Update PR comment
if: ${{ steps.download.outcome == 'success' }}
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
if [ ! -f pr_number.txt ] || [ ! -f pr_comment.md ]; then
echo "No comment artifact found, skipping."
if [ ! -f pr_number.txt ]; then
echo "No pr_number.txt in artifact, skipping."
exit 0
fi
@@ -43,4 +50,17 @@ jobs:
exit 1
fi
gh pr comment "$PR_NUMBER" --body-file pr_comment.md
# Delete prior comments matching the marker (from check_profiles.yml) or the legacy heading.
OLD_IDS=$(gh api --paginate "repos/${GH_REPO}/issues/${PR_NUMBER}/comments" \
--jq '.[] | select(.user.login == "github-actions[bot]") | select((.body | startswith("<!-- profile-validation-comment -->")) or (.body | startswith("## :x: Profile Validation Errors"))) | .id')
for comment_id in $OLD_IDS; do
echo "Deleting outdated profile-validation comment ${comment_id}"
gh api -X DELETE "repos/${GH_REPO}/issues/comments/${comment_id}" || true
done
# Post a new comment only when validation failed (pr_comment.md present).
if [ -f pr_comment.md ]; then
gh pr comment "$PR_NUMBER" --body-file pr_comment.md
else
echo "Validation succeeded; cleaned up prior comments without posting."
fi

3
.gitignore vendored
View File

@@ -45,4 +45,5 @@ test.js
.clangd
internal_docs/
*.flatpak
/flatpak-repo/
/flatpak-repo/
*.pyc

View File

@@ -199,22 +199,22 @@ echo -e "${GREEN}All required dependencies found${NC}"
# Install runtime and SDK if requested
if [[ "$INSTALL_RUNTIME" == true ]]; then
echo -e "${YELLOW}Installing GNOME runtime and SDK...${NC}"
flatpak install --user -y flathub org.gnome.Platform//49
flatpak install --user -y flathub org.gnome.Sdk//49
flatpak install --user -y flathub org.gnome.Platform//50
flatpak install --user -y flathub org.gnome.Sdk//50
fi
# Check if required runtime is available
if ! flatpak info --user org.gnome.Platform//49 &> /dev/null; then
echo -e "${RED}Error: GNOME Platform 49 runtime is not installed.${NC}"
if ! flatpak info --user org.gnome.Platform//50 &> /dev/null; then
echo -e "${RED}Error: GNOME Platform 50 runtime is not installed.${NC}"
echo "Run with -i flag to install it automatically, or install manually:"
echo "flatpak install --user flathub org.gnome.Platform//49"
echo "flatpak install --user flathub org.gnome.Platform//50"
exit 1
fi
if ! flatpak info --user org.gnome.Sdk//49 &> /dev/null; then
echo -e "${RED}Error: GNOME SDK 49 is not installed.${NC}"
if ! flatpak info --user org.gnome.Sdk//50 &> /dev/null; then
echo -e "${RED}Error: GNOME SDK 50 is not installed.${NC}"
echo "Run with -i flag to install it automatically, or install manually:"
echo "flatpak install --user flathub org.gnome.Sdk//49"
echo "flatpak install --user flathub org.gnome.Sdk//50"
exit 1
fi

View File

@@ -1,5 +1,11 @@
set(_eigen_extra_flags "")
if (MSVC)
set(_eigen_extra_flags "-DCMAKE_CXX_FLAGS:STRING=/bigobj")
endif ()
orcaslicer_add_cmake_project(Eigen
URL https://gitlab.com/libeigen/eigen/-/archive/5.0.1/eigen-5.0.1.zip
URL_HASH SHA256=0dbb1f9e3aaad66f352c03227d8c983f6f0b49e0b07e71a7300f4abcc01aee12
CMAKE_ARGS "${_eigen_extra_flags}"
DEPENDS dep_Boost dep_GMP dep_MPFR
)

668
doc/automation.md Normal file
View File

@@ -0,0 +1,668 @@
# OrcaSlicer UI Automation Protocol (v1.0.0)
OrcaSlicer ships an **opt-in, localhost-only JSON-RPC server** that lets external
scripts introspect, drive, and screenshot the running OrcaSlicer GUI. It is built
for end-to-end testing and automation: a script can enumerate the live widget
tree, click buttons, type text, send keyboard shortcuts, wait for UI state, query
high-level application state, load models/projects into the running instance,
switch the active view/tab, and capture window images (the on-screen capture
includes the 3D viewport).
This document is the protocol reference. It describes activation, the transport,
the JSON-RPC envelope, every method, the unified node shape, the target/locator
model, error codes, the set of instrumented automation ids, ImGui specifics,
platform caveats, a quick-start snippet, and planned future work.
---
## 1. Overview & activation
The automation server is **OFF by default**. It is enabled with two
command-line flags:
| Flag | Meaning |
|---|---|
| `--automation-server` | Enable the automation server. |
| `--automation-server-port=PORT` | Override the listening port. Optional; default is **13619**. |
Example:
```bash
OrcaSlicer --automation-server --automation-server-port=13619 model.stl
```
The server binds to **`127.0.0.1` only** (the loopback interface). It is never
exposed on an external network interface.
**Security note (v1):** there is **no authentication token** in v1. The localhost
bind is the *only* security boundary. Any process able to run code on the machine
can connect to the port and drive the GUI — including injecting mouse and keyboard
input — while the server is enabled. The feature is intended for testing and
automation environments, not for production or shared/multi-user machines.
When the server is enabled, OrcaSlicer emits a `warning`-level log line at startup
to make the active input-injection surface obvious in logs, for example:
```
UI automation server ENABLED ... input injection is active
```
---
## 2. Transport
The server speaks **HTTP/1.1** over the loopback TCP socket:
| Request | Response |
|---|---|
| `POST /jsonrpc` with a JSON-RPC 2.0 request body | A JSON-RPC 2.0 response with `Content-Type: application/json`. |
| `GET /` | A plain-text health page: `OrcaSlicer automation server v1.0.0` (`Content-Type: text/plain`). |
| Anything else | HTTP `404 Not Found`. |
The server is **single-client / serialized** in v1: it handles one request at a
time on its own dedicated I/O thread. Connections are not kept alive; each request
is answered and the socket is closed. Clients should issue requests sequentially.
---
## 3. JSON-RPC envelope
The protocol follows **JSON-RPC 2.0**.
**Request:**
```json
{ "jsonrpc": "2.0", "id": <id>, "method": "<method>", "params": { ... } }
```
- `params` may be omitted; the server treats a missing `params` as an empty object.
**Success response:**
```json
{ "jsonrpc": "2.0", "id": <id>, "result": { ... } }
```
**Error response:**
```json
{ "jsonrpc": "2.0", "id": <id>, "error": { "code": <int>, "message": "<string>" } }
```
The request `id` is echoed back in the response. When the request has no `id`, or
when the request body cannot be parsed as JSON, the response `id` is `null`.
---
## 4. Methods
There are 12 methods. Capabilities advertised by `automation.version` list the 11
callable feature methods (every method except `automation.version` itself).
### `automation.version`
Returns server identity and the list of supported methods. Takes no parameters.
**Result:**
```json
{
"version": "1.0.0",
"protocol": "2.0",
"capabilities": [
"tree.dump", "tree.find", "widget.get", "input.click", "input.type",
"input.key", "sync.wait_for", "app.state", "screenshot.window", "file.open",
"view.select"
]
}
```
### `tree.dump`
Snapshot the live UI tree as a single root node with nested children.
**Params (all optional):**
| Param | Type | Default | Meaning |
|---|---|---|---|
| `root` | string (id or path) | full tree | Root the dump at the node with this id/path. |
| `max_depth` | int | `-1` | Maximum depth to descend. `-1` = unlimited. |
| `visible_only` | bool | `false` | When true, omit non-visible nodes. |
| `include_imgui` | bool | `true` | When true, include ImGui items. |
**Result:** the serialized root [node](#5-unified-node-shape), with `children`
included.
### `tree.find`
Find all nodes matching a [target predicate](#6-target--locator).
**Params:** a target predicate — any combination of `name`, `class`, `label`,
`value`, `backend` (provided fields are ANDed). The params object is the target
itself (it is *not* wrapped in a `target` key for this method).
**Result:** a **flat JSON array** of matching nodes. The nodes in this array are
returned **without** their `children` (use `widget.get`/`tree.dump` to descend).
### `widget.get`
Fetch a single node by [target](#6-target--locator).
**Params:**
| Param | Type | Required | Meaning |
|---|---|---|---|
| `target` | object | yes | Target spec (id / path / predicate). |
**Result:** a single [node](#5-unified-node-shape), with its `children` included.
**Errors:** `1001` if the target is **not found** *or* **ambiguous** (more than one
match).
### `input.click`
Click a resolved, actionable node.
**Params:**
| Param | Type | Default | Meaning |
|---|---|---|---|
| `target` | object | required | Target spec; must resolve to exactly one node. |
| `button` | string | `"left"` | `"left"`, `"right"`, or `"middle"`. |
| `double` | bool | `false` | Double-click when true. |
| `modifiers` | array of string | `[]` | Held modifiers: any of `"ctrl"`, `"shift"`, `"alt"`, `"cmd"` (`"meta"` is accepted as an alias of `"cmd"`). |
**Result:** `{ "ok": true }`.
**Errors:** `1001` not found / ambiguous; `1002` if the target is disabled or
hidden (not actionable). The click path raises and focuses the target's top-level
window before injecting the click.
### `input.type`
Type text into the currently focused control.
**Params:**
| Param | Type | Required | Meaning |
|---|---|---|---|
| `text` | string | yes | The text to type. |
| `target` | object | no | If given, this node is clicked first (to focus it) before typing. |
**Result:** `{ "ok": true }`.
**Errors:** if `target` is supplied, the same actionability errors as
`input.click` apply (`1001` / `1002`).
### `input.key`
Send a key chord (a key plus optional modifiers) to the focused window.
**Params:**
| Param | Type | Required | Meaning |
|---|---|---|---|
| `keys` | string or array | yes | Either a `"+"`-joined string like `"ctrl+s"`, or an array like `["ctrl", "s"]`. The last token is the key; earlier tokens are modifiers. |
**Result:** `{ "ok": true }`.
**Key names must be lowercase.** Recognized key names include `"enter"`, `"tab"`,
`"esc"`, `"space"`, `"delete"`, `"backspace"`, `"f5"` (and other function keys),
and single characters (e.g. `"s"`, `"a"`). Recognized modifiers are `"ctrl"`,
`"shift"`, `"alt"`, `"cmd"` (with `"meta"` as an alias for `"cmd"`).
**Unrecognized or uppercase key names are silently ignored** — no error is
returned, the key simply does not fire. Use lowercase names exclusively.
### `sync.wait_for`
Poll the UI until a target node reaches a desired state, or time out. This is the
preferred way to synchronize with asynchronous UI changes (it replaces fragile
fixed sleeps). Internally it repeatedly refreshes and dumps the tree, re-resolves
the target, and evaluates the requested state until it is satisfied.
**Params:**
| Param | Type | Default | Meaning |
|---|---|---|---|
| `target` | object | required | Target spec. |
| `state` | string | required | One of `"exists"`, `"visible"`, `"enabled"`, `"value"`. |
| `value` | string | — | Required when `state` is `"value"`; the expected value to match. |
| `timeout_ms` | int | `5000` | Maximum time to wait, in milliseconds. |
| `poll_ms` | int | `100` | Poll interval, in milliseconds (minimum 1). |
State semantics:
- `exists` — the target resolves to a node.
- `visible` — the node exists and is visible.
- `enabled` — the node exists and is **both enabled and visible**.
- `value` — the node has a value and that value equals the supplied `value`.
**Result:** `{ "ok": true, "elapsed_ms": <int> }`.
**Errors:** `1003` on timeout (the state was not reached within `timeout_ms`).
### `app.state`
Return a high-level application-state snapshot. Takes no parameters.
**Result:**
```json
{
"active_tab": "<string>",
"project_loaded": <bool>,
"slicing": <bool>,
"slice_progress": <int>,
"foreground": <bool>,
"modal_dialog": "<string>"
}
```
| Field | Meaning |
|---|---|
| `active_tab` | The active top-level tab/page. |
| `project_loaded` | Whether a project/model is currently loaded. |
| `slicing` | Whether slicing is currently in progress. |
| `slice_progress` | Slicing progress (`-1` when unknown). |
| `foreground` | Whether the main window is in the foreground. |
| `modal_dialog` | Present only when a modal dialog is active; identifies it. Omitted otherwise. |
### `screenshot.window`
Capture a window as a PNG, exactly as it appears on screen.
**Params:**
| Param | Type | Default | Meaning |
|---|---|---|---|
| `target` | object | main frame | If given, capture this window; otherwise capture the main frame. |
**Result:** `{ "png_base64": "<base64 PNG>", "width": <int>, "height": <int> }`.
**Errors:** `1005` on screenshot failure; `1001` if a supplied `target` is not
found or ambiguous.
**How it works:** the window's on-screen rectangle is read back from the
DWM-composited desktop framebuffer (`wxScreenDC`), so the capture includes every
native child control, the OpenGL 3D viewport, and ImGui overlays — it is a faithful
image of what the user sees. (Capturing the parent window's own client DC instead
would clip out child HWNDs and the GL surface, leaving them black; that is why this
method reads from the screen.)
**Caveats:**
- The window must be **visible and unobscured**. Because the source is the on-screen
framebuffer, any overlapping window occludes the captured region. The backend
raises the target window before capturing.
- **HiDPI:** the reported `width`/`height` come from the window's logical client size,
while the screen framebuffer is in physical pixels. On per-monitor-DPI displays the
two can differ; the capture may be cropped or scaled relative to the logical size.
- Because the capture is the live on-screen image, the 3D content reflects the
**current view**: the model in the 3D editor, or the gcode toolpaths in Preview
after a slice. There is no separate offscreen 3D-render method — the window
capture already includes whatever the GL canvas is showing.
### `file.open`
Load one or more files into the **already-running** instance at runtime, by calling
`Plater::load_files(...)` directly on the GUI thread. This is the supported way to add
or swap a model without relaunching the process. Loading is **synchronous**: when the
call returns `ok: true`, `app.state().project_loaded` is already `true` (no polling
race).
**Params:**
| Param | Type | Required | Meaning |
|---|---|---|---|
| `paths` | string or array of strings | yes | One or more **absolute** file paths. A bare string is accepted and treated as a one-element list. Paths are read from the **host (server) filesystem** — client and server are localhost-only. |
`.3mf` files are routed as projects and meshes as models automatically, based on file
content (the same default strategy as drag-drop); there is no `as_project` flag in v1.
**Result:** `{ "ok": true, "loaded": <int> }`, where `loaded` is the number of objects
added to the scene (`load_files(...).size()`).
**Errors:**
- `-32602` (invalid params) — `paths` is missing, is not a string/array, contains a
non-string entry, or yields no non-empty path.
- `1007` (load failed) — `load_files` returned empty or threw (file not found, parse
error, or unsupported format).
- `1004` (GUI busy) — the GUI-thread marshal timed out. An extremely large model can
exceed the marshal timeout and surface here; documented, not mitigated in v1.
### `view.select`
Switch the main window to a top-level view/tab at runtime. Useful to put the UI in a
known state before other actions — e.g. switch to **Prepare** before loading a model,
or to **Preview** after slicing.
**Params:**
| Param | Type | Required | Meaning |
|---|---|---|---|
| `view` | string | yes | The target view. One of: `home`, `prepare` (3D editor), `preview` (sliced G-code), `device`, `multi_device`, `project`, `calibration`. |
**Result:** `{ "ok": true, "view": <string>, "index": <int> }`, where `index` is the
resulting tab index (it can vary with layout, since some tabs — e.g. `multi_device`
are only present in certain configurations).
**Errors:**
- `-32602` (invalid params) — `view` is missing, is not a string, or is empty.
- `1001` (not found) — the view name is unknown, or that view is not available in the
current layout (for example `prepare`/`preview` in G-code-viewer mode, or
`multi_device` when multi-device is disabled).
- `1004` (GUI busy) — the GUI-thread marshal timed out.
---
## 5. Unified node shape
Both wx widgets and ImGui items are reported with the same node schema:
```json
{
"backend": "wx" | "imgui",
"id": "<string>",
"path": "<string>",
"class": "<string>",
"label": "<string>",
"rect": { "x": <int>, "y": <int>, "w": <int>, "h": <int> },
"enabled": <bool>,
"visible": <bool>,
"value": "<string>",
"children": [ <node>, ... ]
}
```
| Field | Meaning |
|---|---|
| `backend` | `"wx"` for native wxWidgets controls, `"imgui"` for immediate-mode ImGui items. |
| `id` | The automation id when one is set, otherwise a derived id. For ImGui items the `path` doubles as the `id`. |
| `path` | Positional path, e.g. `"MainFrame/Panel[2]/Button[0]"`. For ImGui items: `"ImGui/<window>/<label>"`. |
| `class` | wx class name, or the ImGui item type. |
| `label` | The control's label/caption. May include an ImGui `##`-id suffix for ImGui items. |
| `rect` | Bounding rectangle in **screen coordinates**. |
| `enabled` | Whether the control is enabled. |
| `visible` | Whether the control is visible. |
| `value` | The control's value (text/choice/check/slider, etc.). **Omitted entirely** when the control has no applicable value. |
| `children` | Child nodes. **wx only**, and present only when children are included (e.g. `tree.dump`, `widget.get`). ImGui items are flat (no children) and are listed under their window. |
Notes:
- The `value` key is **omitted** (not `null`) when the control has no value.
- `children` is present only for wx nodes when children are requested; ImGui nodes
never carry `children`.
---
## 6. Target / locator
Most methods accept a **target** object that identifies one or more nodes. A
target may specify:
| Field | Meaning |
|---|---|
| `id` | Exact automation id. |
| `path` | Exact positional path. |
| `name` | Predicate: matches either the node's `id` **or** its `label`. |
| `class` | Predicate: exact class name. |
| `label` | Predicate: exact label. |
| `value` | Predicate: node has a value and it equals this string. |
| `backend` | Predicate: `"wx"` or `"imgui"`. |
**Resolution order:** **`id``path` → predicate.**
- If `id` is present, only `id` is used (exact match).
- Else if `path` is present, only `path` is used (exact match).
- Else the predicate fields (`name`, `class`, `label`, `value`, `backend`) are
used, and all provided predicate fields are **ANDed** together.
Action methods (`input.click`, `input.type` with a target, `widget.get`, and
single-target `screenshot.window`) require a **unique** match. If the target
resolves to zero matches or more than one match, the call fails with error `1001`
(not found / ambiguous). `tree.find` is the exception: it returns *all* matches as
an array and never errors on ambiguity.
---
## 7. Error codes
Standard JSON-RPC codes:
| Code | Meaning |
|---|---|
| `-32700` | Parse error — the request body was not valid JSON. |
| `-32600` | Invalid request — missing/invalid `method`. |
| `-32601` | Method not found — unknown method name. |
| `-32602` | Invalid params — missing/invalid parameters for the method. |
Application-specific codes:
| Code | Meaning |
|---|---|
| `1001` | Widget/target not found **or** ambiguous (more than one match). |
| `1002` | Not actionable — the target is disabled or hidden. |
| `1003` | Wait timeout — `sync.wait_for` did not reach the requested state in time. |
| `1004` | GUI thread busy / timeout — a backend call could not be marshaled onto the GUI thread in time (wedged GUI). |
| `1005` | Screenshot failed. |
| `1006` | Disabled. |
| `1007` | Load failed — `file.open`'s `load_files` returned empty or threw (not found, parse error, unsupported format). |
---
## 8. Automation-id naming conventions & instrumented ids
Stable automation ids follow these prefix conventions:
| Prefix | Used for |
|---|---|
| `btn_` | Buttons |
| `combo_` | Preset combo boxes |
| `tab_` | Tabs |
| `canvas_` | Canvases |
| `dlg_` | Dialog buttons |
### Instrumented ids (as-built in v1)
The following controls currently carry stable automation ids:
| id | Control | Note |
|---|---|---|
| `btn_slice` | Slice-plate button | |
| `btn_export` | Print / Export button | Multi-purpose: the action (Print plate / Export G-code / Send) depends on the current mode. |
| `tab_device` | Device / Monitor tab (`MonitorPanel`) | |
| `combo_printer` | Printer preset combo (sidebar) | |
| `combo_filament` | Filament preset combo (sidebar) | First filament row only; extra multi-material rows are not instrumented. |
| `canvas_3d` | 3D editor GL canvas | |
### Controls NOT instrumented in v1
Several controls are intentionally **not** instrumented in v1 because they have no
stable `wxWindow` target to attach an id to:
- **`combo_process`** — process settings are not a sidebar combo box in the current
OrcaSlicer layout, so there is no combo control to instrument.
- **`btn_add`** — the add/import-object control is a `GLToolbar` item rendered
*inside* the GL canvas, not a `wxWindow`.
- **`tab_prepare` / `tab_preview`** — the Prepare and Preview notebook pages are
both backed by the **same** window, and the per-tab buttons are private; there is
no distinct stable window to target.
For controls that are not instrumented, scripts should fall back to class / label /
path lookup (for wx controls) or ImGui-item lookup (for ImGui controls).
---
## 9. ImGui notes
ImGui is **immediate-mode**: an item is addressable only while it is being drawn in
the current frame. The automation backend records ImGui items each frame, and a
`refresh_ui` is forced before every read or action so that the latest frame's items
are captured.
Consequences and conventions:
- Use [`sync.wait_for`](#syncwait_for) to wait for a transient gizmo or panel item
to appear before acting on it.
- ImGui items are reported with `backend: "imgui"`, a `path` of the form
`ImGui/<window>/<label>`, and that **path doubles as the item's `id`** in v1.
- ImGui items are **flat** — they have no `children` and are listed under their
window.
- Labels may include ImGui `##`-id suffixes (the part after `##` that ImGui uses to
disambiguate identically labeled widgets).
- Raw `ImGui::` gizmos that are *not* routed through the instrumented
`ImGuiWrapper` widgets (for example some Emboss / SVG / Text gizmo controls) are
only covered at the **window level** in v1; their individual sub-items are not
enumerated.
---
## 10. Platform & display caveats
- **Input requires a focused, visible window.** OS-level input injection uses
`wxUIActionSimulator`, which requires a focused, visible window. The click path
raises and focuses the target's top-level window first.
- **Linux CI needs a display.** There must be an X display available; wrap test
runs with `xvfb-run` (for example, `xvfb-run -a python example_slice.py ...`).
- **Input is asynchronous.** Do **not** rely on fixed sleeps. Use
[`sync.wait_for`](#syncwait_for) — for example, wait for `btn_export` to become
`enabled` after slicing completes — rather than sleeping for a guessed duration.
- **`screenshot.window` reads the screen.** It captures the on-screen, DWM-composited
framebuffer, so the target window must be visible and unobscured, and the result is
in physical pixels (see HiDPI caveat under [`screenshot.window`](#screenshotwindow)).
The capture includes the GL 3D viewport as currently shown (model or toolpaths).
- **Single-client / serialized.** v1 handles one request at a time; issue requests
sequentially from a single client.
- **GUI-thread marshaling.** Every backend call is marshaled onto the GUI thread
with a timeout. A wedged or unresponsive GUI returns error `1004`.
---
## 11. Quick start
Using the reference client in `tools/automation/orca_automation.py`:
```python
from orca_automation import OrcaClient
orca = OrcaClient(port=13619)
print(orca.version()) # {'version': '1.0.0', ...}
orca.select_view("prepare") # switch to the 3D editor
orca.open(r"C:\models\part.stl") # load a model at runtime (synchronous)
orca.click({"id": "btn_slice"}) # start slicing the plate
orca.wait_for({"id": "btn_export"}, # wait until slicing finishes
state="enabled", timeout_ms=180000)
orca.select_view("preview") # switch to the sliced G-code preview
png = orca.screenshot() # on-screen capture (incl. 3D view)
with open("window.png", "wb") as f:
f.write(png)
```
For a full, runnable end-to-end example — launching OrcaSlicer with the automation
flags, loading a model, slicing, waiting for completion, and saving a window PNG —
see `tools/automation/example_slice.py`.
---
## 12. Future work
Planned enhancements beyond v1:
- **Authentication token** plus a Preferences toggle to enable/disable the server
from the GUI.
- **WebSocket push events** for real-time UI/state notifications (instead of
polling).
- **Per-item ImGui gizmo instrumentation** so individual gizmo sub-controls (Emboss
/ SVG / Text, etc.) are addressable, not just at the window level.
- **More widget ids** — the process combo, the add/import button, and the
Prepare/Preview tabs once they expose stable windows.
- An **MCP wrapper** to expose the automation surface to model-context tooling.
---
## Verification (v1)
This section records the final regression gate for the v1 feature: confirmation
that the protocol core is covered by unit tests, that the existing test suites
are unaffected, and that the **disabled path (automation OFF, the default) is a
true no-op** — zero new threads, zero socket binds, zero allocations, and zero
behavior change.
### Unit-suite results (Release, Windows / MSVC, Ninja Multi-Config)
| Suite | Result |
|---|---|
| `automation` (protocol core) | **32 / 32 passed** |
| `libslic3r` (most affected by the additive `PrintConfig.cpp` CLI options) | **99 / 99 passed** |
| `fff_print` | **14 / 14 passed** |
| `libnest2d` | **14 / 14 passed** |
| `sla_print` | **21 / 21 passed** |
| `slic3rutils` | 3 / 5 passed — 2 pre-existing `[OrcaCloudServiceAgent]` SEGFAULTs, **unrelated to automation** (see note) |
> The two `slic3rutils` failures are `Orca cloud flat/nested session resolves
> display name consistently`. They exercise `Slic3r::OrcaCloudServiceAgent`, which
> the automation branch does **not** touch (verified via `git diff --stat
> main...HEAD` — no change to `src/slic3r/Utils/OrcaCloudServiceAgent.*` or
> `tests/slic3rutils/*`). They are pre-existing and not a regression introduced by
> this feature.
### Static disabled-path audit (the core regression guarantee)
Verified by code reading that with no `--automation-server` flag:
- **Flag defaults off.** `m_automation_port` defaults to `0`
(`src/slic3r/GUI/GUI_App.hpp:249`); `is_automation_enabled()` returns
`m_automation_port > 0` (`GUI_App.hpp:386`) → `false` by default.
- **No server / thread / socket.** `post_init()` calls
`start_automation_server()` **only** when
`init_params->automation_port > 0` (`src/slic3r/GUI/GUI_App.cpp:737-740`), and
`start_automation_server()` itself early-returns when `m_automation_port <= 0`
(`GUI_App.cpp:7097`). The backend / dispatcher / beast server objects are
constructed nowhere else → no `orca_automation` thread and no localhost bind
when the flag is absent.
- **Recording hooks short-circuit.** `ImGuiWrapper::automation_record_last_item`
has as its **first statement** `if (!wxGetApp().is_automation_enabled())
return;` (`src/slic3r/GUI/ImGuiWrapper.cpp:576-577`) — a single bool check, no
`ImGuiItemRecord` allocation and no `ImGuiItemTable` access on the disabled
path. In `ImGuiWrapper::render()` the window-enumeration loop and
`swap_frame()` are fully wrapped in `if (wxGetApp().is_automation_enabled())`
(`ImGuiWrapper.cpp:599-611`); when off, `render()` is its original
`ImGui::Render()` + `render_draw_data()` plus one bool check.
- **Instrumentation is inert.** The ~7 `set_automation_id(...)` calls
(`MainFrame.cpp:1330,1389,1841,1842`; `Plater.cpp:1772,2172,5068`) only store a
pointer into a static registry and bind a `wxEVT_DESTROY` pruning handler
(`src/slic3r/GUI/Automation/AutomationRegistry.cpp:24-36`). The registry is
**read** only via `window_for_automation_id` / `automation_id_of`, which are
called solely by the backend while the server is running → harmless when off.
- **CLI options are purely additive.** `automation_server` (coBool, default
`false`) and `automation_server_port` (coInt, default `13619`) are new `add()`
entries appended after `enable_timelapse`
(`src/libslic3r/PrintConfig.cpp:10794-10805`); no existing option is changed.
`GUI_InitParams::automation_port` defaults to `0`
(`src/slic3r/GUI/GUI_Init.hpp:37`) and is set only when `--automation-server`
is supplied (`src/OrcaSlicer.cpp:1345-1348`).
**Conclusion:** with automation OFF (the default), the feature allocates nothing
and changes nothing — the only added cost on any hot path is a single boolean
comparison.
### Deferred manual runtime checks (require a display / Xvfb)
These need a live GUI and cannot be run headlessly in CI; they are the manual
acceptance steps:
1. Launch **without** `--automation-server``curl http://127.0.0.1:13619/`
fails to connect (no listener); no `orca_automation` thread exists.
2. Launch **with** `--automation-server --automation-server-port=13619`
`GET /` returns the health text; `POST /jsonrpc {"method":"automation.version"}`
returns version / protocol / capabilities; `widget.get {"target":{"id":"btn_slice"}}`
returns a node with a sensible screen rect.
3. Interactive sanity: open a gizmo / move sliders with automation OFF → no
visual or behavior change.
See `tools/automation/example_slice.py` for the runnable end-to-end path.

View File

@@ -0,0 +1,608 @@
# `file.open` Automation Method Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Add a `file.open` JSON-RPC automation method that loads one or more files into an already-running OrcaSlicer instance by calling `Plater::load_files(...)` synchronously on the GUI thread.
**Architecture:** Follows the existing `screenshot.window` / `app.state` method pattern. A new pure-virtual `open_files(paths)` is added to the wx-free `IUiBackend` interface; `WxUiBackend` implements it via the existing `run_on_gui(...)` GUI-thread marshal calling `Plater::load_files`; the `JsonRpcDispatcher` gains a `file.open` route, a param-parsing helper, and a new `kErrLoadFailed = 1007` error code. The unit-testable surface (dispatcher + param validation + routing) is driven against `MockUiBackend`.
**Tech Stack:** C++17, nlohmann::json, Catch2 v2 (`catch_all.hpp` / `Catch2WithMain`), wxWidgets, CMake + Ninja Multi-Config. Python 3 reference client (stdlib only).
---
## Design-spec note (resolve before coding)
The design spec's error table reads `1002 | kInvalidParams | paths missing/empty…`, but in the codebase `kInvalidParams` is the standard JSON-RPC code **`-32602`**, while `1002` is `kErrNotActionable`. The spec's **Constant column (`kInvalidParams`) is authoritative** and matches every other param-validation path in the dispatcher (e.g. `m_input_type` throws `kInvalidParams` for a bad `text`). This plan therefore validates `file.open` params with **`kInvalidParams` (-32602)**, exactly like the existing handlers, and the tests assert `== kInvalidParams`. The literal "1002" in the spec is a typo; do not emit code 1002 for param errors.
## File Structure
| File | Change | Responsibility |
|---|---|---|
| `src/slic3r/GUI/Automation/IUiBackend.hpp` | modify | Add pure-virtual `int open_files(paths)` to the backend abstraction (stays wx-free). |
| `src/slic3r/GUI/Automation/JsonRpcDispatcher.hpp` | modify | Add `kErrLoadFailed = 1007` constant + `m_file_open` declaration. |
| `src/slic3r/GUI/Automation/JsonRpcDispatcher.cpp` | modify | Add `parse_paths` helper, `m_file_open` body, dispatch route, capabilities entry. |
| `src/slic3r/GUI/Automation/WxUiBackend.hpp` | modify | Declare `open_files` override. |
| `src/slic3r/GUI/Automation/WxUiBackend.cpp` | modify | Implement `open_files` via `run_on_gui``Plater::load_files`. |
| `tests/automation/MockUiBackend.hpp` | modify | `open_files` override: record paths + return-count + fail knob. |
| `tests/automation/test_dispatcher.cpp` | modify | Catch2 tests for routing, string/array, validation, failure, capabilities. |
| `tools/automation/orca_automation.py` | modify | `open(self, paths)` client wrapper. |
| `tools/automation/example_slice.py` | modify | Launch without a model arg, then `orca.open([model])`. |
| `doc/automation.md` | modify | Document the method, capabilities, error `1007`. |
**Build/test layout:** Ninja Multi-Config in `build/`. The unit suite target is `automation_tests`; its sources (`tests/automation/CMakeLists.txt`) compile `JsonRpcDispatcher.cpp` + `MockUiBackend` but **not** `WxUiBackend.cpp`. So dispatcher/mock changes are fully unit-testable headlessly; `WxUiBackend.cpp` is verified by the full app build only.
---
## Task 1: Extend the backend abstraction (interface + mock + error code)
Adds the `open_files` contract so tests can be written. Adding a pure virtual to `IUiBackend` forces every implementation to provide it — in the unit-test target that is only `MockUiBackend`, so this task keeps the `automation_tests` build green.
**Files:**
- Modify: `src/slic3r/GUI/Automation/IUiBackend.hpp`
- Modify: `src/slic3r/GUI/Automation/JsonRpcDispatcher.hpp:19`
- Modify: `tests/automation/MockUiBackend.hpp`
- [ ] **Step 1: Add the error constant**
In `src/slic3r/GUI/Automation/JsonRpcDispatcher.hpp`, after the existing `kErrDisabled` line (currently line 19), add:
```cpp
constexpr int kErrDisabled = 1006;
constexpr int kErrLoadFailed = 1007; // file.open: load_files returned empty / threw
```
- [ ] **Step 2: Add the pure-virtual `open_files` to the interface**
In `src/slic3r/GUI/Automation/IUiBackend.hpp`, inside `class IUiBackend`, immediately after the `screenshot_window` pure virtual (currently line 97), add:
```cpp
// Load one or more files (absolute paths) into the running instance on the GUI
// thread. Returns the number of objects added to the scene (load_files(...).size()).
// Throws AutomationError(kErrLoadFailed) when nothing loads. Header stays wx-free:
// the concrete LoadStrategy is chosen inside WxUiBackend, not exposed here.
virtual int open_files(const std::vector<std::string>& paths) = 0;
```
- [ ] **Step 3: Implement `open_files` in the mock with record + knobs**
In `tests/automation/MockUiBackend.hpp`: add an include for the error constant near the top (after the `IUiBackend.hpp` include on line 2):
```cpp
#include "slic3r/GUI/Automation/IUiBackend.hpp"
#include "slic3r/GUI/Automation/JsonRpcDispatcher.hpp" // kErrLoadFailed
```
Add recorded-call + canned-output members. After the `screenshot_window_count` recorded field (line 20) add:
```cpp
int screenshot_window_count = 0;
std::vector<std::vector<std::string>> opened_paths; // paths of each open_files()
```
After the `click_result` canned field (line 26) add:
```cpp
bool click_result = true;
int open_return_count = 0; // value open_files() returns
bool open_should_fail = false; // when true, open_files() throws kErrLoadFailed
```
Add the override next to the other overrides, after `screenshot_window` (lines 49-51):
```cpp
PngImage screenshot_window(const UiNode*) override {
++screenshot_window_count; return canned_png;
}
int open_files(const std::vector<std::string>& paths) override {
opened_paths.push_back(paths);
if (open_should_fail)
throw AutomationError(kErrLoadFailed, "mock load failed");
return open_return_count;
}
```
- [ ] **Step 4: Build the unit-test target to confirm it still compiles**
Run: `cmake --build build --config RelWithDebInfo --target automation_tests`
Expected: build succeeds (the new pure virtual is satisfied by the mock; no behavior change yet).
- [ ] **Step 5: Commit**
```bash
git add src/slic3r/GUI/Automation/IUiBackend.hpp src/slic3r/GUI/Automation/JsonRpcDispatcher.hpp tests/automation/MockUiBackend.hpp
git commit -m "feat(automation): add open_files to backend interface + kErrLoadFailed (1007)"
```
---
## Task 2: `file.open` dispatcher handler (parse, route, validate, fail)
Implements the full JSON-RPC handler against the mock: param parsing (string or array), validation, routing to `open_files`, and `kErrLoadFailed` propagation.
**Files:**
- Modify: `src/slic3r/GUI/Automation/JsonRpcDispatcher.hpp:49` (declaration)
- Modify: `src/slic3r/GUI/Automation/JsonRpcDispatcher.cpp`
- Test: `tests/automation/test_dispatcher.cpp`
- [ ] **Step 1: Write the failing happy-path test (array of paths)**
Append to `tests/automation/test_dispatcher.cpp`:
```cpp
TEST_CASE("file.open with an array of paths routes to backend", "[automation][rpc]") {
MockUiBackend mock;
mock.open_return_count = 3;
JsonRpcDispatcher d(mock);
const json resp = d.dispatch({{"jsonrpc","2.0"},{"id",1},{"method","file.open"},
{"params",{{"paths", json::array({"C:/abs/a.stl","C:/abs/b.stl"})}}}});
CHECK(resp.at("result").at("ok") == true);
CHECK(resp.at("result").at("loaded") == 3);
REQUIRE(mock.opened_paths.size() == 1);
REQUIRE(mock.opened_paths[0].size() == 2);
CHECK(mock.opened_paths[0][0] == "C:/abs/a.stl");
CHECK(mock.opened_paths[0][1] == "C:/abs/b.stl");
}
```
- [ ] **Step 2: Run the test to verify it fails**
Run: `cmake --build build --config RelWithDebInfo --target automation_tests && build/tests/automation/RelWithDebInfo/automation_tests.exe "file.open with an array of paths routes to backend"`
Expected: FAIL — `file.open` is an unknown method, so the response carries `error.code == -32601` and has no `result` (the `resp.at("result")` access throws). (If the exe path differs on your machine, locate it with `find build -iname automation_tests.exe`.)
- [ ] **Step 3: Declare the handler**
In `src/slic3r/GUI/Automation/JsonRpcDispatcher.hpp`, after the `m_screenshot_window` declaration (currently line 49) add:
```cpp
nlohmann::json m_screenshot_window(const nlohmann::json& params);
nlohmann::json m_file_open(const nlohmann::json& params);
```
- [ ] **Step 4: Add the `parse_paths` helper and `m_file_open` body**
In `src/slic3r/GUI/Automation/JsonRpcDispatcher.cpp`, add a `parse_paths` helper. Place it in the anonymous namespace that also holds `parse_keys` — insert it right before that namespace's closing `} // namespace` (currently line 130):
```cpp
// "paths" may be a single string ("C:/a.stl") or an array of strings. Returns the
// non-empty absolute paths; throws kInvalidParams when paths is missing, not a
// string/array, contains a non-string entry, or yields no non-empty path.
std::vector<std::string> parse_paths(const nlohmann::json& params) {
if (!params.is_object() || !params.contains("paths"))
throw AutomationError(kInvalidParams, "file.open requires 'paths'");
const auto& p = params.at("paths");
std::vector<std::string> out;
if (p.is_string()) {
out.push_back(p.get<std::string>());
} else if (p.is_array()) {
for (const auto& e : p) {
if (!e.is_string())
throw AutomationError(kInvalidParams, "'paths' entries must be strings");
out.push_back(e.get<std::string>());
}
} else {
throw AutomationError(kInvalidParams, "'paths' must be a string or array");
}
out.erase(std::remove_if(out.begin(), out.end(),
[](const std::string& s) { return s.empty(); }),
out.end());
if (out.empty())
throw AutomationError(kInvalidParams, "'paths' is empty");
return out;
}
```
(`<algorithm>` for `std::remove_if` is already included at the top of the file, line 4.)
Add the handler body next to the other handlers. After `m_screenshot_window` (currently ends line 343, just before the final `}}} // namespace`), add:
```cpp
nlohmann::json JsonRpcDispatcher::m_file_open(const nlohmann::json& params) {
const std::vector<std::string> paths = parse_paths(params);
const int loaded = m_backend.open_files(paths);
return { {"ok", true}, {"loaded", loaded} };
}
```
- [ ] **Step 5: Add the dispatch route**
In `JsonRpcDispatcher::dispatch`, after the `screenshot.window` route (currently line 195) add:
```cpp
if (method == "screenshot.window") return make_result(id, m_screenshot_window(params));
if (method == "file.open") return make_result(id, m_file_open(params));
```
- [ ] **Step 6: Run the happy-path test to verify it passes**
Run: `cmake --build build --config RelWithDebInfo --target automation_tests && build/tests/automation/RelWithDebInfo/automation_tests.exe "file.open with an array of paths routes to backend"`
Expected: PASS — 1 test case, all assertions passed.
- [ ] **Step 7: Add the remaining handler tests (string, validation, failure)**
Append to `tests/automation/test_dispatcher.cpp`:
```cpp
TEST_CASE("file.open accepts a bare string path", "[automation][rpc]") {
MockUiBackend mock;
mock.open_return_count = 1;
JsonRpcDispatcher d(mock);
const json resp = d.dispatch({{"jsonrpc","2.0"},{"id",2},{"method","file.open"},
{"params",{{"paths","C:/abs/a.stl"}}}});
CHECK(resp.at("result").at("loaded") == 1);
REQUIRE(mock.opened_paths.size() == 1);
REQUIRE(mock.opened_paths[0].size() == 1);
CHECK(mock.opened_paths[0][0] == "C:/abs/a.stl");
}
TEST_CASE("file.open with missing paths -> invalid params", "[automation][rpc]") {
MockUiBackend mock;
JsonRpcDispatcher d(mock);
const json resp = d.dispatch({{"jsonrpc","2.0"},{"id",3},{"method","file.open"},
{"params", json::object()}});
CHECK(resp.at("error").at("code") == kInvalidParams);
CHECK(mock.opened_paths.empty());
}
TEST_CASE("file.open with empty paths array -> invalid params", "[automation][rpc]") {
MockUiBackend mock;
JsonRpcDispatcher d(mock);
const json resp = d.dispatch({{"jsonrpc","2.0"},{"id",4},{"method","file.open"},
{"params",{{"paths", json::array()}}}});
CHECK(resp.at("error").at("code") == kInvalidParams);
CHECK(mock.opened_paths.empty());
}
TEST_CASE("file.open with a non-string entry -> invalid params", "[automation][rpc]") {
MockUiBackend mock;
JsonRpcDispatcher d(mock);
const json resp = d.dispatch({{"jsonrpc","2.0"},{"id",5},{"method","file.open"},
{"params",{{"paths", json::array({"C:/a.stl", 42})}}}});
CHECK(resp.at("error").at("code") == kInvalidParams);
CHECK(mock.opened_paths.empty());
}
TEST_CASE("file.open backend load failure -> 1007", "[automation][rpc]") {
MockUiBackend mock;
mock.open_should_fail = true;
JsonRpcDispatcher d(mock);
const json resp = d.dispatch({{"jsonrpc","2.0"},{"id",6},{"method","file.open"},
{"params",{{"paths","C:/abs/a.stl"}}}});
CHECK(resp.at("error").at("code") == kErrLoadFailed);
}
```
- [ ] **Step 8: Run all file.open tests to verify they pass**
Run: `cmake --build build --config RelWithDebInfo --target automation_tests && build/tests/automation/RelWithDebInfo/automation_tests.exe "file.open*"`
Expected: PASS — 6 test cases, all assertions passed.
- [ ] **Step 9: Commit**
```bash
git add src/slic3r/GUI/Automation/JsonRpcDispatcher.hpp src/slic3r/GUI/Automation/JsonRpcDispatcher.cpp tests/automation/test_dispatcher.cpp
git commit -m "feat(automation): add file.open dispatcher handler with validation + tests"
```
---
## Task 3: Advertise `file.open` in `automation.version` capabilities
**Files:**
- Modify: `src/slic3r/GUI/Automation/JsonRpcDispatcher.cpp:166-172`
- Test: `tests/automation/test_dispatcher.cpp`
- [ ] **Step 1: Write the failing capabilities test**
Append to `tests/automation/test_dispatcher.cpp`:
```cpp
TEST_CASE("automation.version capabilities include file.open", "[automation][rpc]") {
MockUiBackend mock;
JsonRpcDispatcher d(mock);
const json resp = d.dispatch({{"jsonrpc","2.0"},{"id",1},{"method","automation.version"}});
const auto& caps = resp.at("result").at("capabilities");
bool found = false;
for (const auto& c : caps) if (c == "file.open") found = true;
CHECK(found);
}
```
- [ ] **Step 2: Run the test to verify it fails**
Run: `cmake --build build --config RelWithDebInfo --target automation_tests && build/tests/automation/RelWithDebInfo/automation_tests.exe "automation.version capabilities include file.open"`
Expected: FAIL — `CHECK(found)` is false; `file.open` is not yet in the capabilities array.
- [ ] **Step 3: Add `file.open` to the capabilities array**
In `JsonRpcDispatcher::m_version` (currently lines 166-172), add `"file.open"` to the array:
```cpp
nlohmann::json JsonRpcDispatcher::m_version(const nlohmann::json&) {
return { {"version", kAutomationVersion},
{"protocol", "2.0"},
{"capabilities", nlohmann::json::array({
"tree.dump","tree.find","widget.get","input.click","input.type",
"input.key","sync.wait_for","app.state","screenshot.window",
"file.open" })} };
}
```
- [ ] **Step 4: Run the test to verify it passes**
Run: `cmake --build build --config RelWithDebInfo --target automation_tests && build/tests/automation/RelWithDebInfo/automation_tests.exe "automation.version capabilities include file.open"`
Expected: PASS.
- [ ] **Step 5: Run the whole automation suite to confirm no regressions**
Run: `cmake --build build --config RelWithDebInfo --target automation_tests && build/tests/automation/RelWithDebInfo/automation_tests.exe --order rand --warn NoAssertions`
Expected: PASS — all cases green (the pre-existing ~32 plus the 7 new `file.open`/capabilities cases ≈ 39).
- [ ] **Step 6: Commit**
```bash
git add src/slic3r/GUI/Automation/JsonRpcDispatcher.cpp tests/automation/test_dispatcher.cpp
git commit -m "feat(automation): advertise file.open in automation.version capabilities"
```
---
## Task 4: Implement `WxUiBackend::open_files` (real GUI-thread load)
Not covered by the headless unit suite (`WxUiBackend.cpp` is excluded from `automation_tests`); verified by the full app build + the manual runtime check in Task 8.
**Files:**
- Modify: `src/slic3r/GUI/Automation/WxUiBackend.hpp:21`
- Modify: `src/slic3r/GUI/Automation/WxUiBackend.cpp`
- [ ] **Step 1: Declare the override**
In `src/slic3r/GUI/Automation/WxUiBackend.hpp`, after the `screenshot_window` declaration (line 21) add:
```cpp
PngImage screenshot_window(const UiNode* target) override;
int open_files(const std::vector<std::string>& paths) override;
```
- [ ] **Step 2: Implement `open_files`**
In `src/slic3r/GUI/Automation/WxUiBackend.cpp`, add the implementation just before the final `}}} // namespace Slic3r::GUI::Automation` (currently line 306):
```cpp
int WxUiBackend::open_files(const std::vector<std::string>& paths) {
return run_on_gui(m_gui_timeout_ms, [&]() -> int {
Plater* plater = wxGetApp().plater();
if (plater == nullptr)
throw AutomationError(kErrLoadFailed, "no plater to load into");
// Default strategy matches drag-drop / Plater::load_files's own default: it
// routes .3mf as a project and meshes as models based on file content, so no
// as_project flag is needed in v1. ask_multi=false: never prompt.
const LoadStrategy strategy = LoadStrategy::LoadModel | LoadStrategy::LoadConfig;
std::vector<size_t> loaded;
try {
loaded = plater->load_files(paths, strategy, /*ask_multi=*/false);
} catch (const std::exception& e) {
throw AutomationError(kErrLoadFailed,
std::string("load_files failed: ") + e.what());
}
if (loaded.empty())
throw AutomationError(kErrLoadFailed, "load_files loaded nothing");
return static_cast<int>(loaded.size());
});
}
```
Notes for the implementer:
- `LoadStrategy` and its `operator|` (namespace `Slic3r`, from `libslic3r/Format/bbs_3mf.hpp`) are already in scope: `WxUiBackend.cpp` includes `Plater.hpp` (line 7), which transitively pulls in the enum, and this translation unit lives in `Slic3r::GUI::Automation` so unqualified `LoadStrategy` resolves via the enclosing `Slic3r` namespace. No new include is required.
- `Plater::load_files(const std::vector<std::string>&, LoadStrategy, bool)` is the existing string overload (`Plater.hpp:379`) — no `boost::filesystem::path` conversion needed.
- `kErrLoadFailed` comes from `JsonRpcDispatcher.hpp`, already included at line 4.
- An `AutomationError` thrown inside the `run_on_gui` lambda is captured by the helper's `set_exception` and rethrown from `fut.get()`, so the 1007 code propagates to the dispatcher unchanged.
- [ ] **Step 3: Build the full app to verify it compiles and links**
Run: `cmake --build build --config RelWithDebInfo --target OrcaSlicer`
Expected: build succeeds (no missing-symbol / pure-virtual errors; `WxUiBackend` is now concrete).
- [ ] **Step 4: Commit**
```bash
git add src/slic3r/GUI/Automation/WxUiBackend.hpp src/slic3r/GUI/Automation/WxUiBackend.cpp
git commit -m "feat(automation): implement WxUiBackend::open_files via Plater::load_files"
```
---
## Task 5: Python client wrapper `OrcaClient.open`
**Files:**
- Modify: `tools/automation/orca_automation.py:80-82`
- [ ] **Step 1: Add the `open` method**
In `tools/automation/orca_automation.py`, after the `key` method (ends line 82), add:
```python
def key(self, keys) -> dict:
# keys: "ctrl+s" or ["ctrl", "s"]
return self._call("input.key", {"keys": keys})
def open(self, paths) -> dict:
"""Load one or more files into the running instance at runtime.
`paths` is a single absolute path string or a list of them. Paths are read
from the host filesystem by the server (localhost-only). Returns
{"ok": True, "loaded": <count>}. Raises OrcaError 1007 on load failure."""
if isinstance(paths, str):
paths = [paths]
return self._call("file.open", {"paths": list(paths)})
```
- [ ] **Step 2: Smoke-test the wrapper's normalization offline (no server needed)**
Run:
```bash
python -c "import sys; sys.path.insert(0, 'tools/automation'); import orca_automation as m; c = m.OrcaClient.__new__(m.OrcaClient); c._call = lambda meth, params=None: (meth, params); print(c.open('C:/a.stl')); print(c.open(['C:/a.stl','C:/b.stl']))"
```
Expected output:
```
('file.open', {'paths': ['C:/a.stl']})
('file.open', {'paths': ['C:/a.stl', 'C:/b.stl']})
```
- [ ] **Step 3: Commit**
```bash
git add tools/automation/orca_automation.py
git commit -m "feat(automation): add OrcaClient.open() wrapper for file.open"
```
---
## Task 6: Update `example_slice.py` to load at runtime via `file.open`
**Files:**
- Modify: `tools/automation/example_slice.py:26-52`
- [ ] **Step 1: Launch without the model arg, then call `open`**
In `tools/automation/example_slice.py`, change the `subprocess.Popen` call (lines 26-31) to drop the trailing model positional:
```python
proc = subprocess.Popen([
args.orca,
"--automation-server",
f"--automation-server-port={args.port}",
])
```
Then replace the project-load wait block (currently lines 46-51) so the model is loaded at runtime via `file.open` instead of relying on a launch-time positional:
```python
# Load the model into the already-running instance, then wait until the
# project reports loaded. file.open is synchronous, so project_loaded is
# already true on return; the wait is a belt-and-suspenders guard.
orca.open([args.model])
deadline = time.time() + 30
while time.time() < deadline:
if orca.app_state().get("project_loaded"):
break
time.sleep(0.5)
```
- [ ] **Step 2: Byte-compile the script to confirm no syntax errors**
Run: `python -m py_compile tools/automation/example_slice.py`
Expected: no output, exit code 0.
- [ ] **Step 3: Commit**
```bash
git add tools/automation/example_slice.py
git commit -m "docs(automation): example_slice.py loads model at runtime via file.open"
```
---
## Task 7: Document `file.open` in `doc/automation.md`
**Files:**
- Modify: `doc/automation.md` (capabilities example §4 line 111-114; new method subsection after `screenshot.window`; error table §7)
- [ ] **Step 1: Add `file.open` to the capabilities example**
In `doc/automation.md`, update the `automation.version` result example (lines 111-114) to include `file.open`:
```json
"capabilities": [
"tree.dump", "tree.find", "widget.get", "input.click", "input.type",
"input.key", "sync.wait_for", "app.state", "screenshot.window", "file.open"
]
```
The §4 prose count is already written for this: "There are 11 methods … the 10 callable feature methods" now matches exactly (10 capability entries + `automation.version` = 11). Leave that sentence unchanged.
- [ ] **Step 2: Add the `file.open` method subsection**
In `doc/automation.md`, immediately after the `screenshot.window` method subsection (it ends just before the `---` on line 303) and before that `---`, insert:
```markdown
### `file.open`
Load one or more files into the **already-running** instance at runtime, by calling
`Plater::load_files(...)` directly on the GUI thread. This is the supported way to add
or swap a model without relaunching the process. Loading is **synchronous**: when the
call returns `ok: true`, `app.state().project_loaded` is already `true` (no polling
race).
**Params:**
| Param | Type | Required | Meaning |
|---|---|---|---|
| `paths` | string or array of strings | yes | One or more **absolute** file paths. A bare string is accepted and treated as a one-element list. Paths are read from the **host (server) filesystem** — client and server are localhost-only. |
`.3mf` files are routed as projects and meshes as models automatically, based on file
content (the same default strategy as drag-drop); there is no `as_project` flag in v1.
**Result:** `{ "ok": true, "loaded": <int> }`, where `loaded` is the number of objects
added to the scene (`load_files(...).size()`).
**Errors:**
- `-32602` (invalid params) — `paths` is missing, is not a string/array, contains a
non-string entry, or yields no non-empty path.
- `1007` (load failed) — `load_files` returned empty or threw (file not found, parse
error, or unsupported format).
- `1004` (GUI busy) — the GUI-thread marshal timed out. An extremely large model can
exceed the marshal timeout and surface here; documented, not mitigated in v1.
```
- [ ] **Step 3: Add the `1007` row to the error-code table**
In `doc/automation.md` §7, in the application-specific codes table, after the `1006` row (line 395) add:
```markdown
| `1006` | Disabled. |
| `1007` | Load failed — `file.open`'s `load_files` returned empty or threw (not found, parse error, unsupported format). |
```
- [ ] **Step 4: Commit**
```bash
git add doc/automation.md
git commit -m "docs(automation): document file.open method and error 1007"
```
---
## Final verification
- [ ] **Step 1: Full automation unit suite green**
Run: `cmake --build build --config RelWithDebInfo --target automation_tests && build/tests/automation/RelWithDebInfo/automation_tests.exe --order rand --warn NoAssertions`
Expected: PASS — all cases (pre-existing ~32 + 7 new) green, no `NoAssertions` warnings on the new cases.
- [ ] **Step 2: Full app builds**
Run: `cmake --build build --config RelWithDebInfo --target ALL_BUILD -- -m`
Expected: build succeeds.
- [ ] **Step 3: Manual runtime check (requires a display)**
Launch with `--automation-server` and **no** model arg, then from a Python shell:
```python
from orca_automation import OrcaClient
orca = OrcaClient(port=13619)
print(orca.open(["C:/abs/path/cube.stl"])) # -> {'ok': True, 'loaded': 1}
print(orca.app_state()["project_loaded"]) # -> True
open("window.png","wb").write(orca.screenshot()) # PNG shows the loaded model
```
Expected: `loaded >= 1`, `project_loaded == True`, screenshot shows the model.
- [ ] **Step 4: Gating check (automation OFF is a no-op)**
Confirm by reading: with no `--automation-server` flag, the server/backend/dispatcher are never constructed (`GUI_App.cpp` `start_automation_server()` early-return), so `file.open` is unreachable. No new hot-path cost beyond the existing single bool check. (See `doc/automation.md` §Verification — disabled-path audit; this feature adds no new gating surface.)
---
## Backward compatibility
Additive only: one new method (`file.open`), one new error code (`1007`), one new capabilities entry, and one new backend interface method. No existing method, profile, project-file handling, or default behavior changes. The method is reachable only when `--automation-server` is passed.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,117 @@
# Design — `file.open` automation method (runtime model loading)
**Date:** 2026-06-03
**Branch:** `feature/automation`
**Status:** Approved for implementation
---
## Problem
Today a model can be loaded into OrcaSlicer **only at process launch**: the model path
is passed as a CLI positional arg and OrcaSlicer's normal startup file-loading ingests
it. The JSON-RPC automation protocol has **no** load method, so swapping or adding a
model in an already-running instance requires a fresh process launch.
Driving the native File→Import dialog via `input.click` is not a viable substitute: the
OS file picker is not a `wxWindow`, so it never appears in the `tree.dump` hierarchy
(`WxUiBackend::dump_tree` walks `wxGetApp().mainframe` children only), and `input.click`
can only target nodes resolved from that tree (no raw-coordinate click). Blind typing via
`input.type` is mechanically possible but unobservable: the native picker is not a
`wxDialog`, so `app.state().modal_dialog` and `sync.wait_for` cannot gate on it, leaving
only sleep-and-hope timing. A direct API method is the clean fix.
## Goal
Add a `file.open` JSON-RPC method that loads one or more files into a running instance by
calling `Plater::load_files(...)` directly on the GUI thread. Out of scope: any
dialog-driving mechanism (intercept hook or true OS-level drive) — explicitly deferred.
---
## Protocol
- **Method:** `file.open`
- **Params:** `{ "paths": ["C:/abs/a.stl", ...] }`
- A bare string is also accepted: `{ "paths": "C:/abs/a.stl" }`.
- Paths must be **absolute**. The server reads them from the host filesystem
(client/server are localhost-only).
- **Result:** `{ "ok": true, "loaded": <count> }`
- `count` is `load_files(...).size()` — the number of objects added to the scene.
- **Errors:**
| Code | Constant | Condition |
|------|----------|-----------|
| -32602 | `kInvalidParams` | `paths` missing/empty, a non-string entry, or no non-empty path |
| 1004 | `kErrGuiBusy` | GUI-thread marshal timed out (`m_gui_timeout_ms`) |
| 1007 | `kErrLoadFailed` | `load_files` returned empty / threw (not found, parse error, unsupported format) — **new code** |
## Semantics — synchronous
`Plater::load_files` runs and completes on the GUI thread. The backend marshals via the
existing `run_on_gui(m_gui_timeout_ms, …)` helper and returns only after the load
finishes. Consequently, when `file.open` returns `ok:true`, `app.state().project_loaded`
is already `true` — there is no polling race.
Rejected alternative — async "fire-and-poll-`project_loaded`": adds client complexity and
loses a definitive per-call error result, with no benefit since loading is synchronous.
**Caveat:** an extremely large model could exceed `m_gui_timeout_ms` and surface as
`1004 kErrGuiBusy`. Documented; not mitigated in v1.
## Load strategy (v1 minimal)
Pass the default `LoadStrategy::LoadModel | LoadStrategy::LoadConfig` (identical to
drag-drop / `Plater::load_files`'s default) with `ask_multi = false`. This already routes
`.3mf` files as projects and meshes as models based on file content, so **no `as_project`
flag is needed in v1**. A future `{ "as_project": bool }` flag remains possible but is not
implemented now.
---
## Components / files to touch
Follows the existing `screenshot_window` / `app_state` method pattern.
1. **`src/slic3r/GUI/Automation/IUiBackend.hpp`** — add pure-virtual
`int open_files(const std::vector<std::string>& paths)` returning the loaded count,
throwing `AutomationError` on failure. Header stays wx-free (no `LoadStrategy` leak).
2. **`src/slic3r/GUI/Automation/WxUiBackend.{hpp,cpp}`** — implement `open_files`:
`run_on_gui(m_gui_timeout_ms, …)``wxGetApp().plater()->load_files(paths, default_strategy, false)`;
throw `kErrLoadFailed` if the returned vector is empty.
3. **`src/slic3r/GUI/Automation/JsonRpcDispatcher.{hpp,cpp}`** —
- add `constexpr int kErrLoadFailed = 1007;`
- declare + define `m_file_open(params)` (param parsing/validation; accept string or
array; require ≥1 non-empty string path)
- add dispatch route `if (method == "file.open") return make_result(id, m_file_open(params));`
- add `"file.open"` to the capabilities array in `m_version`.
4. **`tests/automation/MockUiBackend.hpp`** — `open_files` override recording the paths
vector + a configurable return-count (and a throw/fail knob).
5. **`tests/automation/test_dispatcher.cpp`** — Catch2 v2 tests:
- array of paths → routes to backend, returns `loaded` count
- bare-string path → normalized to one path
- missing/empty `paths``-32602` (`kInvalidParams`)
- backend load failure → `1007`
- `automation.version` capabilities array includes `"file.open"`
6. **`tools/automation/orca_automation.py`** — `open(self, paths)` wrapper (normalize
`str``[str]`, send `file.open`).
7. **`tools/automation/example_slice.py`** — launch **without** a model arg, then
`orca.open([model])`, then wait for `project_loaded`.
8. **`doc/automation.md`** — document method (params/result/errors), add to the
capabilities list, method index, and error table (`1007`).
---
## Testing / verification
- **Build (Windows):** `cmake --build . --config RelWithDebInfo --target ALL_BUILD -- -m`.
- **Unit:** `automation` Catch2 suite green including new tests (≈31 → ≈34 cases).
- **Manual:** launch with `--automation-server` and **no** model arg → call `file.open`
→ confirm `app.state().project_loaded` flips `true` and `screenshot.window` shows the
model.
- **Gating:** unchanged — the server only runs under `--automation-server`, so the method
is a no-op (unreachable) when automation is disabled.
## Backward compatibility
Additive only: a new method, a new error code, and a new capabilities entry. No change to
existing methods, profiles, project-file handling, or default behavior.

View File

@@ -0,0 +1,314 @@
# OrcaSlicer UI Automation — Design Spec
**Date:** 2026-06-03
**Status:** Approved design, pending implementation plan
**Topic:** Add an opt-in, externally-controllable UI automation interface to OrcaSlicer for automated GUI testing and future AI-agent control.
---
## 1. Overview
Add a localhost JSON-RPC server to a running OrcaSlicer GUI instance that lets an
**external** script (or AI agent) drive and observe the real GUI. "Driving" is done
the way a user would — simulated mouse/keyboard via `wxUIActionSimulator` — while
"observing" reads the live widget state and captures screenshots.
The interface must cover **three UI technologies** present in OrcaSlicer:
1. Native **wxWidgets** widgets (`wxWindow` hierarchy).
2. The **OpenGL 3D viewport** (`GLCanvas3D`) — screenshots via the existing
framebuffer/thumbnail path.
3. **Dear ImGui** immediate-mode controls (gizmo panels, in-canvas overlays,
notifications) — recorded as they are drawn, because there is no persistent tree.
## 2. Goals / Non-Goals
### Goals
- Let an external, language-agnostic client connect to a running instance and:
introspect the UI, locate widgets by stable name, perform input actions, wait on
conditions, and capture screenshots (including the 3D view as a separate image).
- Be safe by default: disabled unless explicitly enabled; bound to `127.0.0.1` only.
- Be testable in CI without a display (pure-logic units behind a mock backend).
- Ship a reference Python client, a runnable end-to-end example, protocol docs, and
C++ unit tests.
### Non-Goals (v1)
- No headless/offscreen automation — OS input injection needs a focused, visible
window (Linux CI requires a display, e.g. Xvfb).
- No auth token in v1 (documented future hardening; localhost-only is the boundary).
- No per-item coverage of raw-`ImGui::` gizmos (Emboss/SVG/Text). They get
window-level coverage; per-item is future work.
- No new scripting language embedded in the app; control is purely external over JSON-RPC.
- We do **not** modify the existing auth `HttpServer`.
## 3. Background — existing infrastructure (verified)
- **`wxUIActionSimulator`** is compiled into the wx build and already used
(`src/slic3r/GUI/GUI_ObjectList.cpp:211`). Cross-platform simulated input is available.
- **`GLCanvas3D::render_thumbnail()`** (`src/slic3r/GUI/GLCanvas3D.cpp:2210+`) →
`render_thumbnail_framebuffer()` (`:6352`) renders the 3D scene into a
`ThumbnailData` (RGBA) via an FBO + `glReadPixels`. `debug_output_thumbnail()`
(`:6099`) shows the `ThumbnailData → wxImage → PNG` conversion. This is the
separate-3D-screenshot path. `Plater::generate_thumbnail()`/`generate_thumbnails()`
wrap it.
- **`ImGuiWrapper`** (`src/slic3r/GUI/ImGuiWrapper.hpp`) is the chokepoint for nearly
all ImGui controls: `button`/`bbl_button`, `checkbox`/`bbl_checkbox`, `combo`,
`slider_float`, `input_double`, `radio_button`, `menu_item_with_icon`, plus
`begin`/`end` for windows. `imgui_internal.h` is in-tree, exposing
`ImGui::GetCurrentContext()->Windows`, item rects, and hovered/active id.
- **`HttpServer`** (`src/slic3r/GUI/HttpServer.{hpp,cpp}`, boost::beast, port 13618)
is used for cloud auth. **It cannot serve a POST body**`session::read_body()`
reads and discards the body and never replies (`HttpServer.cpp:57-65`). It is
effectively GET-only. We will **not** reuse or modify it.
- **`OtherInstanceMessageHandler`** (`src/slic3r/GUI/InstanceCheck.{hpp,cpp}`) is the
template for "start a localhost listener once the MainFrame exists, post events into
the GUI." Useful as a structural reference.
- **CLI args** are parsed in `src/OrcaSlicer.cpp` (`CLI::setup`/`CLI::run`) and flow
into the GUI run params consumed by `GUI_App::OnInit()`.
## 4. Architecture
```
External script / AI agent ──► Python client (orca_automation.py)
│ HTTP POST /jsonrpc (JSON-RPC 2.0) on 127.0.0.1:<port>
AutomationServer (dedicated boost::beast listener; own thread;
│ started only when --automation-server is set)
│ parse JSON-RPC envelope
JsonRpcDispatcher (pure logic — method registry; unit-testable)
│ marshal each call to the GUI thread via wxGetApp().CallAfter
│ + std::promise/future with a per-request timeout
IUiBackend (interface) ──► WxUiBackend (real, GUI thread) / MockBackend (tests)
├─ Introspection : walk wxWindow tree + read ImGui item table → unified JSON
├─ Locator : resolve automation-id / predicate → wx widget or ImGui item
├─ Actions : raise window, then wxUIActionSimulator click/type/key
├─ Sync : wait_for (poll condition) + app.state snapshot
└─ Screenshots : wx widget → wxDC→PNG ; 3D view → render_thumbnail()→PNG
```
### Components (new files unless noted)
All new code lives under `src/slic3r/GUI/Automation/`.
| Component | Responsibility |
|---|---|
| `AutomationServer.{hpp,cpp}` | Dedicated boost::beast HTTP listener with **POST + body** support; one `POST /jsonrpc` endpoint; returns `application/json`. Localhost-only. Own thread. |
| `JsonRpcDispatcher.{hpp,cpp}` | Parse JSON-RPC 2.0; route `method` → handler; build result/error. Depends only on `IUiBackend`. No wx/ImGui includes → unit-testable. |
| `IUiBackend.hpp` | Abstract interface: `dump_tree`, `find`, `get_widget`, `click`, `type`, `key`, `wait_for`, `app_state`, `screenshot_window`, `screenshot_viewport3d`. Uses plain structs (no wx types) so tests can mock it. |
| `WxUiBackend.{hpp,cpp}` | Real implementation. Runs on GUI thread. Walks `wxWindow` tree, reads the ImGui item table, drives `wxUIActionSimulator`, captures screenshots. |
| `MockUiBackend.{hpp,cpp}` (tests) | Deterministic fake tree + recorded actions for unit tests. |
| `AutomationRegistry.{hpp,cpp}` | Process-wide `wxWindow* → automation_id` map + reverse lookup; `set_automation_id(win, "id")` helper. Header is dependency-light so widget-construction code can call the helper unconditionally (it is a cheap no-op-safe registration). |
| `WidgetSerializer.{hpp,cpp}` | `wxWindow` → JSON node (name/id, class, label, screen-rect, enabled, shown, value via RTTI). |
| `ImGuiItemTable.{hpp,cpp}` | Per-frame recorder of ImGui items + live-window enumeration. Populated from `ImGuiWrapper`; read on GUI thread. |
### Touch points in existing files
- `src/slic3r/GUI/ImGuiWrapper.cpp` — add recording hooks inside the wrapped widget
methods and `begin`/`end`, **guarded by an `is_automation_enabled()` flag** so there
is zero overhead and zero behavior change when automation is off.
- `src/slic3r/GUI/GUI_App.{hpp,cpp}` — own the `AutomationServer`; start it in
`OnInit()` only when the flag is set; stop it on exit. Expose
`is_automation_enabled()`.
- `src/OrcaSlicer.cpp` — parse `--automation-server[=PORT]`; pass through GUI run params.
- A handful of widget-construction sites (Slice/Export buttons, preset combos, main
tabs, common dialog OK/Cancel, the 3D canvas) — add `set_automation_id(...)` calls
(~15-20 widgets in v1).
- CMake: add the new `Automation/` sources to the GUI target; add the unit-test target.
## 5. Transport & Protocol
- **Transport:** HTTP/1.1 on `127.0.0.1:<port>` (default **13619**, adjacent to the
auth server's 13618). Single endpoint: `POST /jsonrpc`, body is a JSON-RPC 2.0
request, response is a JSON-RPC 2.0 result/error. `GET /` returns a tiny health/version page.
- **Protocol:** JSON-RPC 2.0. `id`, `method`, `params`. Batch not required in v1.
### v1 methods
| Method | Params | Result |
|---|---|---|
| `automation.version` | — | `{version, protocol, capabilities[]}` |
| `tree.dump` | `{root?, max_depth?, visible_only?, include_imgui?}` | tree of nodes (wx + imgui) |
| `tree.find` | `{name?, class?, label?, value?, backend?}` | `[node...]` matches |
| `widget.get` | `{target}` | single node detail |
| `input.click` | `{target, button?=left, double?=false, modifiers?[]}` | `{ok}` |
| `input.type` | `{target?, text}` | `{ok}` |
| `input.key` | `{keys}` e.g. `"ctrl+s"` or `["ctrl","s"]` | `{ok}` |
| `sync.wait_for` | `{target, state: exists\|visible\|enabled\|value, value?, timeout_ms?=5000, poll_ms?=100}` | `{ok, elapsed_ms}` |
| `app.state` | — | `{active_tab, project_loaded, slicing, slice_progress, modal_dialog?, foreground}` |
| `screenshot.window` | `{target?}` (default main frame) | `{png_base64, width, height}` |
| `screenshot.viewport3d` | `{plate?, width?, height?}` | `{png_base64, width, height}` |
### Node shape (unified for wx and ImGui)
```json
{
"backend": "wx" | "imgui",
"id": "btn_slice", // automation id if set, else derived path id
"path": "MainFrame/.../btn_slice", // stable-ish positional path
"class": "Button", // wx class name or imgui item type
"label": "Slice plate",
"rect": { "x": 100, "y": 200, "w": 120, "h": 32 }, // screen coords
"enabled": true,
"visible": true,
"value": "PLA", // when applicable (text/choice/check/slider)
"children": [ ... ] // wx only; imgui items are flat under their window
}
```
### Error model (JSON-RPC `error.code`)
- `-32700` parse error, `-32601` method not found, `-32602` invalid params (standard).
- Application codes: `1001` widget/target not found, `1002` target not actionable
(disabled/hidden), `1003` wait timeout, `1004` GUI thread busy/timeout,
`1005` screenshot failed, `1006` automation feature disabled.
## 6. Threading model
- `AutomationServer` runs on its own thread and accepts connections; the dispatcher
parses on that thread.
- **Every** call touching wx/ImGui/GL is marshaled to the GUI thread with
`wxGetApp().CallAfter([...]{ ... })`; the server thread blocks on a `std::future`
with a per-request timeout (default 5 s; `wait_for` uses its own larger budget).
Timeout → error `1004`.
- This is mandatory: wx widgets, the ImGui context, and the GL context are not
thread-safe and are owned by the GUI thread.
- `CallAfter` is serviced even while modal dialogs run (nested event loop), so
automation can interact with dialogs.
## 7. Widget locator & automation IDs (wxWidgets)
- **Stable IDs:** `set_automation_id(window, "btn_slice")` registers the widget in
`AutomationRegistry`. Stored in a side map keyed by `wxWindow*` (not via `SetName`,
to avoid any coupling with wx's name-based lookups). Registration is removed on
widget destruction (bind to `wxEVT_DESTROY` or prune lazily on lookup).
- **Derived IDs:** for un-instrumented widgets, `WidgetSerializer` derives a positional
`path` (e.g. `MainFrame/Panel[2]/Button[0]`) so an AI agent can still target anything.
Named IDs are the preferred, stable path.
- **Locator resolution order:** exact automation id → exact path → predicate match
(name/class/label/value). Ambiguous matches return the list via `tree.find`; action
methods require a unique match or error `1001`.
- **v1 instrumented widgets (~15-20):** Slice/Export buttons, printer & filament preset
combos, the main tab buttons (`tp3DEditor`/`tpPreview`/`tpMonitor`/…), Add/Import,
common dialog OK/Cancel/Yes/No, the `GLCanvas3D` itself.
## 8. ImGui coverage (v1 = wrapper items + window introspection)
- **Item recording:** inside each `ImGuiWrapper` wrapped method, when
`is_automation_enabled()` is true, append the just-drawn item to a per-frame
`ImGuiItemTable` entry: `{window_name, label/id, type, rect, enabled, value}`.
Item rect comes from `ImGui::GetItemRectMin/Max()` (ImGui display coords) mapped to
**screen** coords via the `GLCanvas3D` client origin (`ClientToScreen`) and DPI scale.
- **Window enumeration:** via `imgui_internal.h`, enumerate `GetCurrentContext()->Windows`
for window name, rect, visibility, plus the global hovered/active item id.
- **Double-buffering:** the table is swapped at frame end (`ImGuiWrapper::render`) so
readers see a complete frame. Reads happen on the GUI thread (after marshaling), same
thread as rendering, so a simple front/back swap suffices.
- **Freshness:** because items exist only while drawn, before an ImGui tree read or
action the backend forces a canvas refresh and flushes events so the latest frame is
captured. `sync.wait_for` can poll for an ImGui item to appear (e.g. after opening a
gizmo).
- **Actions:** an ImGui target resolves to its recorded screen rect; `input.click`/
`input.type` use `wxUIActionSimulator` on that rect — identical action path to wx,
different rect source. Typing into an ImGui input works because simulated keystrokes
flow through the existing `ImGuiWrapper::update_key_data` bridge once the field is
focused by a click.
- **Limitation (documented):** raw-`ImGui::` gizmos (Emboss, SVG, Text) are covered at
the **window** level only in v1; per-item instrumentation is future work.
## 9. Screenshots
- **`screenshot.window`:** capture a `wxWindow` (default: main frame) via
`wxClientDC`/`wxWindowDC``wxBitmap``wxImage` → PNG → base64. Works for native
widgets but **not** for the GL canvas region (returns black there) — hence the
separate 3D method.
- **`screenshot.viewport3d`:** reuse `GLCanvas3D::render_thumbnail()` (FBO +
`glReadPixels`) → `ThumbnailData``wxImage` (per `debug_output_thumbnail`) → PNG →
base64. Optional `plate`, `width`, `height` params. Runs on the GUI thread with the GL
context current.
## 10. Activation & security
- **Off by default.** Enabled by CLI flag `--automation-server[=PORT]` (default port
13619). (An app-config/Preferences toggle may be added later; v1 is flag-only.)
- **Bind `127.0.0.1` only.** No external interface.
- **No token in v1** (per decision); documented as a recommended future hardening,
along with an optional `--automation-token`.
- When disabled: no listener, no thread, and all `ImGuiWrapper` recording hooks are
skipped — **zero** runtime overhead and **zero** behavior change. This satisfies the
project's "features gated by options must not affect existing behavior when disabled"
constraint.
## 11. Testability
- `JsonRpcDispatcher` depends only on `IUiBackend` and has **no** wx/ImGui/GL includes.
- **C++ unit tests (Catch2), display-free, run in CI:**
- JSON-RPC envelope parse/validate/dispatch (good + malformed input, error codes).
- Method routing and param validation for every v1 method against `MockUiBackend`.
- `WidgetSerializer` node shape (fed a synthetic node model, not real wx widgets).
- Locator resolution: exact id, path, predicate, ambiguity, not-found.
- The only piece needing a real GUI is `WxUiBackend`; it is exercised by the manual
end-to-end example, not by CI unit tests.
## 12. Deliverables
- **C++:** the `Automation/` components, `ImGuiWrapper` recording hooks, widget
instrumentation, CLI flag plumbing, `GUI_App` lifecycle, CMake wiring.
- **`tools/automation/orca_automation.py`:** reference Python client wrapping the
JSON-RPC calls (`connect`, `version`, `dump_tree`, `find`, `click`, `type`, `key`,
`wait_for`, `app_state`, `screenshot`, `screenshot_3d`).
- **`tools/automation/example_slice.py`:** runnable end-to-end flow — launch OrcaSlicer
with the flag, load a model, click Slice, `wait_for` completion, save a 3D-preview PNG.
Doubles as a manual smoke test.
- **`doc/automation.md`:** protocol reference (methods, params, results, error codes),
node shape, automation-id naming conventions, ImGui notes, platform/display caveats.
- **`tests/`:** Catch2 unit-test target for the dispatch/serialize/locator logic.
## 13. New / changed file inventory
**New**
- `src/slic3r/GUI/Automation/AutomationServer.{hpp,cpp}`
- `src/slic3r/GUI/Automation/JsonRpcDispatcher.{hpp,cpp}`
- `src/slic3r/GUI/Automation/IUiBackend.hpp`
- `src/slic3r/GUI/Automation/WxUiBackend.{hpp,cpp}`
- `src/slic3r/GUI/Automation/AutomationRegistry.{hpp,cpp}`
- `src/slic3r/GUI/Automation/WidgetSerializer.{hpp,cpp}`
- `src/slic3r/GUI/Automation/ImGuiItemTable.{hpp,cpp}`
- `tools/automation/orca_automation.py`
- `tools/automation/example_slice.py`
- `doc/automation.md`
- `tests/automation/` (Catch2 target) + `MockUiBackend.{hpp,cpp}`
**Changed**
- `src/slic3r/GUI/ImGuiWrapper.cpp` (guarded recording hooks)
- `src/slic3r/GUI/GUI_App.{hpp,cpp}` (server lifecycle, `is_automation_enabled()`)
- `src/OrcaSlicer.cpp` (CLI flag)
- ~15-20 widget-construction sites (`set_automation_id`)
- `src/slic3r/GUI/CMakeLists.txt` + `tests/CMakeLists.txt`
## 14. Known constraints & limitations
- OS input injection requires the OrcaSlicer window **focused and visible**; the backend
raises/focuses the main window before injecting. Linux CI needs a display (Xvfb).
- Input is asynchronous at the OS level; correctness relies on `sync.wait_for` rather
than fixed sleeps.
- ImGui items are only addressable while their host panel is drawn.
- Raw-`ImGui::` gizmos: window-level only in v1.
- Single-client assumption in v1 (serialized request handling); no concurrent sessions
contract.
## 15. Future work (out of scope for v1)
- Optional auth token + Preferences toggle.
- WebSocket channel for server-push events (slice progress, dialog-appeared).
- Per-item instrumentation for raw-`ImGui::` gizmos.
- An MCP server wrapping the JSON-RPC client for direct AI-agent integration.
- Optional integration of Dear ImGui Test Engine for deterministic ImGui interaction.
## 16. Verification plan
- **CI:** Catch2 unit tests (dispatch/serialize/locator) pass with no display.
- **Manual / e2e:** run `tools/automation/example_slice.py` against a built OrcaSlicer
launched with `--automation-server`; confirm model loads, Slice runs, `wait_for`
returns on completion, and both a wx-window PNG and a 3D-viewport PNG are produced.
- **Regression:** build and run with automation **off**; confirm no new threads, no
listener, and ImGui rendering is byte-for-byte unchanged (hooks compiled out of the
hot path via the disabled flag).

View File

@@ -122,7 +122,7 @@ msgid "On highlighted overhangs only"
msgstr "Nur an hervorgehobenen Überhängen"
msgid "Erase all"
msgstr ""
msgstr "Alles löschen"
msgid "Highlight overhang areas"
msgstr "Bereiche mit Überhang hervorheben"
@@ -350,10 +350,10 @@ msgid "Fixed step drag"
msgstr "Fester Schritt ziehen"
msgid "Context Menu"
msgstr ""
msgstr "Kontextmenü"
msgid "Toggle Auto-Drop"
msgstr ""
msgstr "Automatisches Absenken umschalten"
msgid "Single sided scaling"
msgstr "Einseitige Skalierung"
@@ -509,10 +509,10 @@ msgid "Multiple"
msgstr "Mehrere"
msgid "Count"
msgstr ""
msgstr "Anzahl"
msgid "Gap"
msgstr ""
msgstr "Spalt"
msgid "Spacing"
msgstr "Abstand"
@@ -883,7 +883,7 @@ msgid "Advanced"
msgstr "Erweiterte Einstellungen"
msgid "Reset all options except the text and operation"
msgstr ""
msgstr "Alle Optionen außer dem Text und der Operation zurücksetzen"
msgid ""
"The text cannot be written using the selected font. Please try choosing a "
@@ -1805,16 +1805,16 @@ msgid "Info"
msgstr "Info"
msgid "Loading printer & filament profiles"
msgstr ""
msgstr "Lade Drucker- und Filamentprofile"
msgid "Creating main window"
msgstr ""
msgstr "Erstelle Hauptfenster"
msgid "Loading current preset"
msgstr ""
msgstr "Lade aktuelles Preset"
msgid "Showing main window"
msgstr ""
msgstr "Zeige Hauptfenster"
msgid ""
"The OrcaSlicer configuration file may be corrupted and cannot be parsed.\n"
@@ -1897,9 +1897,12 @@ msgid ""
"Please check your network connectivity\n"
"(HTTP %u)"
msgstr ""
"Verbindung zu OrcaCloud fehlgeschlagen.\n"
"Bitte überprüfen Sie Ihre Netzwerkverbindung\n"
"(HTTP %u)"
msgid "Cloud Error"
msgstr ""
msgstr "Cloud-Fehler"
msgid "Retrieving printer information, please try again later."
msgstr "Empfange Druckerinformationen, bitte später erneut versuchen."
@@ -6100,13 +6103,13 @@ msgid "Export"
msgstr "Exportieren"
msgid "Sync Presets"
msgstr ""
msgstr "Presets synchronisieren"
msgid "Pull and apply the latest presets from OrcaCloud"
msgstr ""
msgstr "Die neuesten Presets von OrcaCloud abrufen und anwenden"
msgid "You must be logged in to sync presets from cloud."
msgstr ""
msgstr "Sie müssen angemeldet sein, um Presets aus der Cloud zu synchronisieren."
msgid "Quit"
msgstr "Beenden"
@@ -6236,7 +6239,7 @@ msgid "Preset Bundle"
msgstr "Vorlagen-Bundle"
msgid "Syncing presets from cloud…"
msgstr ""
msgstr "Synchronisiere Presets aus der Cloud…"
msgid "Help"
msgstr "Hilfe"
@@ -8941,25 +8944,25 @@ msgid "If enabled, reverses the direction of zoom with mouse wheel."
msgstr "Wenn aktiviert, wird die Richtung des Zooms mit dem Mausrad umgekehrt."
msgid "Pan"
msgstr ""
msgstr "Schwenken"
msgid "Left Mouse Drag"
msgstr ""
msgstr "Linke Maustaste drücken"
msgid "Set the action that dragging the left mouse button should perform."
msgstr ""
msgstr "Legen Sie die Aktion fest, die das Ziehen der linken Maustaste ausführen soll."
msgid "Middle Mouse Drag"
msgstr ""
msgstr "Mittlere Maustaste drücken"
msgid "Set the action that dragging the middle mouse button should perform."
msgstr ""
msgstr "Legen Sie die Aktion fest, die das Ziehen der mittleren Maustaste ausführen soll."
msgid "Right Mouse Drag"
msgstr ""
msgstr "Rechte Maustaste drücken"
msgid "Set the action that dragging the right mouse button should perform."
msgstr ""
msgstr "Legen Sie die Aktion fest, die das Ziehen der rechten Maustaste ausführen soll."
msgid "Clear my choice on..."
msgstr "Meine Auswahl löschen bei ..."
@@ -8986,13 +8989,13 @@ msgstr ""
"der Datei."
msgid "Graphics"
msgstr ""
msgstr "Grafik"
msgid "Anti-aliasing"
msgstr ""
msgstr "Kantenglättung"
msgid "MSAA Multiplier"
msgstr ""
msgstr "MSAA-Multiplikator"
msgid ""
"Set the Multi-Sample Anti-Aliasing level.\n"
@@ -9004,12 +9007,19 @@ msgid ""
"\n"
"Requires application restart."
msgstr ""
"Stellen Sie die Stufe der Multi-Sample-Kantenglättung ein.\n"
"Höhere Werte führen zu glatteren Kanten, aber die Auswirkungen auf die "
"Leistung sind exponentiell.\n"
"Niedrigere Werte verbessern die Leistung auf Kosten von gezackten Kanten.\n"
"Wenn deaktiviert, wird empfohlen, FXAA zu aktivieren, um gezackte Kanten mit minimalen Auswirkungen auf die Leistung zu reduzieren.\n"
"\n"
"Erfordert einen Neustart der Anwendung."
msgid "Disabled"
msgstr "Deaktiviert"
msgid "FXAA post-processing"
msgstr ""
msgstr "FXAA-Nachbearbeitung"
msgid ""
"Applies Fast Approximate Anti-Aliasing as a screen-space pass.\n"
@@ -9017,26 +9027,34 @@ msgid ""
"\n"
"Takes effect immediately."
msgstr ""
"Führt Fast Approximate Anti-Aliasing als Bildschirmraum-Pass aus.\n"
"Nützlich, um die MSAA-Einstellung zu deaktivieren oder zu reduzieren, um die Leistung zu verbessern.\n"
"\n"
"ist sofort wirksam"
msgid "FPS"
msgstr ""
msgstr "FPS"
msgid "FPS cap"
msgstr ""
msgstr "FPS-Begrenzung"
msgid "(0 = unlimited)"
msgstr ""
msgstr "(0 = unbegrenzt)"
msgid ""
"Limits viewport frame rate to reduce GPU load and power usage.\n"
"Set to 0 for unlimited frame rate."
msgstr ""
"Begrenzt die Bildrate des Viewports, um die GPU-Auslastung und den "
"Energieverbrauch zu reduzieren.\n"
"Auf 0 setzen für unbegrenzte Bildrate."
msgid "Show FPS overlay"
msgstr ""
msgstr "FPS-Overlay anzeigen"
msgid "Displays current viewport FPS in the top-right corner."
msgstr ""
msgstr "Zeigt die aktuelle FPS des Viewports in der oberen rechten Ecke an."
msgid "Login region"
msgstr "Login region"
@@ -9207,13 +9225,17 @@ msgid "Skip AMS blacklist check"
msgstr "Überspringen der AMS Blacklist-Prüfung"
msgid "(Experimental) Keep painted feature after mesh change"
msgstr ""
msgstr "(Experimentell) Behalte bemalte Funktionen nach Mesh-Änderung bei"
msgid ""
"Attempt to keep painted features (color/seam/support/fuzzy etc.) after "
"changing the object mesh (such as cut/reload from disk/simplify/fix etc.)\n"
"Highly experimental! Slow and may create artifact."
msgstr ""
"Versuchen Sie, bemalte Funktionen (Farbe/Naht/Stütze/unscharf usw.) nach "
"Änderung des Objekt-Meshs (z. B. schneiden/neu laden von der Festplatte/vereinfachen/reparieren usw.) beizubehalten\n"
"Sehr experimentell! Langsam und kann Artefakte erzeugen."
msgid "Allow Abnormal Storage"
msgstr "Fehlerhaften Speicher zulassen"
@@ -10939,26 +10961,37 @@ msgid ""
" %s first layer %d %s, other layers %d %s\n"
" %s max delta %d %s, current delta %d %s\n"
msgstr ""
" - %s:\n"
" %s erste Schicht %d %s, andere Schichten %d %s\n"
" %s maximale Delta %d %s, aktuelle Delta %d %s\n"
msgid ""
"Some first-layer and other-layer temperature pairs exceed safety limits.\n"
msgstr ""
msgstr "Einige Temperaturpaare für die erste und andere Schicht überschreiten die Sicherheitsgrenzen.\n"
msgid ""
"\n"
"Invalid pairs:\n"
msgstr ""
"\n"
"Ungültige Paare:\n"
msgid ""
"\n"
"You can go back to edit values, or continue if this is intentional."
msgstr ""
"\n"
"Sie können zurückgehen, um die Werte zu bearbeiten, oder fortfahren, wenn dies "
"absichtlich ist."
msgid ""
"\n"
"\n"
"Continue anyway?"
msgstr ""
"\n"
"\n"
"Trotzdem fortfahren?"
msgid "Temperature Safety Check"
msgstr "Temperatur-Sicherheitsprüfung"
@@ -11042,6 +11075,10 @@ msgid ""
"\"%1%\"\n"
"and \"%2%\" will open without any changes."
msgstr ""
"Alle \"Neuer Wert\" Einstellungen in\n"
"\"%1%\"\n"
"werden gespeichert und \"%2%\" wird ohne Änderungen geöffnet."
msgid "Click the right mouse button to display the full text."
msgstr ""
@@ -11647,7 +11684,7 @@ msgid "Login"
msgstr "Anmelden"
msgid "Login failed. Please try again."
msgstr ""
msgstr "Anmeldung fehlgeschlagen. Bitte versuchen Sie es erneut."
msgid "[Action Required] "
msgstr "[Aktion erforderlich] "
@@ -11687,16 +11724,16 @@ msgid "Global shortcuts"
msgstr "Globale Tastaturkürzel"
msgid "Pan View"
msgstr ""
msgstr "Ansicht verschieben"
msgid "Rotate View"
msgstr ""
msgstr "Ansicht drehen"
msgid "Middle mouse button"
msgstr ""
msgstr "Mittlere Maustaste"
msgid "Zoom View"
msgstr ""
msgstr "Ansicht zoomen"
msgid ""
"Auto orients selected objects or all objects. If there are selected objects, "
@@ -12599,6 +12636,8 @@ msgid ""
"The Hollow base pattern is not supported by this support type; Rectilinear "
"will be used instead."
msgstr ""
"Das Hohl-Basis-Muster wird von diesem Stütztyp nicht unterstützt; Stattdessen "
"wird das Rechteckmuster verwendet."
msgid ""
"Support enforcers are used but support is not enabled. Please enable support."
@@ -14846,16 +14885,16 @@ msgid "Auto For Match"
msgstr "Automatisch für Übereinstimmung"
msgid "Enable filament dynamic map"
msgstr ""
msgstr "Dynamische Filamentzuordnung aktivieren"
msgid "Enable dynamic filament mapping during print."
msgstr ""
msgstr "Dynamische Filamentzuordnung während des Drucks aktivieren."
msgid "Has filament switcher"
msgstr ""
msgstr "Hat Filamentwechsler"
msgid "Printer has a filament switcher hardware (e.g., AMS)."
msgstr ""
msgstr "Der Drucker verfügt über eine Filamentwechsler-Hardware (z. B. AMS)."
msgid "Flush temperature"
msgstr "Spültemperatur"
@@ -15376,7 +15415,7 @@ msgstr ""
"unterstützt."
msgid "Z-buckling bias optimization (experimental)"
msgstr ""
msgstr "Z-Buckling-Bias-Optimierung (experimentell)"
msgid ""
"Tightens the gyroid wave along the Z (vertical) axis at low infill density "
@@ -15385,6 +15424,11 @@ msgid ""
"~30% sparse infill density and above. Only applies when Sparse infill "
"pattern is set to Gyroid."
msgstr ""
"Strafft die Gyroid-Welle entlang der Z-Achse (vertikal) bei geringer Fülldichte, "
"um die effektive vertikale Säulenlänge zu verkürzen und die Z-Achsen-Kompressions-"
"Knickfestigkeit zu verbessern. Der Filamentverbrauch bleibt erhalten. Keine "
"Auswirkung bei ~30% einfacher Fülldichte und darüber. Gilt nur, wenn das einfache "
"Füllmuster auf Gyroid eingestellt ist."
msgid "Sparse infill pattern"
msgstr "Füllmuster"
@@ -16274,7 +16318,7 @@ msgstr ""
"bringen.Setze den Wert auf 0, um diese Funktion zu deaktivieren."
msgid "Minimum non-zero part cooling fan speed"
msgstr ""
msgstr "Minimale nicht-null Lüftergeschwindigkeit für die Teilekühlung"
msgid ""
"Some part-cooling fans cannot start spinning when commanded below a certain "
@@ -16293,6 +16337,11 @@ msgid ""
"below the one you know it can actually spool at.\n"
"Set to 0 to deactivate."
msgstr ""
"Einige Teilekühlventilatoren können nicht zu drehen beginnen, wenn sie unter einem bestimmten PWM-Arbeitszyklus befehligt werden. Wenn dieser Wert über 0 eingestellt ist, wird jeder nicht-null-Teilekühlventilatorbefehl auf mindestens diesen Prozentsatz angehoben, damit der Lüfter zuverlässig startet. Ein Lüfterbefehl von 0 (Lüfter aus) wird immer genau eingehalten. Diese Begrenzung wird nach jeder anderen Lüfterberechnung angewendet (Erstschicht-Ramp-up, Schichtzeit-Interpolation, Überhangs-/Brücken-/Stützstruktur-Schnittstellen-/Glättungsüberschreibungen), sodass die Skalierung weiterhin im Bereich [dieser Wert, 100%] erfolgt.\n"
"\n"
"Wenn Ihre Firmware den Lüfter bereits unter einem Schwellenwert deaktiviert (z.B. Klipper's [fan] off_below: 0.10 schaltet den Lüfter aus, wenn der befehligte Arbeitszyklus unter 10% liegt), sollten idealerweise dieser Wert und der Firmware-Schwellenwert auf denselben Wert eingestellt werden. Wenn sie übereinstimmen (z.B. off_below: 0.10 in Klipper und 10% hier), garantiert der Slicer, dass er nie einen nicht-null-Wert emittiert, den die Firmware stillschweigend fallen lässt, und der Lüfter nie einen Wert erhält, der unter dem Wert liegt, den er tatsächlich anfahren kann.\n"
"\n"
"Setze den Wert auf 0, um diese Funktion zu deaktivieren."
msgid "%"
msgstr "%"
@@ -18383,7 +18432,7 @@ msgid "Enable filament ramming"
msgstr "Erlaube Filamentrammen"
msgid "Tool change on wipe tower"
msgstr ""
msgstr "Werkzeugwechsel auf dem Reinigungsturm"
msgid ""
"Force the toolhead to travel to the wipe tower before issuing the tool "
@@ -18394,6 +18443,8 @@ msgid ""
"this option if you want the tool change to always be issued above the wipe "
"tower instead."
msgstr ""
"Erzwinge, dass der Werkzeugkopf zum Reinigungsturm fährt, bevor der Werkzeugwechselbefehl (Tx) ausgegeben wird. Nur relevant für Mehrfach-Extruder (Mehrfach-Werkzeugkopf) Drucker, die einen Typ-2-Reinigungsturm verwenden. Standardmäßig überspringt Orca die Fahrt auf Mehrfach-Werkzeugkopf-Maschinen, da die Firmware den Kopfwechsel übernimmt, was dazu führen kann, dass der Tx-Befehl über dem gedruckten Teil ausgegeben wird. Aktivieren Sie diese Option, wenn Sie möchten, dass der Werkzeugwechsel immer über dem Reinigungsturm ausgegeben wird."
msgid "No sparse layers (beta)"
msgstr "Keine dünnen Schichten (Beta)"
@@ -19345,7 +19396,7 @@ msgstr ""
"verschiedene Materialien aufeinandertreffen."
msgid "Cool down from interface boost during prime tower"
msgstr "^"
msgstr "Abkühlung von der Schnittstellen-Boost während des Reinigungsturms"
msgid ""
"When interface-layer temperature boost is active, set the nozzle back to "
@@ -21994,12 +22045,18 @@ msgid ""
"\n"
"Available nozzle profiles for this printer:"
msgstr ""
"\n"
"\n"
"Verfügbare Düsenprofile für diesen Drucker:"
msgid ""
"\n"
"\n"
"Choose YES to switch existing preset:"
msgstr ""
"\n"
"\n"
"Wählen Sie JA, um das vorhandene Profil zu wechseln:"
msgid "Printer Created Successfully"
msgstr "Drucker erfolgreich erstellt"
@@ -23208,16 +23265,16 @@ msgid "Detection radius"
msgstr "Erkennungsradius"
msgid "Selected"
msgstr ""
msgstr "Ausgewählt"
msgid "Auto-generate"
msgstr ""
msgstr "Automatisch generieren"
msgid "Generate brim ears using Max angle and Detection radius"
msgstr ""
msgstr "Mausohren mit Maximalwinkel und Erkennungsradius generieren"
msgid "Add or Select"
msgstr ""
msgstr "Hinzufügen oder auswählen"
msgid ""
"Warning: The brim type is not set to \"painted\", the brim ears will not "
@@ -23230,7 +23287,7 @@ msgid "Set the brim type of this object to \"painted\""
msgstr "Den Brim-Typ dieses Objekts auf \"bemalt\" setzen"
msgid "invalid brim ears"
msgstr ""
msgstr "Ungültige Mausohren"
msgid "Brim Ears"
msgstr "Mausohren"

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
02.00.00.29
02.00.00.30

View File

@@ -472,6 +472,10 @@
"name": "Anycubic Generic PA-CF",
"sub_path": "filament/Anycubic Generic PA-CF.json"
},
{
"name": "Fiberon PA6-CF20 @Anycubic Kobra S1",
"sub_path": "filament/Polymaker/Fiberon PA6-CF20 @Anycubic Kobra S1.json"
},
{
"name": "Anycubic Generic PC",
"sub_path": "filament/Anycubic Generic PC.json"
@@ -588,6 +592,18 @@
"name": "Anycubic PLA+ @Anycubic Kobra X 0.4 nozzle",
"sub_path": "filament/Anycubic PLA+ @Anycubic Kobra X 0.4 nozzle.json"
},
{
"name": "Panchroma PLA @Anycubic Kobra S1",
"sub_path": "filament/Polymaker/Panchroma PLA @Anycubic Kobra S1.json"
},
{
"name": "Polymaker PLA Pro @Anycubic Kobra S1",
"sub_path": "filament/Polymaker/Polymaker PLA Pro @Anycubic Kobra S1.json"
},
{
"name": "Polymaker PLA Pro Metallic @Anycubic Kobra S1",
"sub_path": "filament/Polymaker/Polymaker PLA Pro Metallic @Anycubic Kobra S1.json"
},
{
"name": "Anycubic Generic PVA",
"sub_path": "filament/Anycubic Generic PVA.json"

View File

@@ -0,0 +1,81 @@
{
"type": "filament",
"name": "Fiberon PA6-CF20 @Anycubic Kobra S1",
"inherits": "fdm_filament_pa",
"from": "system",
"setting_id": "GFSL57_AC",
"filament_id": "GFL57",
"instantiation": "true",
"compatible_printers": [
"Anycubic Kobra S1 0.4 nozzle"
],
"filament_vendor": [
"Polymaker"
],
"filament_type": [
"PA6-CF"
],
"eng_plate_temp": [
"40"
],
"hot_plate_temp": [
"40"
],
"textured_plate_temp": [
"40"
],
"eng_plate_temp_initial_layer": [
"40"
],
"hot_plate_temp_initial_layer": [
"40"
],
"textured_plate_temp_initial_layer": [
"40"
],
"overhang_fan_speed": [
"100"
],
"filament_flow_ratio": [
"1.03"
],
"reduce_fan_stop_start_freq": [
"1"
],
"fan_cooling_layer_time": [
"30"
],
"filament_cost": [
"79.98"
],
"filament_density": [
"1.17"
],
"filament_max_volumetric_speed": [
"7.5"
],
"filament_retraction_length": [
"1.0"
],
"filament_z_hop": [
"0.0"
],
"nozzle_temperature_initial_layer": [
"300"
],
"fan_max_speed": [
"30"
],
"slow_down_layer_time": [
"6"
],
"nozzle_temperature": [
"300"
],
"temperature_vitrification": [
"74.2"
],
"nozzle_temperature_range_low": [
"280"
]
}

View File

@@ -0,0 +1,70 @@
{
"type": "filament",
"name": "Panchroma PLA @Anycubic Kobra S1",
"inherits": "fdm_filament_pla",
"from": "system",
"setting_id": "GFSPM001_AC",
"filament_id": "GFPM001",
"instantiation": "true",
"compatible_printers": [
"Anycubic Kobra S1 0.4 nozzle"
],
"filament_vendor": [
"Polymaker"
],
"filament_type": [
"PLA"
],
"cool_plate_temp": [
"50"
],
"eng_plate_temp": [
"50"
],
"hot_plate_temp": [
"50"
],
"textured_plate_temp": [
"50"
],
"cool_plate_temp_initial_layer": [
"50"
],
"eng_plate_temp_initial_layer": [
"50"
],
"hot_plate_temp_initial_layer": [
"50"
],
"textured_plate_temp_initial_layer": [
"50"
],
"filament_flow_ratio": [
"0.88"
],
"filament_cost": [
"0"
],
"filament_density": [
"1.32"
],
"filament_max_volumetric_speed": [
"20"
],
"slow_down_min_speed": [
"5"
],
"slow_down_layer_time": [
"10"
],
"temperature_vitrification": [
"62.5"
],
"additional_cooling_fan_speed": [
"0"
],
"enable_pressure_advance": [
"1"
]
}

View File

@@ -0,0 +1,66 @@
{
"type": "filament",
"name": "Polymaker PLA Pro @Anycubic Kobra S1",
"inherits": "fdm_filament_pla",
"from": "system",
"setting_id": "GFSL79_AC",
"filament_id": "GFL79",
"instantiation": "true",
"compatible_printers": [
"Anycubic Kobra S1 0.4 nozzle"
],
"filament_vendor": [
"Polymaker"
],
"filament_type": [
"PLA"
],
"cool_plate_temp": [
"50"
],
"eng_plate_temp": [
"50"
],
"hot_plate_temp": [
"50"
],
"textured_plate_temp": [
"50"
],
"cool_plate_temp_initial_layer": [
"50"
],
"eng_plate_temp_initial_layer": [
"50"
],
"hot_plate_temp_initial_layer": [
"50"
],
"textured_plate_temp_initial_layer": [
"50"
],
"filament_flow_ratio": [
"0.85"
],
"filament_cost": [
"0"
],
"filament_density": [
"1.23"
],
"filament_max_volumetric_speed": [
"16"
],
"slow_down_min_speed": [
"5"
],
"slow_down_layer_time": [
"10"
],
"temperature_vitrification": [
"55"
],
"additional_cooling_fan_speed": [
"0"
]
}

View File

@@ -0,0 +1,66 @@
{
"type": "filament",
"name": "Polymaker PLA Pro Metallic @Anycubic Kobra S1",
"inherits": "fdm_filament_pla",
"from": "system",
"setting_id": "GFSL80_AC",
"filament_id": "GFL80",
"instantiation": "true",
"compatible_printers": [
"Anycubic Kobra S1 0.4 nozzle"
],
"filament_vendor": [
"Polymaker"
],
"filament_type": [
"PLA"
],
"cool_plate_temp": [
"50"
],
"eng_plate_temp": [
"50"
],
"hot_plate_temp": [
"50"
],
"textured_plate_temp": [
"50"
],
"cool_plate_temp_initial_layer": [
"50"
],
"eng_plate_temp_initial_layer": [
"50"
],
"hot_plate_temp_initial_layer": [
"50"
],
"textured_plate_temp_initial_layer": [
"50"
],
"filament_flow_ratio": [
"0.85"
],
"filament_cost": [
"0"
],
"filament_density": [
"1.23"
],
"filament_max_volumetric_speed": [
"16"
],
"slow_down_min_speed": [
"5"
],
"slow_down_layer_time": [
"10"
],
"temperature_vitrification": [
"55"
],
"additional_cooling_fan_speed": [
"0"
]
}

View File

@@ -33,10 +33,10 @@
"disable_m73": "0",
"gcode_flavor": "klipper",
"printable_area": [
"3x0",
"423x0",
"423x420",
"3x420"
"0x0",
"426x0",
"426x420",
"0x420"
],
"printable_height": "501",
"thumbnails": "230x110/PNG",
@@ -48,7 +48,24 @@
"bbl_use_printhost": "0",
"bed_custom_model": "",
"bed_custom_texture": "",
"bed_exclude_area": [],
"bed_exclude_area": [
"0x0",
"3x0",
"3x420",
"0x420",
"0x0",
"423x0",
"423x0",
"423x0",
"423x0",
"426x0",
"426x420",
"423x420",
"423x0",
"0x0",
"0x0",
"0x0"
],
"bed_mesh_max": "0,0",
"bed_mesh_min": "0,0",
"bed_mesh_probe_distance": "0,0",

View File

@@ -23,10 +23,10 @@
"disable_m73": "0",
"gcode_flavor": "klipper",
"printable_area": [
"3x0",
"423x0",
"423x420",
"3x420"
"0x0",
"426x0",
"426x420",
"0x420"
],
"printable_height": "501",
"thumbnails": "230x110/PNG",
@@ -38,7 +38,24 @@
"bbl_use_printhost": "0",
"bed_custom_model": "",
"bed_custom_texture": "",
"bed_exclude_area": [],
"bed_exclude_area": [
"0x0",
"3x0",
"3x420",
"0x420",
"0x0",
"423x0",
"423x0",
"423x0",
"423x0",
"426x0",
"426x420",
"423x420",
"423x0",
"0x0",
"0x0",
"0x0"
],
"bed_mesh_max": "0,0",
"bed_mesh_min": "0,0",
"bed_mesh_probe_distance": "0,0",

View File

@@ -23,10 +23,10 @@
"disable_m73": "0",
"gcode_flavor": "klipper",
"printable_area": [
"3x0",
"423x0",
"423x420",
"3x420"
"0x0",
"426x0",
"426x420",
"0x420"
],
"printable_height": "501",
"thumbnails": "230x110/PNG",
@@ -38,7 +38,24 @@
"bbl_use_printhost": "0",
"bed_custom_model": "",
"bed_custom_texture": "",
"bed_exclude_area": [],
"bed_exclude_area": [
"0x0",
"3x0",
"3x420",
"0x420",
"0x0",
"423x0",
"423x0",
"423x0",
"423x0",
"426x0",
"426x420",
"423x420",
"423x0",
"0x0",
"0x0",
"0x0"
],
"bed_mesh_max": "0,0",
"bed_mesh_min": "0,0",
"bed_mesh_probe_distance": "0,0",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "10000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "450",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -201,10 +201,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "150",
@@ -271,7 +271,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "grid",
"sparse_infill_speed": "100",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.22",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "150",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "4",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "150",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "30%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "10000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "430",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "150",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "80",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "3dhoneycomb",
"sparse_infill_speed": "180",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -201,10 +201,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "150",
@@ -271,7 +271,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "grid",
"sparse_infill_speed": "180",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "200",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "300",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "30%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "300",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "150",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -201,10 +201,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "300",
@@ -271,7 +271,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "150",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "grid",
"sparse_infill_speed": "350",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "80",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.62",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "100",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "200",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "200",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "150",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "200",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "200",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "30%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "300",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -201,10 +201,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "300",
@@ -271,7 +271,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.82",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "100",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "150",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "270",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "outer wall/inner wall",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "grid",
"sparse_infill_speed": "300",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "30%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "200",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -201,10 +201,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "150",
@@ -271,7 +271,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "80",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.62",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "100",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.82",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "100",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "grid",
"sparse_infill_speed": "230",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "120",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "120",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -201,10 +201,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "150",
@@ -271,7 +271,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "120",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "grid",
"sparse_infill_speed": "200",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "30%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "200",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "10000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.62",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "100",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "80",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.62",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "100",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.82",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "100",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "80",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.62",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "100",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.82",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "100",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.82",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "100",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "80",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.62",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "100",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.82",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "100",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -190,10 +190,10 @@
],
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "grid",
"sparse_infill_speed": "450",
@@ -251,7 +251,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",
"wall_transition_angle": "10",

View File

@@ -195,10 +195,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.22",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "100",
@@ -265,7 +265,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_loops": "4",
"wall_sequence": "inner wall/outer wall",

View File

@@ -186,10 +186,10 @@
],
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "gyroid",
"spiral_mode": "0",
@@ -250,7 +250,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "arachne",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -188,10 +188,10 @@
],
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "grid",
"spiral_mode": "0",
@@ -250,7 +250,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "arachne",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -187,10 +187,10 @@
],
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "gyroid",
"spiral_mode": "0",
@@ -250,7 +250,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "arachne",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -188,10 +188,10 @@
],
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "grid",
"spiral_mode": "0",
@@ -250,7 +250,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "arachne",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -191,10 +191,10 @@
],
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "grid",
"spiral_mode": "0",
@@ -250,7 +250,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "arachne",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -192,10 +192,10 @@
],
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "grid",
"sparse_infill_speed": "270",
@@ -251,7 +251,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",
"wall_transition_angle": "10",

View File

@@ -181,10 +181,10 @@
],
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "150",
@@ -250,7 +250,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "arachne",
"wall_loops": "3",
"wall_sequence": "inner wall/outer wall",

View File

@@ -181,10 +181,10 @@
],
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "150",
@@ -250,7 +250,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "arachne",
"wall_loops": "3",
"wall_sequence": "outer wall/inner wall",

View File

@@ -181,10 +181,10 @@
],
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "200",
@@ -250,7 +250,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "arachne",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -181,10 +181,10 @@
],
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "200",
@@ -250,7 +250,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "arachne",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -189,10 +189,10 @@
],
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "25%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "grid",
"sparse_infill_speed": "270",
@@ -251,7 +251,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "arachne",
"wall_sequence": "inner wall/outer wall",
"wall_transition_angle": "10",

View File

@@ -186,10 +186,10 @@
],
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "grid",
"sparse_infill_speed": "230",
@@ -250,7 +250,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "arachne",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -195,10 +195,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.62",
"sparse_infill_pattern": "grid",
"sparse_infill_speed": "100",
@@ -265,7 +265,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -195,10 +195,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.82",
"sparse_infill_pattern": "grid",
"sparse_infill_speed": "100",
@@ -265,7 +265,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "arachne",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -188,10 +188,10 @@
],
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_filament": "1",
"solid_infill_filament": "0",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "1",
"sparse_infill_filament": "0",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "grid",
"sparse_infill_speed": "200",
@@ -250,7 +250,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "1",
"wall_filament": "0",
"wall_generator": "arachne",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
{
"type": "filament",
"name": "Fiberon PA12-CF10 @BBL X1",
"inherits": "Fiberon PA12-CF10 @base",
"from": "system",
"setting_id": "GFSL56_00",
"instantiation": "true",
"compatible_printers": [
"Bambu Lab X1 0.4 nozzle",
"Bambu Lab X1 Carbon 0.4 nozzle",
"Bambu Lab P1S 0.4 nozzle",
"Bambu Lab X1E 0.4 nozzle"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
]
}

View File

@@ -0,0 +1,77 @@
{
"type": "filament",
"name": "Fiberon PA12-CF10 @base",
"inherits": "fdm_filament_pa",
"from": "system",
"filament_id": "GFL56",
"instantiation": "false",
"bed_type": [
"Cool Plate"
],
"eng_plate_temp": [
"40"
],
"eng_plate_temp_initial_layer": [
"40"
],
"fan_cooling_layer_time": [
"15"
],
"fan_max_speed": [
"100"
],
"filament_cost": [
"99.99"
],
"filament_density": [
"1.06"
],
"filament_flow_ratio": [
"0.95"
],
"filament_max_volumetric_speed": [
"14"
],
"filament_type": [
"PA-CF"
],
"filament_vendor": [
"Polymaker"
],
"full_fan_speed_layer": [
"2"
],
"hot_plate_temp": [
"40"
],
"hot_plate_temp_initial_layer": [
"40"
],
"nozzle_temperature": [
"300"
],
"nozzle_temperature_initial_layer": [
"300"
],
"nozzle_temperature_range_low": [
"280"
],
"overhang_fan_speed": [
"100"
],
"reduce_fan_stop_start_freq": [
"1"
],
"slow_down_min_speed": [
"10"
],
"temperature_vitrification": [
"55"
],
"textured_plate_temp": [
"40"
],
"textured_plate_temp_initial_layer": [
"40"
]
}

View File

@@ -0,0 +1,18 @@
{
"type": "filament",
"name": "Fiberon PA6-CF20 @BBL X1",
"inherits": "Fiberon PA6-CF20 @base",
"from": "system",
"setting_id": "GFSL57_00",
"instantiation": "true",
"compatible_printers": [
"Bambu Lab X1 0.4 nozzle",
"Bambu Lab X1 Carbon 0.4 nozzle",
"Bambu Lab P1S 0.4 nozzle",
"Bambu Lab X1E 0.4 nozzle"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
]
}

View File

@@ -0,0 +1,74 @@
{
"type": "filament",
"name": "Fiberon PA6-CF20 @base",
"inherits": "fdm_filament_pa",
"from": "system",
"filament_id": "GFL57",
"instantiation": "false",
"bed_type": [
"Cool Plate"
],
"eng_plate_temp": [
"40"
],
"eng_plate_temp_initial_layer": [
"40"
],
"fan_cooling_layer_time": [
"15"
],
"fan_max_speed": [
"100"
],
"filament_cost": [
"83.99"
],
"filament_density": [
"1.17"
],
"filament_flow_ratio": [
"0.95"
],
"filament_max_volumetric_speed": [
"14"
],
"filament_type": [
"PA6-CF"
],
"filament_vendor": [
"Polymaker"
],
"hot_plate_temp": [
"40"
],
"hot_plate_temp_initial_layer": [
"40"
],
"nozzle_temperature": [
"300"
],
"nozzle_temperature_initial_layer": [
"300"
],
"nozzle_temperature_range_low": [
"280"
],
"overhang_fan_speed": [
"100"
],
"reduce_fan_stop_start_freq": [
"1"
],
"slow_down_min_speed": [
"10"
],
"temperature_vitrification": [
"74.2"
],
"textured_plate_temp": [
"40"
],
"textured_plate_temp_initial_layer": [
"40"
]
}

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