Compare commits

..
Author SHA1 Message Date
peachismomo 5af7140cb0 Merge remote-tracking branch 'origin/main' into feat/printer-agent-impl 2026-10-01 00:51:05 +08:00
a5413efc37 refactor: printer agent infrastructure changes to work with other printer agents aside from bambu (#15710)
* Add developer flag for printer agents

* Parse user print info on the UI thread to prevent heap corruption (#119)

get_user_print_info()'s HTTP fetch can run on a worker thread (e.g. BindJob),
but parse_user_print_info() mutates userMachineList (insert/erase/delete
MachineObject). on_machine_alive (SSDP) mutates the same maps on the UI thread
without locking, so parsing off-thread races the map and frees MachineObjects
out from under it -> heap corruption.

Keep all device-list mutation on the UI thread: parse inline when already on
the main thread, otherwise marshal via CallAfter so it stays serialized with
on_machine_alive.

* Prevent loss of user access code on LAN reselect

Keep user access code intact to maintain access rights even if
device slot is unpopulated, ensuring continuous connection
and status message reception.

* Harden send flow and separate upload failure recovery (#111)

* fix(send): harden FT send path + IP pre-flight UX

* Remove early returns

* Working Moonraker and Qidi printer agent transport (#104)

Folds the Qidi AMS box-mapping print
overrides (apply_box_mapping +
start_* wrappers) that the transport
fix builds on.

* Add support for runtime error status in plugins

Distinguish a loaded plugin whose
capability errored (RuntimeError,
warn-styled, stays checked) from a
load-time Error. Status now derives
via resolve_plugin_status(); enum
ordinal keeps dialog sort priority.
Unloading clears stale errors.

* Resolve duplicate agent ID conflicts

Reject a printer-agent capability
whose agent ID is already owned by
another capability or built-in:
flag the plugin error, disable the
capability, and warn the user
instead of silently ignoring it.

* fix: checkbox should depend on plugin is_loaded status

* Replace fake-enum printer agent dropdown (#121)

A dedicated PrinterAgentChoice field
reads rows straight from the live
agent registry and stores the agent
id string, replacing the fake-coEnum
index mapping. The field moves to
TabPrinter and registers with the
searcher so UnsavedChanges renders
it; the PhysicalPrinterDialog copy
and its update hook are removed
(#125). switch_printer_agent now
resolves ids via
resolve_printer_agent_id.

* Reset device selection on agent swap or unload (#124)

set_live_printer_agent centralizes
the swap: deselect the machine,
clear stale sidebar state and the
previous agent's Other Devices, then
install the new agent (or null when
its provider vanished). Plugin
load/unload callbacks refresh the
dropdown and re-run agent selection.
load_last_machine no longer falls
back to the first available machine.

* Gate agent mode behind use_printer_agents toggle

Replace per-printer auto-activation
(is_current_printer_agent_plugin)
with a global experimental AppConfig
toggle, default off: legacy
print-host behavior is unchanged
until the user opts in. The toggle
drives device-tab routing, print
button defaults, connect-button
visibility and sidebar layout, and
dedups machine-select dialog opens.

* Track BBLPrinterAgentPlugin.py

* Add printer-agent and plugin status tests

Ports the agent lifecycle, duplicate
agent-id, built-in-id clash and
status-resolution tests. The loader
runs on a detached worker thread, so
the lifecycle tests live in their own
executable. Tests install the
production unload-side registry
wiring themselves (no GUI in the
test binary) and register agents
manually so concurrent loads stay
deterministic.

* fix: pin HTTP to prevent connection refusal

Set `use_ssl` to false to ensure Moonraker
connectivity, as the service uses HTTP rather
than HTTPS, preventing connection issues. Initialize
device info early for reliable name resolution.

* Bring Moonraker device panel to feature parity

The monitor panel showed wrong or missing
data for Moonraker printers, and its
controls did nothing.

Push payload now carries layer number and
total layers. Remaining time replaces the
wrong total_duration - print_duration
formula. The chamber light toggle maps to
Klipper SET_PIN / SET_LED, and pause,
resume and stop post to
/printer/print/{action}. Task thumbnails
resolve via /server/files/thumbnails onto
a new MachineObject thumbnail url.

Filament sync switches to pull mode so the
agent is queried on demand.

Not compiled or run.

* Stop blocking print on unreported nozzle data

* fix: make Klipper macro lamp control reliable

* Surface Moonraker webcams and gate unrunnable controls

* Keep Bambu AMS dialect out of the agent waist

M620 is Bambu firmware dialect, not a
neutral command. Composing it in
MachineObject let non-Bambu agents
(Moonraker/Klipper) forward it and
report success on firmware that
cannot run it.

Agents now own the dialect: the
default refusal on IPrinterAgent
returns not-supported so the UI
can say so; BBLPrinterAgent keeps
the byte-identical composition.

* Fix multi-color filament logic

Reuse color decoding across functions to improve
code readability and maintain consistency in
multi-color filament handling.

* Move Moonraker commands off the UI thread

Pause/resume/stop, g-code sends, temps, and
light ran synchronous HTTP on the UI thread,
freezing the app up to 10s per click on slow
or unreachable printers.

Run them on a single agent-owned FIFO worker
so g-code ordering is preserved, while command
translation stays synchronous so unsupported-
command dialogs still work.

Add a pending-disabled state to the pause,
resume, and abort buttons for Moonraker-family
printers: the icon only flips once the
WebSocket reports the real state, which also
rules out double-click races.

* Show Snapmaker U1 camera in Device tab

The U1 exposes no /server/webcams/list entry;
its camera only captures after an explicit
camera.start_monitor RPC, which the Moonraker
websocket executes unauthenticated but only
answers over MQTT - so the call is fire and
forget.

Start the camera when the camera view is
shown and renew every 300 s: the printer
retires the capture task at ~362 s and
stop_monitor is accepted but ineffective,
so teardown is simply to stop renewing.

Frames land in monitor.jpg as still JPEGs
(~2 fps at interval 0), so the webview loads
a local HTML wrapper that repolls with a
cache buster.

* fix: start stream when camera URL changes

* fix: stop Qidi slot parse throwing on null

* Keep printer-agent progress in sync

Keep the shared task progress aligned with agent
reports that lack Bambu cloud task identity.

Release the lazily allocated task during reset to
avoid leaks when machine objects reconnect.

* docs: document the printer-agent subsystem

* fix: merge access codes into one

* Reconcile implementation split with PR tip

* feat: abstract remaining gcode commands in devicemanager

* refactor: abstract bambu specific protocol to printer agent

* refactor: push bbl workflows to bbl printer agent

* remove unused

* fix: default impl

* fix callback error

* fix: remove redundant cache

* specify api for getting file transfer url

* revert file transfer abstraction

* fix: ams filament mapping workflow

* feat: update qidi to use subscription based filament sync mode

* fix: resolve stubgen byte header conflict

* fix: ams sync info and periodic ams sync via subscription workflow

* fix: skip filament sync dialog if filamentSyncMode is none

* feat: parse nozzle information for qidi and moonraker printer agents

* fix: extend access code requirements t 0, 8 or more characters.

* remove irrelevant docs

* fix: remove heavy includes from IPrinterAgent

* fix: defer filesystem and camera abstractions

* fix: remote do_fetch_filament_info from tests

* cleanup moonraker and snapmaker printer agents

* fix: access codes regression

* fix: tests

* fix: printer agent switching on preset change

* fix: remove unused variable

* fix: snapmaker U1 SelectMachineDialog blocking print

* fix: merge artifact

* fix: clear up some unrelated changes

* feat: connect to cloud printer and monitor

* feat: connect to cloud printer and monitor

* feat: generic camera stream support for http snapshot and rtsp

* fix: build & access code UI

* feat: generic camera stream support for http snapshot and rtsp

* fix: build & access code UI

* feat: connect to cloud printer and monitor

* feat: connect to cloud printer and monitor

* fix: build errors

* feat: camera via webrtc

* fix: build

* fix: cmake

* feat: remove frame assembler and change config to set protocol

* fix: orcaprinteragent refactor

* fix: LAN paths and camera stream

* feat: use ffmpeg to render http camera stream

* fix: make model_id/dev_type optional instead of blocking

* fix: connect via ip dialog

* feat: LAN impl for Orca Printer Agent

* fix: model_id resolution method for non bambu printers

* fix: ffmpeg http camera stream jittering due to incomplete frames

* fix: revert sdcard check

* feat: check printer storage status before sending

* fix: moonraker printer agent hang on printer power cut

* fix: shim layer for any compatibiliity changes

* fix: cloud printers were using the wrong MQTT endpoint

* feat: cloud download via HTTP

* temp: doc for intended change

* fix: warnings

* fix: warnings

* fix: camera auto-play on startup

* fix: split infra from impl

* fix: uninitialized ams state blocking print

* Fixes nullptr deref

* Log first before std::move

* fix: printer agent virutal optional functions

* fix: parameterize orcaslicer_copy_test_dlls() for printer_agent_plugin_tests

* Revert "fix: parameterize orcaslicer_copy_test_dlls() for printer_agent_plugin_tests"

This reverts commit 2f566e3779.

* Guard libdatachannel. Remove unused code

* fix: unit tests & unused variables

* fix(ci): deps build order for datachannel

* Resolve printer agent first before getting cloud printer agent

* fix(ci): set depends openssl

* fix: re-include apply header guarded by ifdef __APPLE__

* fix(ci): add libdatachannel to flatpak manifest

* fix: add internal_developer_mode chekc back to MediaPlayCtrl::load()

* fix: invoke js clearInterval on WebMediaController::stop

* fix: change rtc log level

* fix: inject provider, agent id and generation to get_user_print_info to ensure correct metadata

* fix: revert moonraker specific behavior

* fix: remove stale comment

* fix: use ORCA_CLOUD_PROVIDER instead of hardcoded string

* fix: remove hardcoded ICE servers

* feat: enable https camera stream mode

* fix: move non-mandatory printer agent function stubs to IPrinterAgent

* fix: dedupe compatible printer type check

* fix: stop the correct media controller

* fix: scope get_my_machine_list to printers listed under the current printer agent

* fix: move printer agent plugin tests into test_plugin_lifecycle.cpp

* fix: always build bundled DataChannel dep

* fix: disable unused DataChannel media support

* revert: filament sync work

* fix: wrap command_* with small wrapper

* fix: regression bug, connecting to bambu needs bblp username

* fix: default impl for vendor agnostic gcode commansd

* refactor: media controller playback routing and ownership

* fix: bump libdatachannel ver & update flatpak to use tar instead

* fix: stop flatpak DataChannel build from re-cloning over the sandboxed network

* fix: update windows ffmpeg prebuild

* fix: update printer agent plugin API

* fix: shift camera signaling channel to network agent

* fix: follow external-packages for flatpak libdatachannel deps & add flatpak path to use source_dir

* feat: add printer-agent.md doc to HLSD

* fix: guard DeviceManager command dispatch when no printer agent is bound

* fix: port BBL implementations from #15711

* refactor: connect_printer api and dialog

* fix(tests): make omitted printer agent operations answer like a missing agent

* fix: preserve printer agent defaults in PrinterAgentPluginCapabilityTrampoline

* test: cover printer agent default command dispatch

* fix: validate windows FFmpeg avformat library

* feat: extend optional printer model warnings to calibration & ams workflows

* fix: handle malformed printer progress values safely

* fix: clear webview document on stop

* chore: reduce diagnostic logging level to trace

* refactor: centralize printer compatibility checks

* tests: add device manager integration coverage

* tests: cover WebMediaController lifecycle with wxWebView stub

* fix: make integration tests headless

* refactor: collapse command_ams_refresh_rfid and command_ams_refresh_rfid2

* refactor: make printer connection SSL agent-specific

* fix: persist input printer host and port

* fix: use correct device id for Moonraker connections

* fix: make moonraker gcode commands asynchronous

* fix: preserve moonraker device names

* fix: add include for non BBL_RELEASE_TO_PUBLIC path in BBLPrinterAgent

---------

Co-authored-by: Andrew <159703254+andrewsoonqn@users.noreply.github.com>
Co-authored-by: SoftFever <softfeverever@gmail.com>
Co-authored-by: Lam Wei Lun <weilun.lam@gmail.com>
2026-10-01 00:30:44 +08:00
peachismomo 1cac04eae8 refactor: separate out networking infra 2026-10-01 00:28:57 +08:00
Rodrigo Faselli 8d69fa3e5a Add 'SECURITY' label to PR label bot (#16024) 2026-09-30 11:28:07 -03:00
Ian BassiandRodrigo Faselli 97700c5ab5 Gyroid Optimization (#16002)
Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
2026-09-30 10:21:24 -03:00
Kris Austin ff2f42016a Fix CLI crash on a 3mf without project settings (#16004)
When a 3mf is tagged as written by OrcaSlicer or BambuStudio, the CLI
treats it as a project and reads the printer settings stored in the
file. #14580 guarded four of those reads, but printable_height still
called opt_float() without a check, so a 3mf whose
project_settings.config is empty or missing crashed with a null
dereference. The handy models OrcaBadge.3mf and OrcaSliced.3mf are both
like this.

Skip the read when the option is absent, like the reads around it. The
bed-size logic further down already treats an old printable height of 0
as unknown and uses the current printer's.
2026-09-30 10:04:14 -03:00
Ian Chua b935a652b9 Merge branch 'feat/printer-agent-infra' into feat/printer-agent-impl 2026-09-30 19:31:28 +08:00
Ian Chua a1fd66362b Merge branch 'main' into feat/printer-agent-infra 2026-09-30 19:29:07 +08:00
peachismomo a6cad8a6e5 fix: printer agent and camera shutdown races 2026-09-30 19:27:50 +08:00
HanifKoh 94266c2819 Fill Settings Missing From a CLI Project From Its System Presets (#15953)
* Fill Settings Missing From a CLI Project From Its System Presets

A project saved before a printer or process option existed has no value
for it. The GUI takes such keys from the project's system preset; the
CLI left them at the option default, so e.g. extruder_clearance_dist_to_rod
sliced as 40 instead of the P1S's 33.

The CLI now resolves the project's system printer and process presets by
name and copies the keys the project lacks, skipping preset bookkeeping,
print-host keys, the extruder variant layout and keys the legacy handler
drops. PresetBundle::resolve_system_preset finds the vendor through its
manifest or preset cache, so it also works in release builds, which ship
vendors as caches only.

* Load a CLI Project's Printer and Process Settings as the GUI Does

The CLI filled only the keys a project lacked from its system preset.
The GUI builds a project preset differently: the project's values go
over the default preset, without the print-host keys, and every key
the project does not list in different_settings_to_system is refreshed
to its base system preset's current value. After a profile update the
two sliced the same project differently.

That step now lives in Preset::load_external_config, which takes plain
configs and gets the base preset from a callback, so the collection
lookup stays in PresetCollection. PresetBundle::project_different_keys
builds the kept-key set from a project's escaped entry, adding the
preset bookkeeping keys, and is used by both the GUI and the CLI.
PresetCollection::load_external_preset calls the shared step with no
change in behaviour.

The CLI now builds the project's printer and process configs with the
same step, passing the system preset from resolve_system_preset. That
drops the hand-kept skip list for print-host and variant-layout keys
and the legacy-key check: the shared step already excludes print-host
keys and maps per-variant values onto the base preset's variant layout.
The --uptodate path and a printer or process given on the command line
are left as they were.

Because the GUI's kept-key set always holds the bookkeeping keys, the
refresh runs for every project that names a system preset, so the CLI
now loads that preset's vendor on every such run.
2026-09-30 18:14:28 +08:00
Ian Chua dc0e269186 test: bumping OFL version to test OTA workflow [To be reverted before 2.5.0 alpha] (#16017)
Merged by /bot merge on behalf of @peachismomo (id 52488812).
Grants: resources/profiles/OrcaFilamentLibrary/filament/Elegoo, resources/profiles/OrcaFilamentLibrary.json, resources/profiles/Elegoo, resources/profiles/Elegoo.json
Head: ff6a2f2056
2026-09-30 08:25:43 +00:00
Error404JoyNotFound da0611a301 Fix : Add curr_bed_type to built-in placeholders in G-code editor (#15987) 2026-09-30 14:59:46 +08:00
Hanif Koh 561737f407 Fix the CLI 3MF Export Crash After Rendering a Plate Thumbnail
Since the CLI can open an OpenGL context (#15745) it renders plate
thumbnails on export, and the viewport restore at the end of
render_thumbnail_internal (#15674) then reads the plater through the wx
application. The CLI has neither, so every --export-3mf on a machine
with a display died with a segmentation fault after the first
thumbnail. Skip the restore when there is no application or no plater;
the GUI path is unchanged.
2026-09-30 14:46:44 +08:00
Ian Chua 7d42ad17a4 fix: malformed jq filter in OFL publisher barrier (#16012) 2026-09-30 14:19:06 +08:00
789f848694 Write the estimated printing time comment after the config block, not before (#15897)
Co-authored-by: Fernando Marino <f.marino@rheagroup.com>
Co-authored-by: yw4z <ywsyildiz@gmail.com>
2026-09-29 19:18:15 -03:00
Rodrigo FaselliandIan Bassi 3a0694dce6 Remember last print action (#15774)
Co-authored-by: Ian Bassi <ian.bassi@outlook.com>
2026-09-29 19:17:45 -03:00
Kris AustinandRodrigo Faselli f5679ad343 perf: load presets in parallel, cutting preset load time by over 60% (#15943)
Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
2026-09-29 18:13:44 -03:00
HanifKoh 2769b12ce7 Give OBJ Quad and Flipped Faces the Texture Coordinates of Their Own Corners (#15977)
load_obj emits the second triangle of a quad from corners 0, 2 and 3,
but read its texture coordinates from corners 0, 1 and 2, so half of
every textured quad sampled the wrong part of the texture. The corner
indices are now passed down to where the coordinates are read.

A mesh with inward-facing triangles is flipped after loading, which
swaps corners 1 and 2 of every face. The texture coordinates were left
as they were. They are now swapped along with the corners.
2026-09-30 03:13:29 +08:00
HanifKoh 203bc63f35 Escape Project Metadata in the Project Page and Restrict Accessory Opening (#15956)
* Escape Project Metadata in the Project Page and Restrict Accessory Opening

The Project page rendered the model and profile name, author, description
and accessory file names from the 3MF as live HTML. Names, authors and file
names are now set as text, and the file list is built from DOM nodes with
bound click handlers instead of concatenated markup. Descriptions can
legitimately carry rich-text HTML, so they are rebuilt from an inert
DOMParser document, keeping only plain formatting tags, http(s) links and
http(s) images, with every other attribute dropped.

Opening an accessory from the page now only launches regular files that
lie inside the project's extracted auxiliary directory. The containment
check is a new libslic3r helper, is_absolute_path_within_root, built on
is_path_within_root so symlinks leading out of the root are rejected too.

* Tighten Project Page Description Rendering and Keep More Formatting

Link and image URLs in descriptions must now start with an http or https
scheme as written and parse as such with the URL parser. Preview images are
built as DOM nodes like the file list, and accessory names show their full
text as a tooltip.

Descriptions keep more plain formatting: del, ins, figure, figcaption, dl,
dt, dd, caption, q, abbr, kbd and wbr, plus alt, title, width and height on
images, colspan and rowspan on table cells and start on ordered lists.
Numeric attributes must be plain integers. Embedded YouTube players become
a link to the video.

* Confirm Before Opening Program Attachments and Load Only HTTPS Images

Opening a project attachment whose type runs as a program or script
(executables, installers, shortcuts, shell and PowerShell scripts, macOS
command files and apps, Linux desktop entries) now asks for confirmation
first. The check lives in libslic3r as is_executable_file_name and ignores
the trailing dots and spaces Windows strips from file names.

Images in project descriptions are kept only when they load over https,
so opening the Project tab no longer issues plain-http requests.

* Open Project Attachments Through One Guarded Helper

The Edit Project Info view launched attachments directly, without the
checks the project page has. Both now call
desktop_open_project_attachment, which checks that the file is inside
the auxiliary directory, asks for confirmation where needed and then
opens it.

The auxiliary root was built through encode_path, which returns code
page bytes on Windows, while boost::filesystem reads a narrow string as
UTF-8. With a non-ASCII temporary directory the root never matched and
no attachment opened. It is now built from the UTF-8 path directly.

The list of program extensions could not be kept complete and let
unknown types open without a prompt. It is replaced by
is_safe_to_open_file_name, a list of plain document, image, model and
video types that open directly. Everything else asks first.
2026-09-30 00:39:30 +08:00
HanifKoh e40030cf81 Stop Malformed Network Responses from Crashing the App (#15947)
* Stop Malformed Network Responses from Crashing the App

Duet, MKS and UltiMaker parsed print host replies with boost read_json
inside the HTTP completion callback with no try, so an HTML or truncated
reply threw out of the Physical Printer Test button and terminated the app,
or killed the upload queue thread. The five identical copies of the parser
(ESP3D's and Flashforge's were unused) are replaced by one shared
PrintHost::get_err_code_from_body that reports a non-JSON reply as an error.
The upload queue now catches a failing job per job, so one bad upload no
longer leaves later jobs queued forever.

Flashforge read material station slots with nlohmann value(), which throws
on off-type fields or non-object entries. The parsing moves into
Flashforge::parse_material_slots, which reads fields leniently with the
existing try_parse_json_int and skips bad entries.

UserManager::parse_json parsed the payload before its try block; the parse
now happens inside it.

* Keep UploadFinished Paired with UploadStarted When an Upload Throws

The exception from a throwing upload was caught around perform_job, so
the UploadFinished lifecycle event was skipped and plugins saw an
upload start that never finished.

The catch now sits around the upload call. The error is reported
through the job's error callback and UploadFinished is fired with an
error code, as for any other failed upload. The worker keeps running
for the next job. The started, upload and finished sequence moved to
PrintHostJobQueue::upload_job so it can be tested without the dialog.
2026-09-30 00:39:01 +08:00
HanifKoh ba468c842d Confine Updater and Plugin Archive Extraction to the Target Directory (#15957)
* Confine Updater Archive Extraction to the Target Directory

The preset updater extracted downloaded archives by appending each entry
name to the cache directory, and the network plugin installer did the same
for the plugin folder, without checking that the result stays inside it.

Move the updater's extraction into libslic3r as extract_archive_confined,
which validates every entry with is_path_within_root before writing
anything and fails the whole archive if one entry resolves outside the
target. The plugin installer now rejects such an entry the same way. Well
formed archives extract exactly as before.

* Harden Archive Extraction Against Symlinks

The plugin installer now creates a symlink entry only when its target is
relative and, joined to the link's own directory, passes
is_path_within_root, via the new is_symlink_target_within_root helper.
Before writing any entry it checks the destination with symlink_status, so
an existing symlink, dangling or not, is replaced rather than followed, and
it creates parent directories inside the existing error handling.
extract_archive_confined replaces a symlink at a destination file the same
way.

is_path_within_root now ignores a trailing separator on the root, which
previously made every path fail the check.

* Validate Plugin Symlink Targets Before Replacing Existing Files

A symlink entry's target is now read and checked before anything already
at its destination is removed or renamed aside, so an archive rejected
for its link target leaves the installed plugin files in place.

* Reject Paths with an Embedded NUL When Confining Extraction

is_path_within_root compared each component with "..", so a name such
as "..\0" passed the check. The filesystem calls stop at the NUL and
act on a shorter path than the one that was checked: a symlink target
read from a plugin archive as raw bytes was created as "..", pointing
out of the plugin directory.

A path containing a NUL is now rejected before anything touches the
filesystem, which covers every caller, including entry names taken from
the Unicode Path extra field.
2026-09-29 23:40:12 +08:00
SoftFever dd9b5dc0d4 Tune pressure advance separately for each extruder variant (#15986)
* Tune pressure advance separately for each extruder variant

Pressure advance, adaptive pressure advance and its model can now take a
different value for each extruder variant of a filament, such as Standard and
High Flow nozzles, like the other per-variant filament settings. Projects
saved with one value per filament apply it to every variant of that filament,
and the addnorth BBL filaments in the Orca Filament Library are updated to the
per-variant layout.

* Move Nozzle type to each extruder's settings page

Editing other settings on an Extruder page of a single-extruder
multi-material printer no longer triggers the nozzle diameter prompt.

* Fix command-line slicing when a filament leaves out a per-variant setting
2026-09-29 23:23:08 +08:00
HanifKoh e68694dbaf Percent-Encode Local File URLs for Embedded Web Pages (#15961)
* Percent-Encode Local File URLs for Embedded Web Pages

The Home tab, setup wizard, Project tab and other embedded pages were
loaded from file:// URLs built by pasting the resources path into a
string. A '#', '%' or '?' in the install path was then read as a URL
fragment, escape or query, so the pages failed to load, for example a
portable install under D:\#OneDrive showed a directory listing instead
of the setup wizard.

Add file_url_from_path(), built on wxFileSystem::FileNameToURL, and use
it wherever a local page or image URL is built from a path. Queries such
as ?lang= are appended after the path is encoded. The wizard's printer
cover images are passed to the page as file URLs too.

* Encode the Login Error Page URL and Cover More Windows Path Forms

The login dialog's error page was still loaded from a raw resources path;
it now uses file_url_from_path like the other local pages.

The Windows file URL tests now also cover a resources path joined with a
forward-slash relative path, as the callers build them, and a UNC path.

* Build the Flush Dialog Page URLs with the Shared Helper

WipingDialog and NozzleListTable still called
wxFileSystem::FileNameToURL directly. They now go through
file_url_from_path like every other local page, so the URLs are built
in one place.

Adds a test for a resources directory with a '#' in its name, which the
plugin page check did not recognise before.
2026-09-29 22:30:56 +08:00
SoftFever 1504bd7153 Fix command-line slicing when a filament leaves out a per-variant setting 2026-09-29 21:09:20 +08:00
SoftFever d87fe1b290 Merge branch 'main' into feature/pa_per_extruder_variant 2026-09-29 19:15:17 +08:00
Kris Austin 72cfe71b81 fix: link webkit2gtk and X11 on every Linux build, not only Flatpak (#15972)
libslic3r_gui calls webkit_* directly, and OrcaSlicer.cpp and libspnav
call Xlib, but both libraries were only linked when FLATPAK was set.
The default build links because the bundled static wxWidgets lists
them in wx-config. A shared wxWidgets does not, so any build against
one, like the Flatpak build or a distro package, fails with undefined
webkit_* and X* symbols.
2026-09-29 08:04:48 -03:00
SoftFever 50eea48408 Move Nozzle type to each extruder's settings page
Editing other settings on an Extruder page of a single-extruder
multi-material printer no longer triggers the nozzle diameter prompt.
2026-09-29 17:52:19 +08:00
SoftFever ef0c656932 Tune pressure advance separately for each extruder variant
Pressure advance, adaptive pressure advance and its model can now take a
different value for each extruder variant of a filament, such as Standard and
High Flow nozzles, like the other per-variant filament settings. Projects
saved with one value per filament apply it to every variant of that filament,
and the addnorth BBL filaments in the Orca Filament Library are updated to the
per-variant layout.
2026-09-29 17:52:13 +08:00
Ian Chua 9bac75dda5 Merge branch 'feat/printer-agent-infra' into feat/printer-agent-impl 2026-09-28 21:15:32 +08:00
Ian Chua 50c5ec2862 fix: remove redundant earlier expires_never call 2026-09-28 21:14:22 +08:00
Ian Chua b36a72e16a fix: orca mqtt session timeout after connack 2026-09-28 21:14:21 +08:00
Ian Chua 64371c191e Merge branch 'main' into feat/printer-agent-infra 2026-09-28 20:50:03 +08:00
Ian Chua 94702a5f8d Merge branch 'feat/printer-agent-infra' of https://github.com/OrcaSlicer/OrcaSlicer into feat/printer-agent-infra 2026-09-28 20:48:51 +08:00
Ian Chua 68cdd31852 Merge branch 'main' into feat/printer-agent-infra 2026-09-28 20:48:37 +08:00
Ian Chua 87eb8de001 fix: serialize MQTT ws I/O and dewcouple keepalive 2026-09-24 20:28:13 +08:00
Ian Chua 220508534a fix: cancel inflight cloud signaling requests 2026-09-24 19:03:58 +08:00
Ian Chua 5a0810365b fix: clarify OrcaCloud signaling endpoint contract 2026-09-24 18:18:48 +08:00
Ian Chua fa98ec5afe feat: add ssl and ca_cert for OrcaPrinterAgent 2026-09-24 17:53:46 +08:00
Ian Chua 3c1d8ac8b4 fix: allow input ip address dialog to validate if is valid endpoint 2026-09-24 17:53:12 +08:00
Ian Chua 936b0bbc17 feat: add https compatibility for moonraker based printer agents 2026-09-24 14:53:32 +08:00
Ian Chua 5222a37eff fix: command_ams_refresh_rfid function signature in test_printer_agent.cpp 2026-09-24 12:35:03 +08:00
peachismomo 6da938b660 fix: compilation after merge 2026-09-24 01:15:55 +08:00
peachismomo 2dcdf49185 Merge branch 'feat/printer-agent-infra' into feat/printer-agent-impl 2026-09-24 01:02:59 +08:00
peachismomo 52e845a821 fix: add include for non BBL_RELEASE_TO_PUBLIC path in BBLPrinterAgent 2026-09-24 00:23:47 +08:00
Ian Chua ffb4813510 fix: preserve moonraker device names 2026-09-23 20:17:06 +08:00
Ian Chua 7436f68e09 fix: make moonraker gcode commands asynchronous 2026-09-23 20:15:52 +08:00
Ian Chua ad956591ce fix: use correct device id for Moonraker connections 2026-09-23 20:14:21 +08:00
Ian Chua 8da8e9bee1 fix: persist input printer host and port 2026-09-23 20:12:18 +08:00
Ian Chua 0acc8608b1 refactor: make printer connection SSL agent-specific 2026-09-23 20:11:47 +08:00
Ian Chua a2a9b2ce94 refactor: collapse command_ams_refresh_rfid and command_ams_refresh_rfid2 2026-09-23 18:30:05 +08:00
Ian Chua 07bee6c7a4 Merge branch 'feat/printer-agent-infra' into feat/printer-agent-impl 2026-09-23 15:35:22 +08:00
Ian Chua 1615390182 Merge branch 'main' into feat/printer-agent-infra 2026-09-23 14:54:38 +08:00
Ian Chua cf20994b58 refactor: connect_printer api and dialog (#15827)
# Description

<!--
> Please provide a summary of the changes made in this PR. Include
details such as:
  > * What issue does this PR address or fix?
  > * What new features or enhancements does this PR introduce?
> * Are there any breaking changes or dependencies that need to be
considered?
-->

Changing the IP address input dialog and `connect_printer` API in
preparation for configurable Moonraker/Orca printer agent connections.

# Screenshots/Recordings/Graphs

<!--
> Please attach relevant screenshots to showcase the UI changes.
> Please attach images that can help explain the changes.
-->
<img width="460" height="699" alt="image"
src="https://github.com/user-attachments/assets/35e3ce17-7594-4401-b140-100dea082221"
/>


## Tests

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

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

[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
2026-09-23 14:54:26 +08:00
Ian Chua 309bb3c6e2 Merge branch 'feat/printer-agent-infra' into feat/printer-agent-impl 2026-09-23 14:53:49 +08:00
Ian Chua 79f20c3996 Merge branch 'feat/printer-agent-infra' into refactor/connect-printer-api 2026-09-23 14:48:33 +08:00
Ian Chua 33179c1902 fix: make integration tests headless 2026-09-23 14:29:57 +08:00
peachismomo ccaafb3003 tests: cover WebMediaController lifecycle with wxWebView stub 2026-09-23 03:50:35 +08:00
peachismomo 7d4d8276a2 tests: add device manager integration coverage 2026-09-23 03:25:08 +08:00
peachismomo c492436228 refactor: centralize printer compatibility checks 2026-09-23 03:06:15 +08:00
peachismomo 99a1cd1b74 chore: reduce diagnostic logging level to trace 2026-09-23 02:58:30 +08:00
peachismomo 4b41bf7f4f fix: clear webview document on stop 2026-09-23 02:57:56 +08:00
peachismomo d5962052ca fix: handle malformed printer progress values safely 2026-09-23 02:53:10 +08:00
peachismomo f1410c8cac feat: extend optional printer model warnings to calibration & ams workflows 2026-09-23 02:49:27 +08:00
peachismomo 7283a05040 fix: validate windows FFmpeg avformat library 2026-09-23 02:33:47 +08:00
peachismomo c32c87863b test: cover printer agent default command dispatch 2026-09-23 01:54:23 +08:00
peachismomo 06f7016d6a fix: preserve printer agent defaults in PrinterAgentPluginCapabilityTrampoline 2026-09-23 01:53:56 +08:00
peachismomo be9ae6ffbf fix(tests): make omitted printer agent operations answer like a missing agent 2026-09-23 00:35:33 +08:00
Ian Chua cda25c3715 refactor: connect_printer api and dialog 2026-09-22 21:54:10 +08:00
Ian Chua 2dcdfe2d32 fix: refresh token before configuring printer MQTT 2026-09-22 21:46:09 +08:00
Ian Chua 9e34ca536b fix: port BBL implementations from #15711 2026-09-22 21:11:03 +08:00
Ian Chua 201617a6c0 fix: guard DeviceManager command dispatch when no printer agent is bound 2026-09-22 21:09:58 +08:00
Ian Chua 2dcbee3d76 Merge branch 'main' into feat/printer-agent-infra 2026-09-22 20:55:29 +08:00
Ian Chua 6c353d9596 feat: add ssl compatibility with moonrakerprinteragent 2026-09-22 18:30:30 +08:00
Ian Chua ce0c759ded fix: snapmakerprinteragent use moonraker's lifecycle-owned command worker 2026-09-22 16:06:18 +08:00
Ian Chua 9a7e925790 fix: re-implement missing commands on BBLPrinterAgent 2026-09-22 15:49:22 +08:00
Ian Chua f27b0a1f9d Merge branch main into feat/printer-agent-infra 2026-09-22 12:11:40 +08:00
Ian Chua cf1c164546 feat: add printer-agent.md doc to HLSD 2026-09-21 18:40:24 +08:00
Ian Chua d9f1f8b207 fix: camera signaling API 2026-09-21 16:19:31 +08:00
Ian Chua a096e1ab8c Merge branch 'feat/printer-agent-infra' into feat/printer-agent-impl 2026-09-21 16:02:11 +08:00
Ian Chua bca8dd00dd Merge branch 'feat/printer-agent-infra' of https://github.com/OrcaSlicer/OrcaSlicer into feat/printer-agent-infra 2026-09-21 15:45:49 +08:00
Ian Chua 8b7f8cac82 fix: follow external-packages for flatpak libdatachannel deps & add flatpak path to use source_dir 2026-09-21 15:45:39 +08:00
Ian Chua aae03257ed Merge branch 'main' into feat/printer-agent-infra 2026-09-21 11:54:23 +08:00
Ian Chua 3fba237c9a fix: shift camera signaling channel to network agent 2026-09-21 11:48:16 +08:00
Ian Chua cfb6349695 fix: update printer agent plugin API 2026-09-21 11:46:36 +08:00
Ian Chua f53ad6993a fix: update windows ffmpeg prebuild 2026-09-21 11:02:20 +08:00
peachismomo 91c6d1d3d5 Merge branch 'feat/printer-agent-impl' of https://github.com/OrcaSlicer/OrcaSlicer into feat/printer-agent-impl 2026-09-18 22:31:37 +08:00
peachismomo b91f48141d fix: load windows roots for cloud MQTT and camera signaling 2026-09-18 22:31:25 +08:00
Ian Chua e168d900f2 fix: stop flatpak DataChannel build from re-cloning over the sandboxed network 2026-09-18 22:10:30 +08:00
Ian Chua 467e249b3d Merge branch 'feat/printer-agent-infra' into feat/printer-agent-impl 2026-09-18 20:46:18 +08:00
Ian Chua bc03e3be2f fix: bump libdatachannel ver & update flatpak to use tar instead 2026-09-18 20:45:29 +08:00
Ian Chua 5287fd0160 fix: swtich start print command from http to mqtt command 2026-09-18 15:59:57 +08:00
Ian Chua c441fd2fa4 Merge branch feat/printer-agent-infra into feat/printer-agent-impl 2026-09-18 15:59:25 +08:00
Ian Chua f224b8e4a6 refactor: media controller playback routing and ownership 2026-09-18 13:36:46 +08:00
Ian Chua 8b479c9af3 fix: default impl for vendor agnostic gcode commansd 2026-09-17 18:46:56 +08:00
Ian Chua d8299c388e fix: regression bug, connecting to bambu needs bblp username 2026-09-17 18:44:54 +08:00
Ian Chua 771d455d12 Merge branch 'feat/printer-agent-infra' into feat/printer-agent-impl 2026-09-17 16:33:38 +08:00
Ian Chua a7ab01b815 fix: remove unimplemented pure virtual functions 2026-09-17 16:33:11 +08:00
Ian Chua 4d85b6627e fix: wrap command_* with small wrapper 2026-09-17 12:51:17 +08:00
Ian Chua 38e35dbff4 Merge branch 'feat/printer-agent-infra' into feat/printer-agent-impl 2026-09-16 22:59:22 +08:00
Ian Chua dcddb9283c fix: updated stale docs 2026-09-16 22:54:54 +08:00
Ian Chua ba9cf6e897 Revert "fix: latent ams in use bug"
This reverts commit 910dbcd2e6.
2026-09-16 22:54:54 +08:00
Ian Chua d1f2ecb903 feat: restore printer agent implementations 2026-09-16 22:54:46 +08:00
Ian Chua ee2b4ccf70 Merge branch 'main' into feat/printer-agent-infra 2026-09-16 22:32:32 +08:00
Ian Chua 753c793ea0 revert: filament sync work 2026-09-16 22:20:35 +08:00
Ian Chua 9c5494b19f fix: disable unused DataChannel media support 2026-09-16 22:08:53 +08:00
Ian Chua 93a119427a Merge branch main into feat/printer-agent-infra 2026-09-16 22:06:09 +08:00
Ian Chua d264fcaef4 fix: always build bundled DataChannel dep 2026-09-16 22:04:27 +08:00
Ian Chua b4ab6dedfd fix: move printer agent plugin tests into test_plugin_lifecycle.cpp 2026-09-16 21:33:04 +08:00
Ian Chua ed59f93456 fix: scope get_my_machine_list to printers listed under the current printer agent 2026-09-16 19:48:59 +08:00
Ian Chua cf52c81dcd fix: stop the correct media controller 2026-09-16 18:44:06 +08:00
Ian Chua 803a2a3239 fix: dedupe compatible printer type check 2026-09-16 17:18:59 +08:00
Ian Chua 985092bb46 fix: move non-mandatory printer agent function stubs to IPrinterAgent 2026-09-16 16:56:43 +08:00
Ian Chua cb16a2e547 feat: enable https camera stream mode 2026-09-16 16:24:29 +08:00
Ian Chua 2c2e91b629 fix: remove hardcoded ICE servers 2026-09-16 14:36:49 +08:00
Ian Chua c3faf903ca fix: use ORCA_CLOUD_PROVIDER instead of hardcoded string 2026-09-16 14:24:40 +08:00
Ian Chua 5f3c957597 fix: remove stale comment 2026-09-16 14:23:00 +08:00
Ian Chua 8557285389 fix: revert moonraker specific behavior 2026-09-16 14:21:09 +08:00
Ian Chua a9edab78a8 fix: inject provider, agent id and generation to get_user_print_info to ensure correct metadata 2026-09-16 14:15:24 +08:00
Ian Chua 79dede7d59 fix: change rtc log level 2026-09-16 14:14:21 +08:00
Ian Chua 2ac45b2f78 fix: invoke js clearInterval on WebMediaController::stop 2026-09-16 14:13:37 +08:00
Ian Chua b4f33e71e9 fix: add internal_developer_mode chekc back to MediaPlayCtrl::load() 2026-09-16 14:12:48 +08:00
Ian Chua 8c91cab971 fix(ci): add libdatachannel to flatpak manifest 2026-09-15 20:01:48 +08:00
Ian Chua 79077518e9 fix: re-include apply header guarded by ifdef __APPLE__ 2026-09-15 19:45:14 +08:00
Ian Chua 56aa348c89 fix(ci): set depends openssl 2026-09-15 18:16:50 +08:00
Lam Wei Lun ae1752fbf2 Resolve printer agent first before getting cloud printer agent 2026-09-15 18:02:02 +08:00
Ian Chua cf3f36a1c6 Merge branch 'feat/printer-agent-infra' of https://github.com/OrcaSlicer/OrcaSlicer into feat/printer-agent-infra 2026-09-15 17:37:57 +08:00
Ian Chua cca7d8adb1 fix(ci): deps build order for datachannel 2026-09-15 17:35:25 +08:00
Ian Chua 396d9ff51a Merge branch 'main' into feat/printer-agent-infra 2026-09-15 16:47:15 +08:00
Ian Chua 81540c81e0 Merge branch 'feat/printer-agent-infra' of https://github.com/OrcaSlicer/OrcaSlicer into feat/printer-agent-infra 2026-09-15 16:46:38 +08:00
Ian Chua e7ace6cc99 fix: unit tests & unused variables 2026-09-15 16:46:28 +08:00
Lam Wei Lun 79c20a1fad Guard libdatachannel. Remove unused code 2026-09-15 16:33:12 +08:00
Ian Chua e48f9f47d2 Revert "fix: parameterize orcaslicer_copy_test_dlls() for printer_agent_plugin_tests"
This reverts commit 2f566e3779.
2026-09-15 16:12:33 +08:00
Ian Chua 2f566e3779 fix: parameterize orcaslicer_copy_test_dlls() for printer_agent_plugin_tests 2026-09-15 16:08:10 +08:00
Ian Chua 37d1edf69a fix: printer agent virutal optional functions 2026-09-15 15:54:55 +08:00
Lam Wei Lun f1cf9c69b1 Log first before std::move 2026-09-15 15:49:15 +08:00
Lam Wei Lun 1df362888e Fixes nullptr deref 2026-09-15 15:47:48 +08:00
Ian Chua 4b2466b179 fix: uninitialized ams state blocking print 2026-09-15 14:40:04 +08:00
Ian Chua 3adcb3e953 fix: split infra from impl 2026-09-15 13:27:17 +08:00
Ian Chua 104dbb2140 fix: camera auto-play on startup 2026-09-14 16:17:17 +08:00
Ian Chua e482cbbdd7 fix: warnings 2026-09-14 14:56:34 +08:00
Ian Chua 2a5ddd9edb fix: warnings 2026-09-14 14:16:02 +08:00
Ian Chua c10f83cf61 Merge branch 'main' into feat/printer-agent-impl 2026-09-14 13:01:03 +08:00
Ian Chua 8eeec6935e Merge branch 'main' of https://github.com/OrcaSlicer/OrcaSlicer_priv into feat/printer-agent-impl 2026-09-14 12:53:04 +08:00
Ian Chua 131a2abf99 Merge pull request #138 from OrcaSlicer/feat/orca-printer-agent
feat: orca printer agent
2026-09-14 12:14:53 +08:00
peachismomo 9daecc59b4 temp: doc for intended change 2026-09-12 02:32:01 +08:00
peachismomo 58e000d842 feat: cloud download via HTTP 2026-09-12 02:31:27 +08:00
Ian Chua af6be5858e fix: cloud printers were using the wrong MQTT endpoint 2026-09-11 17:38:56 +08:00
Ian Chua 3fc7fd99d6 fix: shim layer for any compatibiliity changes 2026-09-10 21:55:31 +08:00
Ian Chua 3fe043651b fix: moonraker printer agent hang on printer power cut 2026-09-09 18:40:01 +08:00
Ian Chua b0b78c296c feat: check printer storage status before sending 2026-09-09 18:35:38 +08:00
Ian Chua 62791fabfa fix: revert sdcard check 2026-09-09 14:10:12 +08:00
Ian Chua b0ada2dee5 fix: ffmpeg http camera stream jittering due to incomplete frames 2026-09-09 13:40:51 +08:00
Ian Chua 8f4df1aa8c fix: model_id resolution method for non bambu printers 2026-09-09 13:40:24 +08:00
Ian Chua 5fec9b8d19 Merge pull request #139 from OrcaSlicer/feat/webrtc-impl
feat: webrtc implementation
2026-09-08 19:37:31 +08:00
Ian Chua 75ed5afbac Merge branch 'feat/orca-printer-agent' into feat/webrtc-impl 2026-09-08 19:31:14 +08:00
Ian Chua 01c553bad9 feat: LAN impl for Orca Printer Agent 2026-09-08 19:13:48 +08:00
Ian Chua a4376c77c3 Merge branch 'feat/orca-printer-agent' of https://github.com/OrcaSlicer/OrcaSlicer_priv into feat/orca-printer-agent 2026-09-08 12:06:25 +08:00
peachismomo c459994290 fix: connect via ip dialog 2026-09-08 04:40:12 +08:00
Ian Chua 3a0fda7d18 fix: make model_id/dev_type optional instead of blocking 2026-09-07 17:28:40 +08:00
Ian Chua eb8733f0f4 Merge branch 'feat/orca-printer-agent' into feat/webrtc-impl 2026-09-04 18:28:48 +08:00
Ian Chua 55acb940a8 Merge branch 'feat/printer-agent-impl' into feat/orca-printer-agent 2026-09-04 18:28:25 +08:00
Ian Chua 0d691403de Merge branch 'main' into feat/printer-agent-impl 2026-09-04 18:26:58 +08:00
Ian Chua 9b2b75a51d Merge branch 'feat/orca-printer-agent' into feat/webrtc-impl 2026-09-04 18:23:26 +08:00
Ian Chua 25da273ea0 Merge pull request #142 from OrcaSlicer/fix/orca-printer-agent-refactor
fix: orcaprinteragent refactor
2026-09-04 18:05:27 +08:00
Ian Chua dbcb82075f feat: use ffmpeg to render http camera stream 2026-09-04 16:42:36 +08:00
Ian Chua 2f82cfe40f fix: LAN paths and camera stream 2026-09-04 16:18:43 +08:00
Ian Chua 972031cf06 fix: orcaprinteragent refactor 2026-09-03 19:42:25 +08:00
Ian Chua 2a4792e762 feat: remove frame assembler and change config to set protocol 2026-09-02 15:59:13 +08:00
Ian Chua dd15ac6146 fix: cmake 2026-09-02 11:51:40 +08:00
Ian Chua b0469254bc Merge branch 'feat/orca-printer-agent' into feat/webrtc-impl 2026-09-01 19:24:04 +08:00
Ian Chua 2228589e16 Merge branch 'feat/printer-agent-impl' into feat/orca-printer-agent 2026-09-01 19:23:47 +08:00
Ian Chua 4d8480e1a1 fix: build 2026-09-01 19:21:23 +08:00
Ian Chua 4320cc78d9 feat: camera via webrtc 2026-09-01 18:59:20 +08:00
Ian Chua d9d9678a7e Merge branch 'feat/orca-printer-agent' into feat/webrtc-impl 2026-09-01 18:38:01 +08:00
Ian Chua d55a0bfec6 fix: build errors 2026-09-01 18:37:04 +08:00
Ian Chua 3b1017df51 Merge branch 'feat/orca-printer-agent' of https://github.com/OrcaSlicer/OrcaSlicer_priv into feat/orca-printer-agent 2026-09-01 18:17:23 +08:00
Ian Chua f5b81d2ffc Merge branch 'feat/webrtc-impl' of https://github.com/OrcaSlicer/OrcaSlicer_priv into feat/webrtc-impl 2026-09-01 18:16:20 +08:00
Ian Chua eb9cfe0ecb feat: connect to cloud printer and monitor 2026-09-01 18:15:53 +08:00
Ian Chua 1e8805d43d feat: connect to cloud printer and monitor 2026-09-01 18:15:53 +08:00
Ian Chua e8f089dfa4 fix: build & access code UI 2026-09-01 18:13:11 +08:00
Ian Chua 84e929ea24 feat: generic camera stream support for http snapshot and rtsp 2026-09-01 18:13:11 +08:00
Ian Chua abec603e73 fix: build & access code UI 2026-09-01 18:04:37 +08:00
Ian Chua 0ed19d4826 feat: generic camera stream support for http snapshot and rtsp 2026-09-01 18:02:53 +08:00
Ian Chua b747c13ef4 Merge branch 'feat/orca-printer-agent' of https://github.com/OrcaSlicer/OrcaSlicer_priv into feat/orca-printer-agent 2026-09-01 17:40:07 +08:00
Ian Chua 6a12aca495 feat: connect to cloud printer and monitor 2026-09-01 17:38:52 +08:00
Ian Chua d486db6459 Merge branch 'main' into feat/printer-agent-impl 2026-09-01 16:51:34 +08:00
Ian Chua fe777b8801 feat: printer agent impl 2026-09-01 16:49:04 +08:00
Ian Chua 1b3e206f34 feat: connect to cloud printer and monitor 2026-08-28 18:10:31 +08:00
Ian Chua 4df1607ec4 fix: clear up some unrelated changes 2026-08-28 16:45:01 +08:00
Ian Chua a0ec8bec8a fix: merge artifact 2026-08-27 15:08:06 +08:00
Ian Chua 36c362cbd2 Merge branch 'main' into feat/printer-agent-impl 2026-08-27 14:54:22 +08:00
Ian Chua 84fde32616 Merge branch 'main' into refactor/printer-agent-interface 2026-08-27 14:52:50 +08:00
Ian Chua 34e349e323 fix: snapmaker U1 SelectMachineDialog blocking print 2026-08-27 14:47:09 +08:00
Ian Chua 44941e571f fix: remove unused variable 2026-08-26 18:40:48 +08:00
Ian Chua 876d6e2499 fix: printer agent switching on preset change 2026-08-26 18:40:47 +08:00
Ian Chua 858b3024ff fix: tests 2026-08-26 12:04:02 +08:00
Ian Chua 6595557c34 fix: access codes regression 2026-08-25 18:32:02 +08:00
Ian Chua c729849843 cleanup moonraker and snapmaker printer agents 2026-08-25 16:39:08 +08:00
Ian Chua c0563be36e Merge branch 'feat/printer-agent-impl' of https://github.com/OrcaSlicer/OrcaSlicer into feat/printer-agent-impl 2026-08-25 14:47:12 +08:00
Ian Chua 462f8dce30 fix: remote do_fetch_filament_info from tests 2026-08-25 14:46:11 +08:00
Ian Chua b2a0485139 fix: defer filesystem and camera abstractions 2026-08-25 14:34:11 +08:00
Ian Chua f16071f083 fix: remove heavy includes from IPrinterAgent 2026-08-25 13:54:40 +08:00
Ian Chua a1505e9bed Merge branch 'main' into refactor/printer-agent-interface 2026-08-25 13:50:25 +08:00
Ian Chua 8e48312b1f Merge branch 'main' into feat/printer-agent-impl 2026-08-25 13:47:11 +08:00
Ian Chua ba7fdb8ceb Merge branch 'main' into feat/printer-agent-impl 2026-08-21 14:17:56 +08:00
Ian Chua 8be9800567 remove irrelevant docs 2026-08-21 14:16:49 +08:00
Ian Chua c4eabc3c52 fix: extend access code requirements t 0, 8 or more characters. 2026-08-21 14:14:23 +08:00
Ian Chua 0d0d281d0c feat: parse nozzle information for qidi and moonraker printer agents 2026-08-20 20:11:31 +08:00
Ian Chua c4bbcd322b fix: skip filament sync dialog if filamentSyncMode is none 2026-08-20 19:28:02 +08:00
Ian Chua 7aea1b1235 Merge branch 'main' into feat/printer-agent-impl 2026-08-20 16:39:54 +08:00
Ian Chua 41c16436bf Merge branch 'main' into refactor/printer-agent-interface 2026-08-20 16:09:10 +08:00
Ian Chua 2bbb229c91 Merge branch 'main' into refactor/printer-agent-interface 2026-08-19 23:30:22 +08:00
Ian Chua c2a43157d2 Merge branch 'main' into feat/printer-agent-impl 2026-08-19 18:29:36 +08:00
Ian Chua 738b30a743 fix: ams sync info and periodic ams sync via subscription workflow 2026-08-19 18:29:19 +08:00
Ian Chua 6e6ffe13f8 Merge branch 'refactor/printer-agent-interface' of https://github.com/OrcaSlicer/OrcaSlicer into refactor/printer-agent-interface 2026-08-19 17:11:14 +08:00
Ian Chua eb96d127b0 fix: resolve stubgen byte header conflict 2026-08-19 17:11:10 +08:00
SoftFever 60421c33f4 Merge branch 'main' into refactor/printer-agent-interface 2026-08-19 14:32:42 +08:00
SoftFever 300c4b8afb Merge branch 'main' into refactor/printer-agent-interface 2026-08-18 20:44:56 +08:00
Ian Chua 96092ef2d3 feat: update qidi to use subscription based filament sync mode 2026-08-18 19:05:17 +08:00
Ian Chua d9002ad87d fix: ams filament mapping workflow 2026-08-18 18:07:22 +08:00
Ian Chua 4c1ea0a602 Merge branch 'main' into 'feat/printer-agent-impl' 2026-08-17 14:51:04 +08:00
Ian Chua 9415812d85 Merge branch 'main' into refactor/printer-agent-interface 2026-08-17 14:24:23 +08:00
Ian Chua d3c728557e Merge 'main' into 'refactor/printer-agent-interface' 2026-08-17 14:24:05 +08:00
Ian Chua 6558c52849 revert file transfer abstraction 2026-08-17 14:19:41 +08:00
Ian Chua a34d056de2 specify api for getting file transfer url 2026-08-14 15:31:42 +08:00
Ian Chua 7ba5718be6 fix: remove redundant cache 2026-08-12 18:41:51 +08:00
Ian Chua c1163ce7e5 Merge branch 'refactor/printer-agent-interface' of https://github.com/OrcaSlicer/OrcaSlicer into refactor/printer-agent-interface 2026-08-12 15:29:56 +08:00
Ian Chua da187eaaf9 fix callback error 2026-08-12 15:29:50 +08:00
Ian Chua de0268ce86 Merge branch 'main' into refactor/printer-agent-interface 2026-08-12 15:28:53 +08:00
Ian Chua 108923bdaa fix: default impl 2026-08-12 14:02:04 +08:00
Ian Chua 44793f7a21 remove unused 2026-08-12 13:51:54 +08:00
Ian Chua 7cb1805272 refactor: push bbl workflows to bbl printer agent 2026-08-12 13:50:47 +08:00
Ian Chua d5c528b7c7 Merge branch 'refactor/printer-agent-interface' of https://github.com/OrcaSlicer/OrcaSlicer into refactor/printer-agent-interface 2026-08-11 15:00:42 +08:00
Ian Chua 742cb712d8 refactor: abstract bambu specific protocol to printer agent 2026-08-11 15:00:34 +08:00
Ian Chua fd1c5d826c Merge branch 'main' into refactor/printer-agent-interface 2026-08-07 18:37:56 +08:00
Ian Chua 58be7f4861 feat: abstract remaining gcode commands in devicemanager 2026-08-07 18:36:38 +08:00
Ian Chua 4031b00915 Merge branch 'main' into refactor/printer-agent-interface 2026-08-06 16:27:57 +08:00
Ian Chua 07c62112b8 Reconcile implementation split with PR tip 2026-08-04 18:12:51 +08:00
Ian Chua f23e4963bf fix: merge access codes into one 2026-08-04 18:12:43 +08:00
Andrew 95279f7084 docs: document the printer-agent subsystem 2026-08-04 18:12:43 +08:00
Andrew 1014558c91 Keep printer-agent progress in sync
Keep the shared task progress aligned with agent
reports that lack Bambu cloud task identity.

Release the lazily allocated task during reset to
avoid leaks when machine objects reconnect.
2026-08-04 18:12:42 +08:00
Andrew 6515062a3a fix: stop Qidi slot parse throwing on null 2026-08-04 18:12:42 +08:00
Andrew 8153e26b6d fix: start stream when camera URL changes 2026-08-04 18:12:42 +08:00
Andrew 79fd33d49a Show Snapmaker U1 camera in Device tab
The U1 exposes no /server/webcams/list entry;
its camera only captures after an explicit
camera.start_monitor RPC, which the Moonraker
websocket executes unauthenticated but only
answers over MQTT - so the call is fire and
forget.

Start the camera when the camera view is
shown and renew every 300 s: the printer
retires the capture task at ~362 s and
stop_monitor is accepted but ineffective,
so teardown is simply to stop renewing.

Frames land in monitor.jpg as still JPEGs
(~2 fps at interval 0), so the webview loads
a local HTML wrapper that repolls with a
cache buster.
2026-08-04 18:12:41 +08:00
Andrew cdb662d4b2 Move Moonraker commands off the UI thread
Pause/resume/stop, g-code sends, temps, and
light ran synchronous HTTP on the UI thread,
freezing the app up to 10s per click on slow
or unreachable printers.

Run them on a single agent-owned FIFO worker
so g-code ordering is preserved, while command
translation stays synchronous so unsupported-
command dialogs still work.

Add a pending-disabled state to the pause,
resume, and abort buttons for Moonraker-family
printers: the icon only flips once the
WebSocket reports the real state, which also
rules out double-click races.
2026-08-04 18:12:41 +08:00
Andrew a27fa7b5df Fix multi-color filament logic
Reuse color decoding across functions to improve
code readability and maintain consistency in
multi-color filament handling.
2026-08-04 18:12:40 +08:00
Andrew cdeed107c5 Keep Bambu AMS dialect out of the agent waist
M620 is Bambu firmware dialect, not a
neutral command. Composing it in
MachineObject let non-Bambu agents
(Moonraker/Klipper) forward it and
report success on firmware that
cannot run it.

Agents now own the dialect: the
default refusal on IPrinterAgent
returns not-supported so the UI
can say so; BBLPrinterAgent keeps
the byte-identical composition.
2026-08-04 18:12:40 +08:00
Andrew be92f7e78c Surface Moonraker webcams and gate unrunnable controls 2026-08-04 18:12:39 +08:00
Andrew 76f9de4cfb fix: make Klipper macro lamp control reliable 2026-08-04 18:12:39 +08:00
Andrew cd6d2cbf5e Stop blocking print on unreported nozzle data 2026-08-04 18:12:39 +08:00
Andrew deaca0189f Bring Moonraker device panel to feature parity
The monitor panel showed wrong or missing
data for Moonraker printers, and its
controls did nothing.

Push payload now carries layer number and
total layers. Remaining time replaces the
wrong total_duration - print_duration
formula. The chamber light toggle maps to
Klipper SET_PIN / SET_LED, and pause,
resume and stop post to
/printer/print/{action}. Task thumbnails
resolve via /server/files/thumbnails onto
a new MachineObject thumbnail url.

Filament sync switches to pull mode so the
agent is queried on demand.

Not compiled or run.
2026-08-04 18:12:38 +08:00
Andrew 7d54ee286d fix: pin HTTP to prevent connection refusal
Set `use_ssl` to false to ensure Moonraker
connectivity, as the service uses HTTP rather
than HTTPS, preventing connection issues. Initialize
device info early for reliable name resolution.
2026-08-04 18:12:38 +08:00
Andrew dddfc7a8ad Add printer-agent and plugin status tests
Ports the agent lifecycle, duplicate
agent-id, built-in-id clash and
status-resolution tests. The loader
runs on a detached worker thread, so
the lifecycle tests live in their own
executable. Tests install the
production unload-side registry
wiring themselves (no GUI in the
test binary) and register agents
manually so concurrent loads stay
deterministic.
2026-08-04 18:12:37 +08:00
Andrew 673bfb0dee Track BBLPrinterAgentPlugin.py 2026-08-04 18:12:37 +08:00
Andrew b9e8c5a38c Gate agent mode behind use_printer_agents toggle
Replace per-printer auto-activation
(is_current_printer_agent_plugin)
with a global experimental AppConfig
toggle, default off: legacy
print-host behavior is unchanged
until the user opts in. The toggle
drives device-tab routing, print
button defaults, connect-button
visibility and sidebar layout, and
dedups machine-select dialog opens.
2026-08-04 18:12:36 +08:00
Andrew a2a4ca20e4 Reset device selection on agent swap or unload (#124)
set_live_printer_agent centralizes
the swap: deselect the machine,
clear stale sidebar state and the
previous agent's Other Devices, then
install the new agent (or null when
its provider vanished). Plugin
load/unload callbacks refresh the
dropdown and re-run agent selection.
load_last_machine no longer falls
back to the first available machine.
2026-08-04 18:12:36 +08:00
Andrew 12075459d2 Replace fake-enum printer agent dropdown (#121)
A dedicated PrinterAgentChoice field
reads rows straight from the live
agent registry and stores the agent
id string, replacing the fake-coEnum
index mapping. The field moves to
TabPrinter and registers with the
searcher so UnsavedChanges renders
it; the PhysicalPrinterDialog copy
and its update hook are removed
(#125). switch_printer_agent now
resolves ids via
resolve_printer_agent_id.
2026-08-04 18:12:36 +08:00
Andrew f3770f3106 fix: checkbox should depend on plugin is_loaded status 2026-08-04 18:12:35 +08:00
Andrew 90fbf1770f Resolve duplicate agent ID conflicts
Reject a printer-agent capability
whose agent ID is already owned by
another capability or built-in:
flag the plugin error, disable the
capability, and warn the user
instead of silently ignoring it.
2026-08-04 18:12:35 +08:00
Andrew bde94ab37f Add support for runtime error status in plugins
Distinguish a loaded plugin whose
capability errored (RuntimeError,
warn-styled, stays checked) from a
load-time Error. Status now derives
via resolve_plugin_status(); enum
ordinal keeps dialog sort priority.
Unloading clears stale errors.
2026-08-04 18:12:34 +08:00
Ian Chua 6d25e1777e Working Moonraker and Qidi printer agent transport (#104)
Folds the Qidi AMS box-mapping print
overrides (apply_box_mapping +
start_* wrappers) that the transport
fix builds on.
2026-08-04 18:12:34 +08:00
Andrew 7ce26ca8e5 Harden send flow and separate upload failure recovery (#111)
* fix(send): harden FT send path + IP pre-flight UX

* Remove early returns
2026-08-04 18:12:33 +08:00
Andrew 367fcce634 Prevent loss of user access code on LAN reselect
Keep user access code intact to maintain access rights even if
device slot is unpopulated, ensuring continuous connection
and status message reception.
2026-08-04 18:12:33 +08:00
Andrew 9b3a44b339 Parse user print info on the UI thread to prevent heap corruption (#119)
get_user_print_info()'s HTTP fetch can run on a worker thread (e.g. BindJob),
but parse_user_print_info() mutates userMachineList (insert/erase/delete
MachineObject). on_machine_alive (SSDP) mutates the same maps on the UI thread
without locking, so parsing off-thread races the map and frees MachineObjects
out from under it -> heap corruption.

Keep all device-list mutation on the UI thread: parse inline when already on
the main thread, otherwise marshal via CallAfter so it stays serialized with
on_machine_alive.
2026-08-04 18:12:33 +08:00
Ian Chua 6384191102 Add developer flag for printer agents 2026-08-04 18:12:32 +08:00
217 changed files with 15208 additions and 2977 deletions
@@ -189,8 +189,7 @@ the presets to a base is in [shared-bases.md](shared-bases.md#variant-arrays-on-
2. Every key in `filament_options_with_variant` ([the full list](#the-four-key-sets)) that reaches the 2. Every key in `filament_options_with_variant` ([the full list](#the-four-key-sets)) that reaches the
preset, whether written, included or inherited, is resized to its variant count: write it at exactly preset, whether written, included or inherited, is resized to its variant count: write it at exactly
that width, or leave it out. Keys outside the set that width, or leave it out. Keys outside the set
(`filament_type`, plate temperatures, `fan_max_speed`, `slow_down_min_speed`, `pressure_advance`, (`filament_type`, plate temperatures, `fan_max_speed`, `slow_down_min_speed`, …) are never addressed by variant index; the preset contributes their first value however wide a
…) are never addressed by variant index; the preset contributes their first value however wide a
file writes them. file writes them.
3. Cover every variant the material is meant to print on across its `compatible_printers`. Leave out 3. Cover every variant the material is meant to print on across its `compatible_printers`. Leave out
a variant deliberately when the material should not be tuned for it (e.g. a TPU High Flow variant a variant deliberately when the material should not be tuned for it (e.g. a TPU High Flow variant
@@ -455,7 +454,7 @@ no set: the variant-length resize leaves it alone, and only the pair rebuild of
last for contrast; it is not a variant set. last for contrast; it is not a variant set.
`check` and `fix-variant` read the four sets from `PrintConfig.cpp` on every run, so they follow the `check` and `fix-variant` read the four sets from `PrintConfig.cpp` on every run, so they follow the
engine. The lists below are from the 2026-09-25 checkout; regenerate them from the repository root engine. The lists below are from the 2026-09-29 checkout; regenerate them from the repository root
before relying on them (the recipe strips comments, since an initializer can carry a commented-out entry): before relying on them (the recipe strips comments, since an initializer can carry a commented-out entry):
```bash ```bash
@@ -478,7 +477,7 @@ EOF
**`print_options_with_variant`**, process, stride 1 (45): `bridge_acceleration`, `bridge_speed`, `default_acceleration`, `default_jerk`, `default_junction_deviation`, `enable_overhang_speed`, `gap_infill_speed`, `infill_jerk`, `initial_layer_acceleration`, `initial_layer_infill_speed`, `initial_layer_jerk`, `initial_layer_speed`, `initial_layer_travel_acceleration`, `initial_layer_travel_jerk`, `initial_layer_travel_speed`, `inner_wall_acceleration`, `inner_wall_jerk`, `inner_wall_speed`, `internal_bridge_speed`, `internal_solid_infill_acceleration`, `internal_solid_infill_speed`, `outer_wall_acceleration`, `outer_wall_jerk`, `outer_wall_speed`, `overhang_1_4_speed`, `overhang_2_4_speed`, `overhang_3_4_speed`, `overhang_4_4_speed`, `print_extruder_id`, `print_extruder_variant`, `slowdown_for_curled_perimeters`, `small_perimeter_speed`, `small_perimeter_threshold`, `sparse_infill_acceleration`, `sparse_infill_speed`, `support_interface_speed`, `support_speed`, `top_solid_infill_flow_ratio`, `top_surface_acceleration`, `top_surface_jerk`, `top_surface_speed`, `travel_acceleration`, `travel_jerk`, `travel_speed`, `travel_speed_z` **`print_options_with_variant`**, process, stride 1 (45): `bridge_acceleration`, `bridge_speed`, `default_acceleration`, `default_jerk`, `default_junction_deviation`, `enable_overhang_speed`, `gap_infill_speed`, `infill_jerk`, `initial_layer_acceleration`, `initial_layer_infill_speed`, `initial_layer_jerk`, `initial_layer_speed`, `initial_layer_travel_acceleration`, `initial_layer_travel_jerk`, `initial_layer_travel_speed`, `inner_wall_acceleration`, `inner_wall_jerk`, `inner_wall_speed`, `internal_bridge_speed`, `internal_solid_infill_acceleration`, `internal_solid_infill_speed`, `outer_wall_acceleration`, `outer_wall_jerk`, `outer_wall_speed`, `overhang_1_4_speed`, `overhang_2_4_speed`, `overhang_3_4_speed`, `overhang_4_4_speed`, `print_extruder_id`, `print_extruder_variant`, `slowdown_for_curled_perimeters`, `small_perimeter_speed`, `small_perimeter_threshold`, `sparse_infill_acceleration`, `sparse_infill_speed`, `support_interface_speed`, `support_speed`, `top_solid_infill_flow_ratio`, `top_surface_acceleration`, `top_surface_jerk`, `top_surface_speed`, `travel_acceleration`, `travel_jerk`, `travel_speed`, `travel_speed_z`
**`filament_options_with_variant`**, filament, stride 1 (48): `activate_air_filtration`, `activate_air_filtration_during_print`, `activate_air_filtration_on_completion`, `complete_print_exhaust_fan_speed`, `during_print_exhaust_fan_speed`, `filament_adaptive_volumetric_speed`, `filament_cooling_before_tower`, `filament_deretraction_speed`, `filament_extruder_variant`, `filament_flow_ratio`, `filament_flush_temp`, `filament_flush_volumetric_speed`, `filament_ironing_flow`, `filament_ironing_inset`, `filament_ironing_spacing`, `filament_ironing_speed`, `filament_long_retractions_when_cut`, `filament_max_volumetric_speed`, `filament_pre_cooling_temperature`, `filament_pre_cooling_temperature_nc`, `filament_preheat_temperature_delta`, `filament_ramming_travel_time`, `filament_ramming_travel_time_nc`, `filament_ramming_volumetric_speed`, `filament_ramming_volumetric_speed_nc`, `filament_retract_after_wipe`, `filament_retract_before_wipe`, `filament_retract_length_nc`, `filament_retract_length_toolchange`, `filament_retract_lift_above`, `filament_retract_lift_below`, `filament_retract_lift_enforce`, `filament_retract_restart_extra`, `filament_retract_restart_extra_toolchange`, `filament_retract_when_changing_layer`, `filament_retraction_distances_when_cut`, `filament_retraction_length`, `filament_retraction_minimum_travel`, `filament_retraction_speed`, `filament_wipe`, `filament_wipe_distance`, `filament_z_hop`, `filament_z_hop_types`, `long_retractions_when_ec`, `nozzle_temperature`, `nozzle_temperature_initial_layer`, `retraction_distances_when_ec`, `volumetric_speed_coefficients` **`filament_options_with_variant`**, filament, stride 1 (54): `activate_air_filtration`, `activate_air_filtration_during_print`, `activate_air_filtration_on_completion`, `adaptive_pressure_advance`, `adaptive_pressure_advance_bridges`, `adaptive_pressure_advance_model`, `adaptive_pressure_advance_overhangs`, `complete_print_exhaust_fan_speed`, `during_print_exhaust_fan_speed`, `enable_pressure_advance`, `filament_adaptive_volumetric_speed`, `filament_cooling_before_tower`, `filament_deretraction_speed`, `filament_extruder_variant`, `filament_flow_ratio`, `filament_flush_temp`, `filament_flush_volumetric_speed`, `filament_ironing_flow`, `filament_ironing_inset`, `filament_ironing_spacing`, `filament_ironing_speed`, `filament_long_retractions_when_cut`, `filament_max_volumetric_speed`, `filament_pre_cooling_temperature`, `filament_pre_cooling_temperature_nc`, `filament_preheat_temperature_delta`, `filament_ramming_travel_time`, `filament_ramming_travel_time_nc`, `filament_ramming_volumetric_speed`, `filament_ramming_volumetric_speed_nc`, `filament_retract_after_wipe`, `filament_retract_before_wipe`, `filament_retract_length_nc`, `filament_retract_length_toolchange`, `filament_retract_lift_above`, `filament_retract_lift_below`, `filament_retract_lift_enforce`, `filament_retract_restart_extra`, `filament_retract_restart_extra_toolchange`, `filament_retract_when_changing_layer`, `filament_retraction_distances_when_cut`, `filament_retraction_length`, `filament_retraction_minimum_travel`, `filament_retraction_speed`, `filament_wipe`, `filament_wipe_distance`, `filament_z_hop`, `filament_z_hop_types`, `long_retractions_when_ec`, `nozzle_temperature`, `nozzle_temperature_initial_layer`, `pressure_advance`, `retraction_distances_when_ec`, `volumetric_speed_coefficients`
**`printer_extruder_options`**, machine, one value per extruder, not a variant set (8): **`printer_extruder_options`**, machine, one value per extruder, not a variant set (8):
`default_nozzle_volume_type`, `extruder_max_nozzle_count`, `extruder_printable_area`, `default_nozzle_volume_type`, `extruder_max_nozzle_count`, `extruder_printable_area`,
@@ -244,10 +244,10 @@ preset for a 0.2 nozzle, explicitly revisit flow limits; do not infer a pressure
required direction of change, from diameter alone. required direction of change, from diameter alone.
On a printer with extruder variants, a filament tunes these per variant too: On a printer with extruder variants, a filament tunes these per variant too:
`filament_max_volumetric_speed`, `filament_flow_ratio`, `nozzle_temperature` and the retraction `filament_max_volumetric_speed`, `filament_flow_ratio`, `nozzle_temperature`, pressure advance and the
overrides carry one value per variant of `filament_extruder_variant` (Standard, High Flow, …). The retraction overrides carry one value per variant of `filament_extruder_variant` (Standard, High Flow, …). The
exact key set is [`filament_options_with_variant`](extruder-variants.md#the-four-key-sets); exact key set is [`filament_options_with_variant`](extruder-variants.md#the-four-key-sets);
`slow_down_min_speed` and `pressure_advance` are not in it. Keep every such array at exactly that width, even where the `slow_down_min_speed` and `fan_max_speed` are not in it. Keep every such array at exactly that width, even where the
setting does not differ per variant, and measure the High Flow variant rather than copying Standard setting does not differ per variant, and measure the High Flow variant rather than copying Standard
([extruder-variants.md](extruder-variants.md#filament)). ([extruder-variants.md](extruder-variants.md#filament)).
+1 -1
View File
@@ -146,7 +146,7 @@ jobs:
"repos/${{ github.repository }}/actions/workflows/post_merge_profiles.yml/runs" \ "repos/${{ github.repository }}/actions/workflows/post_merge_profiles.yml/runs" \
-f branch="$branch" -f event=workflow_dispatch -f per_page=100)" -f branch="$branch" -f event=workflow_dispatch -f per_page=100)"
run_id="$(jq -r --arg marker "[OFL cron $dispatch_id]" \ run_id="$(jq -r --arg marker "[OFL cron $dispatch_id]" \
'[.workflow_runs[] | select((.display_title // "") | contains($marker))] \ '[.workflow_runs[] | select((.display_title // "") | contains($marker))]
| sort_by(.created_at) | last | .id // empty' <<< "$runs_json")" | sort_by(.created_at) | last | .id // empty' <<< "$runs_json")"
[ -n "$run_id" ] && break [ -n "$run_id" ] && break
sleep 5 sleep 5
+2
View File
@@ -35,6 +35,7 @@ jobs:
// kind of change // kind of change
'crash', 'crash',
'bug-fix', 'bug-fix',
'SECURITY',
'enhancement', 'enhancement',
'QoL', 'QoL',
'optimization', 'optimization',
@@ -193,6 +194,7 @@ jobs:
// kind of change // kind of change
'crash', 'crash',
'bug-fix', 'bug-fix',
'SECURITY',
'enhancement', 'enhancement',
'QoL', 'QoL',
'optimization', 'optimization',
+42 -1
View File
@@ -834,6 +834,37 @@ find_package(OpenSSL REQUIRED)
find_package(CURL REQUIRED) find_package(CURL REQUIRED)
find_package(Freetype REQUIRED) find_package(Freetype REQUIRED)
if (SLIC3R_GUI)
# LibDataChannel's installed export references its bundled dependencies,
# but does not install their CMake targets. Recreate those targets from
# the same dependency prefix before loading the LibDataChannel config.
if (NOT TARGET Usrsctp::usrsctp)
find_library(_ORCA_USRSCTP_LIBRARY NAMES usrsctp
PATHS "${CMAKE_PREFIX_PATH}/lib" NO_DEFAULT_PATH)
if (_ORCA_USRSCTP_LIBRARY)
add_library(Usrsctp::usrsctp UNKNOWN IMPORTED GLOBAL)
set_target_properties(Usrsctp::usrsctp PROPERTIES
IMPORTED_LOCATION "${_ORCA_USRSCTP_LIBRARY}"
IMPORTED_LINK_INTERFACE_LANGUAGES C
INTERFACE_LINK_LIBRARIES "Threads::Threads")
endif()
endif()
if (NOT TARGET LibJuice::LibJuice)
find_library(_ORCA_LIBJUICE_LIBRARY NAMES juice
PATHS "${CMAKE_PREFIX_PATH}/lib" NO_DEFAULT_PATH)
if (_ORCA_LIBJUICE_LIBRARY)
add_library(LibJuice::LibJuice UNKNOWN IMPORTED GLOBAL)
set_target_properties(LibJuice::LibJuice PROPERTIES
IMPORTED_LOCATION "${_ORCA_LIBJUICE_LIBRARY}"
IMPORTED_LINK_INTERFACE_LANGUAGES C
INTERFACE_LINK_LIBRARIES "Threads::Threads")
endif()
endif()
find_package(LibDataChannel CONFIG REQUIRED)
endif()
add_library(libcurl INTERFACE) add_library(libcurl INTERFACE)
target_link_libraries(libcurl INTERFACE CURL::libcurl) target_link_libraries(libcurl INTERFACE CURL::libcurl)
@@ -1114,6 +1145,7 @@ function(orcaslicer_copy_dlls target config postfix output_dlls)
endif () endif ()
file(COPY ${_occt_dlls} file(COPY ${_occt_dlls}
${CMAKE_PREFIX_PATH}/bin/freetype.dll ${CMAKE_PREFIX_PATH}/bin/freetype.dll
${CMAKE_PREFIX_PATH}/bin/avformat-61.dll
${CMAKE_PREFIX_PATH}/bin/avcodec-61.dll ${CMAKE_PREFIX_PATH}/bin/avcodec-61.dll
${CMAKE_PREFIX_PATH}/bin/swresample-5.dll ${CMAKE_PREFIX_PATH}/bin/swresample-5.dll
${CMAKE_PREFIX_PATH}/bin/swscale-8.dll ${CMAKE_PREFIX_PATH}/bin/swscale-8.dll
@@ -1126,6 +1158,7 @@ function(orcaslicer_copy_dlls target config postfix output_dlls)
${_out_dir}/WebView2Loader.dll ${_out_dir}/WebView2Loader.dll
${_out_dir}/freetype.dll ${_out_dir}/freetype.dll
${_out_dir}/avformat-61.dll
${_out_dir}/avcodec-61.dll ${_out_dir}/avcodec-61.dll
${_out_dir}/swresample-5.dll ${_out_dir}/swresample-5.dll
${_out_dir}/swscale-8.dll ${_out_dir}/swscale-8.dll
@@ -1149,7 +1182,10 @@ function(orcaslicer_copy_sos target config postfix output_sos)
set(_out_dir "${CMAKE_CURRENT_BINARY_DIR}") set(_out_dir "${CMAKE_CURRENT_BINARY_DIR}")
endif () endif ()
file(COPY ${CMAKE_PREFIX_PATH}/lib/libavcodec.so file(COPY ${CMAKE_PREFIX_PATH}/lib/libavformat.so
${CMAKE_PREFIX_PATH}/lib/libavformat.so.61
${CMAKE_PREFIX_PATH}/lib/libavformat.so.61.1.100
${CMAKE_PREFIX_PATH}/lib/libavcodec.so
${CMAKE_PREFIX_PATH}/lib/libavcodec.so.61 ${CMAKE_PREFIX_PATH}/lib/libavcodec.so.61
${CMAKE_PREFIX_PATH}/lib/libavcodec.so.61.3.100 ${CMAKE_PREFIX_PATH}/lib/libavcodec.so.61.3.100
${CMAKE_PREFIX_PATH}/lib/libavutil.so ${CMAKE_PREFIX_PATH}/lib/libavutil.so
@@ -1164,6 +1200,9 @@ function(orcaslicer_copy_sos target config postfix output_sos)
DESTINATION ${_out_dir}) DESTINATION ${_out_dir})
set(${output_sos} set(${output_sos}
${_out_dir}/libavformat.so
${_out_dir}/libavformat.so.61
${_out_dir}/libavformat.so.61.1.100
${_out_dir}/libavcodec.so ${_out_dir}/libavcodec.so
${_out_dir}/libavcodec.so.61 ${_out_dir}/libavcodec.so.61
${_out_dir}/libavcodec.so.61.3.100 ${_out_dir}/libavcodec.so.61.3.100
@@ -1293,6 +1332,8 @@ endif ()
if (CMAKE_SYSTEM_NAME STREQUAL "Linux") if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(LIBRARY_FILES set(LIBRARY_FILES
${LIBDIR_BIN}/libavformat.so.61
${LIBDIR_BIN}/libavformat.so.61.1.100
${LIBDIR_BIN}/libavcodec.so.61 ${LIBDIR_BIN}/libavcodec.so.61
${LIBDIR_BIN}/libavcodec.so.61.3.100 ${LIBDIR_BIN}/libavcodec.so.61.3.100
${LIBDIR_BIN}/libavutil.so.59 ${LIBDIR_BIN}/libavutil.so.59
+18 -6
View File
@@ -164,7 +164,7 @@ if (NOT _is_multi AND NOT CMAKE_BUILD_TYPE)
endif () endif ()
function(orcaslicer_add_cmake_project projectname) function(orcaslicer_add_cmake_project projectname)
cmake_parse_arguments(P_ARGS "FORWARD_CONFIG" "INSTALL_DIR;BUILD_COMMAND;INSTALL_COMMAND" "CMAKE_ARGS" ${ARGN}) cmake_parse_arguments(P_ARGS "FORWARD_CONFIG" "INSTALL_DIR;BUILD_COMMAND;INSTALL_COMMAND;SOURCE_DIR" "CMAKE_ARGS" ${ARGN})
# MSVC is true for clang-cl as well, so the sub-build toolchain has to key on the # MSVC is true for clang-cl as well, so the sub-build toolchain has to key on the
# generator. A non-Visual-Studio superbuild passes its own generator down, and with # generator. A non-Visual-Studio superbuild passes its own generator down, and with
@@ -210,12 +210,18 @@ function(orcaslicer_add_cmake_project projectname)
set(_build_j "-j${NPROC}") set(_build_j "-j${NPROC}")
endif () endif ()
set(_source_dir_arg "")
if (P_ARGS_SOURCE_DIR)
set(_source_dir_arg SOURCE_DIR ${P_ARGS_SOURCE_DIR})
endif ()
if (NOT IS_CROSS_COMPILE OR NOT APPLE) if (NOT IS_CROSS_COMPILE OR NOT APPLE)
ExternalProject_Add( ExternalProject_Add(
dep_${projectname} dep_${projectname}
EXCLUDE_FROM_ALL ON EXCLUDE_FROM_ALL ON
INSTALL_DIR ${DESTDIR} INSTALL_DIR ${DESTDIR}
DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/${projectname} DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/${projectname}
${_source_dir_arg}
${_gen} ${_gen}
CMAKE_ARGS CMAKE_ARGS
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_POLICY_VERSION_MINIMUM=3.5
@@ -249,12 +255,14 @@ if (NOT IS_CROSS_COMPILE OR NOT APPLE)
# note for future devs: shared libs may actually create a size reduction # note for future devs: shared libs may actually create a size reduction
# but orcaslicer_deps tends to get really funny regarding linking after that (notably boost) # but orcaslicer_deps tends to get really funny regarding linking after that (notably boost)
# so, as much as I would like to use that, it's not happening # so, as much as I would like to use that, it's not happening
if (NOT P_ARGS_SOURCE_DIR)
ExternalProject_Add_Step(dep_${projectname} free_download_space ExternalProject_Add_Step(dep_${projectname} free_download_space
DEPENDEES download # do after download DEPENDEES download # do after download
COMMENT "Freeing Space: Removing source archive" COMMENT "Freeing Space: Removing source archive"
WORKING_DIRECTORY ${DEP_DOWNLOAD_DIR} WORKING_DIRECTORY ${DEP_DOWNLOAD_DIR}
COMMAND ${CMAKE_COMMAND} -E rm -r ${projectname} COMMAND ${CMAKE_COMMAND} -E rm -rf ${projectname}
) )
endif ()
ExternalProject_Add_Step(dep_${projectname} free_build_space ExternalProject_Add_Step(dep_${projectname} free_build_space
DEPENDEES install # do after install DEPENDEES install # do after install
COMMENT "Freeing Space: Removing source and build files" COMMENT "Freeing Space: Removing source and build files"
@@ -268,6 +276,7 @@ else()
EXCLUDE_FROM_ALL ON EXCLUDE_FROM_ALL ON
INSTALL_DIR ${DESTDIR} INSTALL_DIR ${DESTDIR}
DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/${projectname} DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/${projectname}
${_source_dir_arg}
${_gen} ${_gen}
CMAKE_ARGS CMAKE_ARGS
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_POLICY_VERSION_MINIMUM=3.5
@@ -396,10 +405,6 @@ include(libnoise/libnoise.cmake)
include(Draco/Draco.cmake) include(Draco/Draco.cmake)
include(FFMPEG/FFMPEG.cmake)
include(Assimp/Assimp.cmake)
# I *think* 1.1 is used for *just* md5 hashing? # I *think* 1.1 is used for *just* md5 hashing?
# 3.1 has everything in the right place, but the md5 funcs used are deprecated # 3.1 has everything in the right place, but the md5 funcs used are deprecated
# a grep across the repo shows it is used for other things # a grep across the repo shows it is used for other things
@@ -410,6 +415,12 @@ if(NOT OPENSSL_FOUND)
set(OPENSSL_PKG dep_OpenSSL) set(OPENSSL_PKG dep_OpenSSL)
endif() endif()
include(FFMPEG/FFMPEG.cmake)
include(Assimp/Assimp.cmake)
include(DataChannel/DataChannel.cmake)
set(DATACHANNEL_PKG dep_DataChannel)
# we don't want to load a "wrong" openssl when loading curl # we don't want to load a "wrong" openssl when loading curl
# so, just don't even bother # so, just don't even bother
# ...i think this is how it works? change if wrong # ...i think this is how it works? change if wrong
@@ -483,6 +494,7 @@ set(_dep_list
dep_wxInspector dep_wxInspector
dep_FFMPEG dep_FFMPEG
dep_Assimp dep_Assimp
${DATACHANNEL_PKG}
) )
if (MSVC) if (MSVC)
+37
View File
@@ -0,0 +1,37 @@
# libdatachannel is the native ICE/DTLS/SCTP implementation used by the
# GUI WebRTC camera controller. Keep the source revision fixed: the signaling
# protocol is evolving independently of this transport dependency.
#
# It vendors plog, usrsctp and libjuice as git submodules, which a plain
# GitHub tag tarball does not include. The flatpak sandbox has no network
# access during the build, so there the manifest itself clones the repo
# (submodules and all) into the dependency download directory before the
# sandbox closes. ExternalProject_Add is pointed at that existing checkout
# instead of being given its own network-dependent download method.
if (FLATPAK)
set(_datachannel_source
SOURCE_DIR ${DEP_DOWNLOAD_DIR}/DataChannel
)
else()
set(_datachannel_source
GIT_REPOSITORY https://github.com/paullouisageneau/libdatachannel.git
GIT_TAG v0.24.5
GIT_SHALLOW ON
GIT_SUBMODULES_RECURSE ON
)
endif()
orcaslicer_add_cmake_project(DataChannel
DEPENDS ${OPENSSL_PKG}
CMAKE_ARGS
-DNO_EXAMPLES=ON
-DNO_TESTS=ON
-DNO_WEBSOCKET=ON
-DNO_MEDIA=ON
-DUSE_NICE=OFF
-DUSE_SYSTEM_JUICE=OFF
-DUSE_SYSTEM_USRSCTP=OFF
-DOPENSSL_ROOT_DIR:PATH=${DESTDIR}
-DOPENSSL_USE_STATIC_LIBS=ON
${_datachannel_source}
)
+23 -7
View File
@@ -1,14 +1,26 @@
set(_conf_cmd ./configure) set(_conf_cmd ./configure)
set(_ffmpeg_depends)
set(_ffmpeg_configure_command ${_conf_cmd})
if (TARGET dep_OpenSSL)
set(_ffmpeg_depends DEPENDS dep_OpenSSL)
set(_ffmpeg_configure_command
${CMAKE_COMMAND} -E env
"PKG_CONFIG_PATH=${DESTDIR}/lib/pkgconfig:$ENV{PKG_CONFIG_PATH}"
${_conf_cmd}
)
endif()
if (MSVC) if (MSVC)
set(_source_dir "${CMAKE_BINARY_DIR}/dep_FFMPEG-prefix/src/dep_FFMPEG") set(_source_dir "${CMAKE_BINARY_DIR}/dep_FFMPEG-prefix/src/dep_FFMPEG")
set(PREBUILD_URL_arm64 "https://github.com/Noisyfox/FFmpeg-Builds-Orca/releases/download/autobuild-2026-07-17-14-28/ffmpeg-n7.0.3-31-g9b6ffd74b5-winarm64-orca-shared-7.0.zip") set(PREBUILD_URL_arm64 "https://github.com/Noisyfox/FFmpeg-Builds-Orca/releases/download/autobuild-2026-09-18-16-50/ffmpeg-n7.0.3-33-g887d4b4919-winarm64-orca-shared-7.0.zip")
set(PREBUILD_HASH_arm64 "12f4140279f2f8469885e1b5b2e8be9d788882914c21523cacd56989f3548054") set(PREBUILD_HASH_arm64 "da480cbb39680056de824c57ec4dc3bd577b479ebbc310ff1f9dc55cf014b4c1")
set(PREBUILD_URL_x64 "https://github.com/Noisyfox/FFmpeg-Builds-Orca/releases/download/autobuild-2026-07-17-14-28/ffmpeg-n7.0.3-31-g9b6ffd74b5-win64-orca-shared-7.0.zip") set(PREBUILD_URL_x64 "https://github.com/Noisyfox/FFmpeg-Builds-Orca/releases/download/autobuild-2026-09-18-16-50/ffmpeg-n7.0.3-33-g887d4b4919-win64-orca-shared-7.0.zip")
set(PREBUILD_HASH_x64 "e65916020ddb9ef84b2666dfbcbfc9b1d67f69d15b4a66db53754637bf2d498c") set(PREBUILD_HASH_x64 "85da19daf198f5548259d8aabb349db84997a3f6e6886d8d7764114add9c6dae")
ExternalProject_Add(dep_FFMPEG ExternalProject_Add(dep_FFMPEG
${_ffmpeg_depends}
URL ${PREBUILD_URL_${DEPS_ARCH}} URL ${PREBUILD_URL_${DEPS_ARCH}}
URL_HASH SHA256=${PREBUILD_HASH_${DEPS_ARCH}} URL_HASH SHA256=${PREBUILD_HASH_${DEPS_ARCH}}
DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/FFMPEG DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/FFMPEG
@@ -21,6 +33,8 @@ if (MSVC)
) )
else () else ()
set(_openssl_cmd --enable-openssl)
if (APPLE) if (APPLE)
set(_minos_cmd set(_minos_cmd
"--extra-cflags=-mmacosx-version-min=${DEP_OSX_TARGET}" "--extra-cflags=-mmacosx-version-min=${DEP_OSX_TARGET}"
@@ -52,10 +66,11 @@ else ()
endif() endif()
ExternalProject_Add(dep_FFMPEG ExternalProject_Add(dep_FFMPEG
${_ffmpeg_depends}
URL https://github.com/FFmpeg/FFmpeg/archive/refs/tags/n7.0.3.tar.gz URL https://github.com/FFmpeg/FFmpeg/archive/refs/tags/n7.0.3.tar.gz
URL_HASH SHA256=DEEDCABE339165214A3637DF4C86A507AEF0D793CF8774FF68735F4737E8DDBC URL_HASH SHA256=DEEDCABE339165214A3637DF4C86A507AEF0D793CF8774FF68735F4737E8DDBC
DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/FFMPEG DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/FFMPEG
CONFIGURE_COMMAND ${_conf_cmd} CONFIGURE_COMMAND ${_ffmpeg_configure_command}
${_cross_cmd} ${_cross_cmd}
${_pic_cmd} ${_pic_cmd}
${_arch_cmd} ${_arch_cmd}
@@ -63,20 +78,21 @@ else ()
"--prefix=${DESTDIR}" "--prefix=${DESTDIR}"
${_link_cmd} ${_link_cmd}
${_minos_cmd} ${_minos_cmd}
${_openssl_cmd}
--disable-doc --disable-doc
--enable-small --enable-small
--disable-outdevs --disable-outdevs
--disable-filters --disable-filters
--enable-filter=*null*,afade,*fifo,*format,*resample,aeval,allrgb,allyuv,atempo,pan,*bars,color,*key,crop,draw*,eq*,framerate,*_qsv,*_vaapi,*v4l2*,hw*,scale,volume,test* --enable-filter=*null*,afade,*fifo,*format,*resample,aeval,allrgb,allyuv,atempo,pan,*bars,color,*key,crop,draw*,eq*,framerate,*_qsv,*_vaapi,*v4l2*,hw*,scale,volume,test*
--disable-protocols --disable-protocols
--enable-protocol=file,fd,pipe,rtp,udp --enable-protocol=file,fd,pipe,http,https,rtp,tcp,udp
--disable-muxers --disable-muxers
--enable-muxer=rtp --enable-muxer=rtp
--disable-encoders --disable-encoders
--disable-decoders --disable-decoders
--enable-decoder=*aac*,h264*,mp3*,mjpeg,rv* --enable-decoder=*aac*,h264*,mp3*,mjpeg,rv*
--disable-demuxers --disable-demuxers
--enable-demuxer=h264,mp3,mov --enable-demuxer=h264,mp3,mov,mpjpeg,rtsp,sdp
--disable-zlib --disable-zlib
--disable-avdevice --disable-avdevice
BUILD_IN_SOURCE ON BUILD_IN_SOURCE ON
+45 -19
View File
@@ -28,8 +28,8 @@ Per-vendor granularity is what makes the system practical:
- A vendor whose profile is bumped invalidates only its own cache. The other 60-odd - A vendor whose profile is bumped invalidates only its own cache. The other 60-odd
vendors keep theirs — even when the bumped vendor is the shared Orca filament vendors keep theirs — even when the bumped vendor is the shared Orca filament
library everyone else inherits from. library everyone else inherits from.
- The setup wizard, which loads vendors one at a time, gets the same speedup as - The setup wizard loads its vendors through the same routine as startup, so it
startup without a second code path. gets the same speedup without a second code path.
- A vendor with no cache, or a broken one, costs only that vendor a parse. - A vendor with no cache, or a broken one, costs only that vendor a parse.
A cache holds *system* presets only. User presets, project settings and modified A cache holds *system* presets only. User presets, project settings and modified
@@ -162,15 +162,20 @@ cache nothing can invalidate is worse than no cache.
Vendors load in a fixed order, because filament inheritance crosses exactly one Vendors load in a fixed order, because filament inheritance crosses exactly one
boundary: any vendor's filament may inherit from the shared Orca filament library, boundary: any vendor's filament may inherit from the shared Orca filament library,
and nothing else reaches across vendors — an `include` is always vendor-local. The and nothing else reaches across vendors — an `include` is always vendor-local. Only
library therefore goes first, alone; every other vendor follows in parallel, resolving installing a vendor's presets crosses it; reading the vendor, from its cache or its
against it; and the results are merged in a stable order: JSONs, needs nothing from the library. So every other vendor is read while the library
loads, each is installed against it as soon as both are done, and the results are
merged in a stable order:
```mermaid ```mermaid
flowchart LR flowchart LR
lib["1 · OrcaFilamentLibrary<br/>loaded first, synchronously"] --> par["2 · every other vendor in parallel,<br/>each into its own bundle, filaments<br/>resolving against the loaded library"] --> merge["3 · bundles merged into one,<br/>sequentially, in stable vendor order"] lib["1 · OrcaFilamentLibrary loaded;<br/>meanwhile every other vendor read<br/>from its cache or its JSONs"] --> par["2 · every other vendor installed<br/>in parallel, each into its own bundle,<br/>filaments resolving against the library"] --> merge["3 · bundles merged into one,<br/>in one pass per collection,<br/>in stable vendor order"]
``` ```
`PresetBundle::load_vendors` runs these steps for startup and for the setup wizard,
which hand it the vendors to load and the directory each is installed in.
Whether a vendor comes from its cache or from a parse changes nothing in that Whether a vendor comes from its cache or from a parse changes nothing in that
order — both produce the same bundle, so cached and parsed vendors mix freely in order — both produce the same bundle, so cached and parsed vendors mix freely in
one startup. one startup.
@@ -211,11 +216,12 @@ shipped cache answered first, so the profile in `<data_dir>/system/` was never p
and its cache was never written back. and its cache was never written back.
Serving from a cache is not a memory-image restore. The entries are deserialized and Serving from a cache is not a memory-image restore. The entries are deserialized and
then installed one by one — inheritance resolved against the presets installed before then installed by `install_vendor`, the routine the JSON path hands the vendor's entries
them and the currently loaded filament library, includes layered in, configs flattened to once it has parsed the sub-files: inheritance resolved against the presets installed
onto the collection defaults, validated and registered — by the same function the JSON before them and the currently loaded filament library, includes layered in, configs
path calls straight after parsing a sub-file. An `include` layers what the included flattened onto the collection defaults, validated and registered. An `include` layers
base states, between the parent and the preset's own keys: the base's diff against the what the included base states, between the parent and the preset's own keys: the base's
diff against the
default, taken when the base itself was installed and before the per-variant padding default, taken when the base itself was installed and before the per-variant padding
`inherits` sees, so only what a template sets reaches the presets including it. The two `inherits` sees, so only what a template sets reaches the presets including it. The two
paths share everything below the parse, which is what makes a cache-loaded bundle paths share everything below the parse, which is what makes a cache-loaded bundle
@@ -223,6 +229,16 @@ indistinguishable from a JSON-loaded one by construction rather than by test cov
Installation also rebuilds each preset's file path from the local data directory, so a Installation also rebuilds each preset's file path from the local data directory, so a
shipped cache never carries the generating machine's paths. shipped cache never carries the generating machine's paths.
Installing an entry is split in two. `resolve_vendor_preset` flattens it, reading only
what is registered under the names it inherits and includes, and `commit_vendor_preset`
registers it, the only step that writes anything shared. Entries resolve across threads
in runs and commit in the order the vendor lists them. A run ends before an entry that
inherits or includes one already in it, since that one's commit registers what the
entry resolves against, so no entry in a run reads what another in it registers. An
entry's parse messages are held until it commits. The bundle, the log's parse and
install messages and the error count therefore come out as parsing and installing one
entry at a time would leave them, whatever the listing order.
App upgrades work because a cache normally survives one. Only a deliberate App upgrades work because a cache normally survives one. Only a deliberate
`CACHE_VERSION` bump makes an installed cache unreadable, and that is handled at `CACHE_VERSION` bump makes an installed cache unreadable, and that is handled at
install time rather than at load: a vendor whose cache this build cannot read counts install time rather than at load: a vendor whose cache this build cannot read counts
@@ -254,17 +270,20 @@ the wizard caches the *derived JSON*, not another form of the inputs:
open, the wizard computes the current stamps (one version peek per vendor) and, when open, the wizard computes the current stamps (one version peek per vendor) and, when
they match, serves the catalog from the file — no bundle built, no preset installed. they match, serves the catalog from the file — no bundle built, no preset installed.
Caching bundle inputs instead was tried and measured: rebuilding the bundle from Caching bundle inputs instead was tried and measured: rebuilding the bundle from
per-vendor caches costs ~2 s of preset installation whatever feeds it, so only per-vendor caches costs over a second of preset installation whatever feeds it, so
skipping the rebuild entirely wins. only skipping the rebuild entirely wins.
Any change to the set — a vendor added, removed or updated, or its cache-only Any change to the set — a vendor added, removed or updated, or its cache-only
`.opc` replaced by a newer one — changes the stamps and retires the whole file; `.opc` replaced by a newer one — changes the stamps and retires the whole file;
the wizard then rebuilds the bundle vendor by vendor (per-vendor caches serving where the wizard then rebuilds the bundle with `PresetBundle::load_vendors`, the load
they cover) and writes the catalog back. Selections, region and per-open decorations startup uses (per-vendor caches serving where they cover), and writes the catalog
are applied downstream of the cache either way, so a served catalog is back. When a vendor fails to load, the filament library included, that open falls
indistinguishable from a rebuilt one. Nothing ships this file and the updater never back to the wizard's own scan of the vendor JSONs, as when no bundle can be built,
touches it; it is a locally written artifact, re-derived whenever stale, written and writes nothing. Selections, region and per-open decorations are applied
through a temp file and rename so half a cache is never readable. downstream of the cache either way, so a served catalog is indistinguishable from a
rebuilt one. Nothing ships this file and the updater never touches it; it is a
locally written artifact, re-derived whenever stale, written through a temp file and
rename so half a cache is never readable.
The cache lives under `<data_dir>/cache/`, not beside the vendors: everything that The cache lives under `<data_dir>/cache/`, not beside the vendors: everything that
scans `<data_dir>/system/` treats any `.opc` there as a vendor, so a non-vendor scans `<data_dir>/system/` treats any `.opc` there as a vendor, so a non-vendor
@@ -378,6 +397,13 @@ enumerates only `*.json` will find no vendors at all in a packaged build.
the `CachedPreset` field list — written and read by `visit_entry` in the `CachedPreset` field list — written and read by `visit_entry` in
`PresetCacheFormat.cpp`, one list for the save, the load and the name peek alike — or `PresetCacheFormat.cpp`, one list for the save, the load and the name peek alike — or
the cache's own layout or stamps, requires bumping `CACHE_VERSION` by hand. the cache's own layout or stamps, requires bumping `CACHE_VERSION` by hand.
- **Adding a kind of reference between presets**, as `inherits` and `include` are:
parse the names into `CachedPreset` (a field change, so `CACHE_VERSION` is bumped),
have `install_vendor_entries` end a run before an entry that names one already in it
and retain what the names point at, look them up only in `resolve_vendor_preset`, and
register what they point at only in `commit_vendor_preset`. The listing-order test in
`test_vendor_cache.cpp` fails for a kind the runs do not check once its fixture uses
it.
- **The dictionary indexes with a `uint16`**, so `print_config_def` may hold at most - **The dictionary indexes with a `uint16`**, so `print_config_def` may hold at most
65535 options and one cache at most 65535 distinct enum value names. 65535 options and one cache at most 65535 distinct enum value names.
`CacheDictionary::save` throws past that, which surfaces when CI generates the `CacheDictionary::save` throws past that, which surfaces when CI generates the
+240
View File
@@ -0,0 +1,240 @@
# Printer agents
Printer agents isolate printer-specific communication from the rest of OrcaSlicer. The GUI and
`DeviceManager` operate on a shared set of printer operations and device state; a selected printer
agent implements those operations for a particular printer ecosystem. The agent boundary allows
Bambu, Moonraker-based printers, built-in integrations, and Python-provided integrations to use the
same application workflow without making the GUI understand every printer protocol.
The current boundary is an adapter boundary around the existing application contract. In particular,
some request fields and message payloads still use the Bambu-shaped representation that existing
`MachineObject` and `DeviceManager` code consumes. The printer agent is responsible for translating
that representation into the protocol spoken by its printer. This is an intentional compatibility
constraint of the current design; the interface is not yet a neutral printer protocol.
The v1 dialect migration path is deliberately narrow. `DeviceManager` currently speaks the Bambu JSON
dialect because that is the payload shape already used throughout the command and state workflow. The
v1 `OrcaPrinterAgent` also accepts that Bambu dialect. Its transport path places the small translation
needed for the target printer at `deliver_to_sink`, keeping the compatibility code at the edge rather
than spreading it through `DeviceManager` or the agent interface.
The eventual direction is for `DeviceManager` to produce an Orca JSON dialect. The Bambu agent will then
own the translation from Orca JSON to Bambu's protocol, while `OrcaPrinterAgent` can forward the Orca
payload directly to its sink. The v1 translation at `deliver_to_sink` can then be removed without
changing `DeviceManager`, the command callers, or the rest of the agent workflow.
## Components
The system has four relevant layers:
```text
GUI / DeviceManager / MachineObject
|
NetworkAgent
/ \
IPrinterAgent ICloudServiceAgent
| |
printer protocol authentication and cloud services
```
### `DeviceManager` and `MachineObject`
`DeviceManager` owns the application-facing printer workflow. It maintains `MachineObject` instances,
updates their state, filters devices for the active printer agent, and initiates operations such as
homing, temperature changes, printing, subscriptions, and camera playback.
`MachineObject` remains the shared state model used by the GUI. It does not contain the implementation
of a printer protocol. When a device is discovered or returned by a cloud query, the device is tagged
with the active `printer_agent_id`. Device lists and selected-machine operations use that tag to avoid
sending an operation through an agent that does not own the device.
### `NetworkAgent`
`NetworkAgent` is the façade used by the GUI and `DeviceManager`. It owns:
- the currently selected `IPrinterAgent`;
- the registered cloud-service instances, indexed by provider;
- callbacks shared by the active printer agent and the application;
- the forwarding methods for printer commands and cloud operations.
There is one active printer agent for the currently selected printer preset. Switching the preset
increments the machine-list generation, disconnects the old printer agent, removes its callbacks, and
installs the newly selected agent. The façade then forwards printer operations to that agent.
Cloud operations are selected separately using a provider key. `NetworkAgent` forwards a cloud request
to the matching `ICloudServiceAgent`, and forwards cloud camera operations with a device ID. The
printer agent receives a cloud-agent pointer through `set_cloud_agent()` when it is created, allowing
printer communication to obtain cloud tokens without depending on a concrete cloud implementation.
### `IPrinterAgent`
`IPrinterAgent` is the printer-facing contract. It covers:
- cloud-relay and direct-LAN message delivery;
- LAN connection, discovery, binding, and certificates;
- printer subscriptions and callbacks;
- print operations;
- filament synchronization;
- camera capability and local camera URL reporting;
- printer command methods.
Concrete built-in implementations include the Bambu wrapper, the native Orca/Moonraker path, and
other printer-agent implementations registered by the application. A printer agent may use either
the cloud agent, a direct LAN connection, or both.
### `ICloudServiceAgent`
`ICloudServiceAgent` owns authentication and services provided by a cloud backend. It covers login
state, tokens, user and printer lists, settings synchronization, model services, cloud messages, and
cloud camera operations.
Cloud camera operations are device-scoped:
- `get_camera_url(dev_id, callback)` obtains a stream URL for one device;
- `create_camera_signaling_channel(dev_id)` creates signaling for one device where the provider
supports it.
This is separate from the local camera URL exposed by `IPrinterAgent`, which is currently scoped to
the active printer agent because a normal LAN agent represents one physical printer connection.
## Agent registration and selection
`NetworkAgentFactory` maintains the printer-agent registry. Each registry entry contains an agent ID,
a display name, and a factory function. Built-in agents register during application initialization.
Python printer-agent capabilities register dynamically and contribute an agent ID and factory entry.
The selected printer preset contains the printer-agent choice. If no explicit choice is stored, the
application preserves the existing default behavior: Bambu presets select the Bambu agent and other
presets select the native Orca agent. When a preset is changed, `GUI_App` resolves the effective agent
ID, obtains the corresponding cloud agent, creates the printer agent through the registry, and installs
it in `NetworkAgent`.
The registry rejects conflicting agent IDs. This matters for Python plugins because an agent ID is the
stable identity used by presets and device ownership; two enabled plugin capabilities must not claim
the same ID.
## Message and command flow
There are two low-level message paths:
- `send_message()` publishes a command through the printer's cloud relay;
- `send_message_to_printer()` sends a command directly to the printer over the LAN path.
Both paths accept a JSON string, quality-of-service and flag values, and return the existing network
status code domain. The agent owns the conversion from that JSON contract to its native transport.
The typed `command_*` methods are the application-facing convenience layer. The five generic defaults
currently implemented by `IPrinterAgent` construct the existing JSON dialect and route through the
same message path:
| Method | Default operation |
| --- | --- |
| `command_xyz_abs()` | Send `G90` for absolute positioning |
| `command_auto_leveling()` | Send `G29` for bed leveling |
| `command_go_home()` | Use the supported homing operation or send `G28` |
| `command_set_bed()` | Use the supported bed control or send `M140` |
| `command_set_nozzle()` | Send `M104` for nozzle temperature |
These are compatibility defaults for common printer workflows, not a guarantee that every firmware
implements every command identically. An agent can override a method when its protocol needs another
operation. For example, a Klipper configuration may use `BED_MESH_CALIBRATE` instead of `G29`.
The remaining common command methods default to `ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED` because their
existing behavior is vendor-specific or has no portable implementation:
- AMS RFID refresh;
- AMS calibration;
- AMS tray selection;
- camera start;
- axis control.
The methods remain on the common interface so an agent that supports them can override them explicitly.
`sequence_id` remains part of the command contract because `DeviceManager` creates and tracks it as
the command ID.
## Device ownership and stale responses
Printer-agent ownership is represented by `printer_agent_id` on device records and `MachineObject`
instances. The active agent ID is attached when a device is discovered, returned by a cloud list, or
reused after a preset switch. Local-machine configuration also persists the agent ID so a saved LAN
device is not silently reused by an unrelated agent.
Cloud printer-list responses carry three pieces of request context added by `NetworkAgent`:
```text
provider cloud provider used for the request
agent_id active printer agent when the request was made
generation machine-list generation when the request was made
```
`DeviceManager` accepts the response only when those values still match the current provider, active
agent, and generation. This prevents a slow response from the previous preset or provider from
repopulating the current device list.
The provider mapping is currently selected by `GUI_App`: the Bambu agent maps to the Bambu cloud
provider and other agents map to the Orca cloud provider. The generation check protects that existing
selection from races; it does not make cloud-provider ownership intrinsic to an agent. Cloud-printer
ownership and the broader Orca cloud services are therefore still separate architectural concerns.
## Python printer agents
`PrinterAgentPluginCapability` implements `IPrinterAgent` directly. The live capability object is
registered with `NetworkAgentFactory` and handed out as the printer agent when its agent ID is selected.
The plugin receives the selected `ICloudServiceAgent` through `set_cloud_agent()` just like a built-in
printer agent.
Python plugins must implement the core communication and lifecycle methods required by the interface,
including agent metadata, printer connection, discovery callbacks, and the two message-send methods.
Methods that are meaningful only to a particular printer are optional overrides where the C++ base
class provides a default.
All ten `command_*` methods are available in the Python binding and in the trampoline. Their override
status is intentionally optional:
- the five generic commands use the C++ default when Python does not override them;
- the five vendor-specific commands return `NOT_SUPPORTED` unless Python supplies an implementation;
- a Python implementation can replace either behavior for its own protocol.
The Python camera binding exposes HTTP, HTTPS, RTSP, and HTTP-snapshot modes. WebRTC remains a
built-in C++ camera mode, but is not exposed as a Python mode because the current Python capability
does not provide the corresponding cloud signaling-channel contract.
## Camera playback boundary
The camera stream mode describes how a stream is obtained; it does not by itself define ownership of
the wxWidgets view that renders it. `MediaPlayCtrl` selects and tears down the active backend, while
the wx parent owns the child window or renderer. This is important because a web view, native media
control, and frame-based/WebRTC renderer have different wx window-lifetime requirements.
Cloud URL and signaling requests are routed through `NetworkAgent` to the cloud provider selected for
the device. Local URL requests are routed to the active printer agent. The distinction keeps cloud
account services device-scoped while preserving the current one-LAN-agent/one-printer model.
## Compatibility constraints
The printer-agent boundary intentionally preserves several existing application contracts:
- Bambu-shaped JSON is still the shared command representation;
- existing network status codes are reused, with Orca-specific unsupported/capability errors added
in the Orca-reserved range;
- `MachineObject` remains the shared device-state model;
- preset and local-machine data retain compatibility with the existing agent-selection behavior;
- Python plugins use the existing capability and pybind11 registration system.
The agent abstraction is therefore responsible for containing vendor differences, not for pretending
that all vendor protocols are identical. The planned Orca JSON dialect is the protocol-neutral command
model for the `DeviceManager`/agent boundary. Once it is introduced, Bambu-specific translation remains
inside the Bambu agent and the Orca agent's v1 sink adapter can be removed as a self-contained cleanup.
## Main implementation locations
- [`IPrinterAgent`](../../src/slic3r/Utils/IPrinterAgent.hpp) — printer-agent contract and generic command defaults
- [`ICloudServiceAgent`](../../src/slic3r/Utils/ICloudServiceAgent.hpp) — cloud service and per-device
cloud camera contract
- [`NetworkAgent`](../../src/slic3r/Utils/NetworkAgent.hpp) — façade and dispatch between active agents
- [`NetworkAgentFactory`](../../src/slic3r/Utils/NetworkAgentFactory.hpp) — built-in and Python agent registry
- [`DeviceManager`](../../src/slic3r/GUI/DeviceCore/DevManager.cpp) — device ownership, filtering, and
stale-response checks
- [`PrinterAgentPluginCapability`](../../src/slic3r/plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.cpp)
— Python bindings
- [`MediaPlayCtrl`](../../src/slic3r/GUI/MediaPlayCtrl.cpp) — camera backend selection and playback lifecycle
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "OrcaFilamentLibrary", "name": "OrcaFilamentLibrary",
"version": "02.04.00.15", "version": "02.04.00.17",
"force_update": "0", "force_update": "0",
"description": "Orca Filament Library", "description": "Orca Filament Library",
"filament_list": [ "filament_list": [
@@ -6,7 +6,7 @@
"filament_id": "OFvgE0Zh", "filament_id": "OFvgE0Zh",
"instantiation": "false", "instantiation": "false",
"fan_cooling_layer_time": [ "fan_cooling_layer_time": [
"81" "85"
], ],
"fan_min_speed": [ "fan_min_speed": [
"50" "50"
@@ -111,6 +111,7 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -316,6 +317,7 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,6 +111,7 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -316,6 +317,7 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,6 +111,7 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -316,6 +317,7 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,6 +111,7 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -316,6 +317,7 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,6 +111,7 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -316,6 +317,7 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,6 +111,7 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -316,6 +317,7 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,6 +111,7 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -316,6 +317,7 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,6 +111,7 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -316,6 +317,7 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,6 +111,7 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -316,6 +317,7 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,6 +111,7 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -316,6 +317,7 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,6 +111,7 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -316,6 +317,7 @@
"1" "1"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,6 +111,7 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -316,6 +317,7 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,6 +111,7 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -316,6 +317,7 @@
"0" "0"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,6 +111,7 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -316,6 +317,7 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,6 +111,7 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -316,6 +317,7 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,6 +111,7 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -316,6 +317,7 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,6 +111,7 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -316,6 +317,7 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,6 +111,7 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -316,6 +317,7 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,6 +111,7 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -316,6 +317,7 @@
"0" "0"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,6 +111,7 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -316,6 +317,7 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,6 +111,7 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -316,6 +317,7 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,6 +111,7 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -316,6 +317,7 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,6 +111,7 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -316,6 +317,7 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,6 +111,7 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -316,6 +317,7 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,6 +111,7 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -316,6 +317,7 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,6 +111,7 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -316,6 +317,7 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
+3 -1
View File
@@ -575,7 +575,7 @@ function CapabilityCanRun(plugin, capability) {
} }
function IsPluginChecked(plugin) { function IsPluginChecked(plugin) {
return GetStatus(plugin) === "Activated"; return plugin.is_loaded;
} }
function HasMixedCapabilityState(plugin) { function HasMixedCapabilityState(plugin) {
@@ -1347,6 +1347,8 @@ function StatusDescription(plugin) {
return "This plugin is still loading."; return "This plugin is still loading.";
case "Error": case "Error":
return "This plugin is blocked until its error is fixed."; return "This plugin is blocked until its error is fixed.";
case "RuntimeError":
return "This plugin is loaded but a capability reported an error.";
case "Inactive": case "Inactive":
default: default:
return "This plugin is inactive. Activate it to install or load it."; return "This plugin is inactive. Activate it to install or load it.";
@@ -424,6 +424,11 @@ body.pane-resizing {
font-weight: 600; font-weight: 600;
} }
.status-cell.status-runtimeerror {
color: var(--plugin-status-warn);
font-weight: 600;
}
.status-cell.status-loading { .status-cell.status-loading {
color: var(--plugin-status-warn); color: var(--plugin-status-warn);
font-weight: 600; font-weight: 600;
@@ -680,6 +685,11 @@ body.pane-resizing {
color: var(--plugin-status-danger); color: var(--plugin-status-danger);
} }
.detail-status-chip.status-runtimeerror {
background: var(--plugin-status-warn-bg);
color: var(--plugin-status-warn);
}
.detail-status-chip.status-loading { .detail-status-chip.status-loading {
background: var(--plugin-status-warn-bg); background: var(--plugin-status-warn-bg);
color: var(--plugin-status-warn); color: var(--plugin-status-warn);
+129 -30
View File
@@ -214,9 +214,9 @@ function ShowModelInfo( pModel )
SendWXDebugInfo("Model Name: "+sModelName); SendWXDebugInfo("Model Name: "+sModelName);
$('#ModelName').html(sModelName); $('#ModelName').text(sModelName);
$('#ModelName').attr('title',sModelName); $('#ModelName').attr('title',sModelName);
$('#ModelAuthorName').html(sModelAuthor); $('#ModelAuthorName').text(sModelAuthor);
switch(UploadType) switch(UploadType)
{ {
@@ -268,7 +268,7 @@ function ShowModelInfo( pModel )
break; break;
} }
$('#Model_Desc').html( html_decode(sModelDesc) ); $('#Model_Desc').empty().append( SanitizeDescHtml( html_decode(sModelDesc) ) );
let ModelPreviewList=pModel.preview_img; let ModelPreviewList=pModel.preview_img;
let TotalPreview=ModelPreviewList.length; let TotalPreview=ModelPreviewList.length;
@@ -281,16 +281,15 @@ function ShowModelInfo( pModel )
if(TotalPreview>0) if(TotalPreview>0)
{ {
let htmlPreview=''; $('#ModelPreviewList').empty();
for(let pn=0;pn<TotalPreview;pn++) for(let pn=0;pn<TotalPreview;pn++)
{ {
//let FTmpPath=decodeURIComponent(ModelPreviewList[pn]); //let FTmpPath=decodeURIComponent(ModelPreviewList[pn]);
let FTmpPath=ModelPreviewList[pn]['filepath']; let FTmpPath=ModelPreviewList[pn]['filepath'];
htmlPreview+='<div class="swiper-slide"><img class="Model_PrevImg" src="'+FTmpPath+'" /></div>'; $('#ModelPreviewList').append( $('<div class="swiper-slide"></div>').append( $('<img class="Model_PrevImg" />').attr('src',FTmpPath) ) );
} }
$('#ModelPreviewList').html(htmlPreview);
$('#Model_Preview_Image').viewer({ $('#Model_Preview_Image').viewer({
title: false, title: false,
fullsreen: false, fullsreen: false,
@@ -410,7 +409,8 @@ function ConstructFileHtml( ID, pItem )
{ {
let fTotal=pItem.length; let fTotal=pItem.length;
let strHtml=''; let pBoard=$('#'+ID+' .FileListBoard');
pBoard.empty();
for( let f=0;f<fTotal;f++ ) for( let f=0;f<fTotal;f++ )
{ {
let pOne=pItem[f]; let pOne=pItem[f];
@@ -443,39 +443,139 @@ function ConstructFileHtml( ID, pItem )
ImgPath='img/default.png'; ImgPath='img/default.png';
} }
//Add html //Add html. File names come from the 3MF, so build the nodes rather than concatenating markup.
let pIconImg=$('<img />').attr('src',ImgPath);
let pMenu=$('<div class="FileMenu"><img src="img/s.svg" /></div>');
if( strClass!='ImageIcon' ) if( strClass!='ImageIcon' )
{ {
strHtml+='<div class="FileItem">'+ pMenu.on('click', function(){ OnClickOpenFile(tPath); });
' <div class="'+strClass+'"><img src="'+ImgPath+'" /></div>'+
' <div class="FileText">'+
' <div class="FileName">'+tName+'</div>'+
' </div>'+
' <div class="FileMenu" onClick="OnClickOpenFile(\''+tPath+'\')"><img src="img/s.svg" /></div>'+
'</div>';
} }
else else
{ {
ImgID++; ImgID++;
let TmpImgID="AF"+ImgID; let TmpImgID="AF"+ImgID;
strHtml+='<div class="FileItem">'+ pIconImg.attr('id',TmpImgID);
' <div class="'+strClass+'"><img id="'+TmpImgID+'" src="'+ImgPath+'" /></div>'+ pMenu.on('click', function(){ OnClickOpenImage(TmpImgID); });
' <div class="FileText">'+
' <div class="FileName">'+tName+'</div>'+
' </div>'+
' <div class="FileMenu" onClick="OnClickOpenImage(\''+TmpImgID+'\')"><img src="img/s.svg" /></div>'+
'</div>';
}
} }
$('#'+ID+' .FileListBoard').html(strHtml); let pFileItem=$('<div class="FileItem"></div>');
pFileItem.append( $('<div></div>').addClass(strClass).append(pIconImg) );
pFileItem.append( $('<div class="FileText"></div>').append( $('<div class="FileName"></div>').text(tName).attr('title',tName) ) );
pFileItem.append( pMenu );
pBoard.append( pFileItem );
}
if( fTotal>0 ) if( fTotal>0 )
$('#'+ID).show(); $('#'+ID).show();
} }
// Descriptions are untrusted 3MF metadata that may carry rich-text HTML (e.g. from MakerWorld).
// Rebuild them from an inert parse, keeping only plain formatting tags and http(s) links and images.
var DescAllowedTags=['P','BR','B','STRONG','I','EM','U','S','STRIKE','DEL','INS','SUB','SUP','SMALL','MARK',
'Q','ABBR','KBD','WBR','H1','H2','H3','H4','H5','H6','UL','OL','LI','DL','DT','DD','BLOCKQUOTE','PRE','CODE',
'HR','SPAN','DIV','FIGURE','FIGCAPTION','TABLE','CAPTION','THEAD','TBODY','TFOOT','TR','TH','TD','A','IMG'];
// Plain attributes kept per tag; the numeric ones must be plain non-negative integers.
var DescAllowedAttrs={'IMG':['alt','title','width','height'],'TD':['colspan','rowspan'],'TH':['colspan','rowspan'],'OL':['start']};
var DescNumericAttrs=['width','height','colspan','rowspan','start'];
// Dropped together with their content; any other unknown tag is unwrapped to its children.
var DescDroppedTags=['SCRIPT','STYLE','TEMPLATE','NOSCRIPT','TEXTAREA','TITLE','IFRAME','FRAME','OBJECT','EMBED','SVG','MATH'];
function IsHttpUrl( strUrl )
{
// The scheme must be written as is, so nothing the URL parser would strip can precede or split it.
if( typeof strUrl!='string' || !/^https?:/i.test(strUrl) )
return false;
try
{
let sProtocol=new URL(strUrl).protocol;
return sProtocol=='http:' || sProtocol=='https:';
}
catch(e)
{
return false;
}
}
// Images load as soon as the page opens, so only https sources are kept: no plain-http requests to the local network.
function IsHttpsUrl( strUrl )
{
return IsHttpUrl(strUrl) && new URL(strUrl).protocol=='https:';
}
// Embedded YouTube players become a plain link to the video.
function GetYouTubeEmbedUrl( pNode )
{
let sSrc=pNode.getAttribute('src');
if( !IsHttpUrl(sSrc) )
return null;
let pUrl=new URL(sSrc);
return ( pUrl.origin=='https://www.youtube.com' && pUrl.pathname.indexOf('/embed/')==0 ) ? pUrl.href : null;
}
function CopyDescNodes( pSrc, pDst )
{
for( let pNode=pSrc.firstChild;pNode!=null;pNode=pNode.nextSibling )
{
if( pNode.nodeType==Node.TEXT_NODE )
{
pDst.appendChild( document.createTextNode(pNode.nodeValue) );
continue;
}
if( pNode.nodeType!=Node.ELEMENT_NODE )
continue;
let sTag=pNode.nodeName.toUpperCase();
if( sTag=='IFRAME' )
{
let sVideoUrl=GetYouTubeEmbedUrl(pNode);
if( sVideoUrl!=null )
{
let pLink=document.createElement('A');
pLink.setAttribute('href',sVideoUrl);
pLink.textContent=sVideoUrl;
pDst.appendChild(pLink);
}
continue;
}
if( $.inArray(sTag,DescDroppedTags)>=0 )
continue;
if( $.inArray(sTag,DescAllowedTags)<0 )
{
CopyDescNodes(pNode,pDst);
continue;
}
let pElem=document.createElement(sTag);
if( sTag=='A' && IsHttpUrl(pNode.getAttribute('href')) )
pElem.setAttribute('href',pNode.getAttribute('href'));
else if( sTag=='IMG' )
{
if( !IsHttpsUrl(pNode.getAttribute('src')) )
continue;
pElem.setAttribute('src',pNode.getAttribute('src'));
}
$.each( DescAllowedAttrs[sTag]||[], function(i,sAttr){
let sValue=pNode.getAttribute(sAttr);
if( sValue!=null && ( $.inArray(sAttr,DescNumericAttrs)<0 || /^\d+$/.test(sValue) ) )
pElem.setAttribute(sAttr,sValue);
});
CopyDescNodes(pNode,pElem);
pDst.appendChild(pElem);
}
}
function SanitizeDescHtml( strHtml )
{
let pFragment=document.createDocumentFragment();
// A DOMParser document is inert: it runs no scripts and loads no resources.
let pDoc=new DOMParser().parseFromString(strHtml,'text/html');
if( pDoc && pDoc.body )
CopyDescNodes(pDoc.body,pFragment);
return pFragment;
}
function ShowProfilelInfo( pProfile ) function ShowProfilelInfo( pProfile )
{ {
//==========Profile Info========== //==========Profile Info==========
@@ -483,10 +583,10 @@ function ShowProfilelInfo( pProfile )
let sProfileAuthor=decodeURIComponent(pProfile.author); let sProfileAuthor=decodeURIComponent(pProfile.author);
let sProfileDesc=decodeURIComponent(pProfile.description); let sProfileDesc=decodeURIComponent(pProfile.description);
$('#ProfileName').html(sProfileName); $('#ProfileName').text(sProfileName);
$('#ProfileAuthor').html(sProfileAuthor); $('#ProfileAuthor').text(sProfileAuthor);
$('#Profile_Desc').html( html_decode(sProfileDesc) ); $('#Profile_Desc').empty().append( SanitizeDescHtml( html_decode(sProfileDesc) ) );
let ProfilePreviewList=pProfile.preview_img; let ProfilePreviewList=pProfile.preview_img;
let TotalPreview=ProfilePreviewList.length; let TotalPreview=ProfilePreviewList.length;
@@ -499,15 +599,14 @@ function ShowProfilelInfo( pProfile )
if(TotalPreview>0) if(TotalPreview>0)
{ {
let htmlPreview=''; $('#ProfilePreviewList').empty();
for(let pn=0;pn<TotalPreview;pn++) for(let pn=0;pn<TotalPreview;pn++)
{ {
let FTmpPath=ProfilePreviewList[pn]['filepath']; let FTmpPath=ProfilePreviewList[pn]['filepath'];
htmlPreview+='<div class="swiper-slide"><img class="Model_PrevImg" src="'+FTmpPath+'" /></div>'; $('#ProfilePreviewList').append( $('<div class="swiper-slide"></div>').append( $('<img class="Model_PrevImg" />').attr('src',FTmpPath) ) );
} }
$('#ProfilePreviewList').html(htmlPreview);
$('#Profile_Preview_Image').viewer({ $('#Profile_Preview_Image').viewer({
title: false, title: false,
fullsreen: false, fullsreen: false,
@@ -331,6 +331,15 @@ modules:
sha256: deedcabe339165214a3637df4c86a507aef0d793cf8774ff68735f4737e8ddbc sha256: deedcabe339165214a3637df4c86a507aef0d793cf8774ff68735f4737e8ddbc
dest: external-packages/FFMPEG dest: external-packages/FFMPEG
# libdatachannel v0.24.5
# The Git checkout includes the submodules that are missing from the
# GitHub source archive. DataChannel.cmake consumes it as SOURCE_DIR.
- type: git
url: https://github.com/paullouisageneau/libdatachannel.git
tag: v0.24.5
commit: 443f6934d9007eb7076ab7825ba330f355fcbead
dest: external-packages/DataChannel
# --------------------------------------------------------------- # ---------------------------------------------------------------
# Fallback archives for deps normally provided by the GNOME SDK. # Fallback archives for deps normally provided by the GNOME SDK.
# These are only used if find_package() fails to locate them. # These are only used if find_package() fails to locate them.
+76 -4
View File
@@ -1913,6 +1913,7 @@ int CLI::run(int argc, char **argv)
old_printable_width = static_cast<int>(old_printable_bbox.size().x()); old_printable_width = static_cast<int>(old_printable_bbox.size().x());
old_printable_depth = static_cast<int>(old_printable_bbox.size().y()); old_printable_depth = static_cast<int>(old_printable_bbox.size().y());
} }
if (config.option<ConfigOptionFloat>("printable_height"))
old_printable_height = (int)(config.opt_float("printable_height")); old_printable_height = (int)(config.opt_float("printable_height"));
if (config.option<ConfigOptionFloat>("extruder_clearance_height_to_rod")) if (config.option<ConfigOptionFloat>("extruder_clearance_height_to_rod"))
@@ -2116,7 +2117,12 @@ int CLI::run(int argc, char **argv)
// One resolver for the whole run, so presets from the same vendor tree share its load. // One resolver for the whole run, so presets from the same vendor tree share its load.
std::unique_ptr<PresetBundle> system_preset_resolver; std::unique_ptr<PresetBundle> system_preset_resolver;
auto resolve_preset = [&ensure_cli_preset_bundle, &system_preset_resolver](const std::string &file, DynamicPrintConfig &config, auto ensure_system_preset_resolver = [&system_preset_resolver]() -> PresetBundle & {
if (!system_preset_resolver)
system_preset_resolver = std::make_unique<PresetBundle>();
return *system_preset_resolver;
};
auto resolve_preset = [&ensure_cli_preset_bundle, &ensure_system_preset_resolver](const std::string &file, DynamicPrintConfig &config,
std::string &config_type, const std::string &config_from, std::string &config_type, const std::string &config_from,
bool probe_type, std::string &error) { bool probe_type, std::string &error) {
const auto *inherits = config.option<ConfigOptionString>(BBL_JSON_KEY_INHERITS); const auto *inherits = config.option<ConfigOptionString>(BBL_JSON_KEY_INHERITS);
@@ -2126,9 +2132,7 @@ int CLI::run(int argc, char **argv)
PresetBundle *bundle = nullptr; PresetBundle *bundle = nullptr;
bool allow_source_manifest = false; bool allow_source_manifest = false;
if (config_from == "system") { if (config_from == "system") {
if (!system_preset_resolver) bundle = &ensure_system_preset_resolver();
system_preset_resolver = std::make_unique<PresetBundle>();
bundle = system_preset_resolver.get();
allow_source_manifest = true; allow_source_manifest = true;
} else { } else {
bundle = ensure_cli_preset_bundle(error); bundle = ensure_cli_preset_bundle(error);
@@ -3120,6 +3124,44 @@ int CLI::run(int argc, char **argv)
return 0; return 0;
}; };
// Load the project's printer and process settings as the GUI loads its presets: over the default preset,
// with every key the project does not list as changed, including keys saved before an option existed,
// taken from its current system preset.
auto load_project_preset = [this, &ensure_system_preset_resolver, &current_different_settings, filament_count](const std::string &system_name, Preset::Type type) {
if (system_name.empty())
return;
// Preset bookkeeping the CLI keeps in its own groups, e.g. inherits_group and print_compatible_printers.
static const std::set<std::string> bookkeeping_keys = {"inherits", "compatible_printers", "compatible_prints", "compatible_printers_condition",
"compatible_prints_condition", "print_settings_id", "printer_settings_id"};
const size_t index = type == Preset::TYPE_PRINTER ? filament_count + 1 : 0;
PresetBundle &resolver = ensure_system_preset_resolver();
DynamicPrintConfig system_config;
t_config_option_keys keys;
const DynamicPrintConfig config = Preset::load_external_config(type,
type == Preset::TYPE_PRINTER ? resolver.printers.default_preset_for(m_print_config).config : resolver.prints.default_preset().config,
m_print_config, PresetBundle::project_different_keys(index < current_different_settings.size() ? current_different_settings[index] : std::string()),
[&](const std::string &) -> DynamicPrintConfig * {
std::string error;
if (resolver.resolve_system_preset(system_config, type, system_name, config_substitution_rule, error))
return &system_config;
BOOST_LOG_TRIVIAL(warning) << boost::format("CLI: system preset '%1%' not resolved (%2%); the project keeps its values") % system_name % error;
return nullptr;
}, &keys);
for (const std::string &key : keys) {
const ConfigOption *opt = config.option(key);
const ConfigOption *old = m_print_config.option(key);
if (bookkeeping_keys.count(key) != 0 || opt == nullptr || (old != nullptr && *old == *opt))
continue;
BOOST_LOG_TRIVIAL(info) << boost::format("CLI: %1% from '%2%': %3% -> %4%") % key % system_name % (old ? old->serialize() : std::string("(missing)")) % opt->serialize();
m_print_config.set_key_value(key, opt->clone());
}
};
// The --uptodate path refreshes the project from its own system configs.
if (new_printer_name.empty() && load_machine_config.empty())
load_project_preset(current_printer_system_name, Preset::TYPE_PRINTER);
if (new_process_name.empty() && load_process_config.empty())
load_project_preset(current_process_system_name, Preset::TYPE_PRINT);
std::vector<std::string>& different_settings = m_print_config.option<ConfigOptionStrings>("different_settings_to_system", true)->values; std::vector<std::string>& different_settings = m_print_config.option<ConfigOptionStrings>("different_settings_to_system", true)->values;
std::vector<std::string>& inherits_group = m_print_config.option<ConfigOptionStrings>("inherits_group", true)->values; std::vector<std::string>& inherits_group = m_print_config.option<ConfigOptionStrings>("inherits_group", true)->values;
inherits_group.resize(filament_count + 2, std::string()); inherits_group.resize(filament_count + 2, std::string());
@@ -3476,6 +3518,14 @@ int CLI::run(int argc, char **argv)
} }
new_variant_counts = old_variant_counts; new_variant_counts = old_variant_counts;
//filament_variant_count = old_variant_counts; //filament_variant_count = old_variant_counts;
//ORCA: lay the per-variant options out one value per variant of the current filaments before each
// loaded filament replaces its own variants, including an option only a loaded filament
// defines, which otherwise starts as a single default value and never reaches the others.
for (const DynamicPrintConfig &config : load_filaments_config)
for (const std::string &opt_key : filament_options_with_variant)
if (opt_key != "filament_extruder_variant" && config.has(opt_key))
m_print_config.option(opt_key, true);
normalize_filament_values_to_variants(m_print_config);
for (int index = 0; index < load_filaments_config.size(); index++) { for (int index = 0; index < load_filaments_config.size(); index++) {
DynamicPrintConfig& config = load_filaments_config[index]; DynamicPrintConfig& config = load_filaments_config[index];
int filament_index = load_filaments_index[index]; int filament_index = load_filaments_index[index];
@@ -3647,6 +3697,25 @@ int CLI::run(int argc, char **argv)
} }
} }
//ORCA: a per-variant option the loaded filament does not define keeps the values of the
// variants the filament already had, and a variant new to it takes its first one's.
const int old_start = old_start_indice[filament_index - 1];
std::vector<int> kept_variant_indice = new_variant_indice;
for (int &i : kept_variant_indice)
if (i < 0)
i = old_start;
for (const std::string &opt_key : filament_options_with_variant) {
if (config.has(opt_key))
continue;
auto *opt_vec_dst = dynamic_cast<ConfigOptionVectorBase *>(m_print_config.option(opt_key));
if (opt_vec_dst == nullptr || opt_vec_dst->size() < size_t(old_start + old_variant_count))
continue;
// set_with_restore_2() pads its source in place
std::unique_ptr<ConfigOption> old_values(opt_vec_dst->clone());
opt_vec_dst->set_with_restore_2(static_cast<ConfigOptionVectorBase *>(old_values.get()), kept_variant_indice, old_start,
old_variant_count, true);
}
//update the old index //update the old index
if (old_variant_count != new_variant_count) if (old_variant_count != new_variant_count)
{ {
@@ -4081,6 +4150,9 @@ int CLI::run(int argc, char **argv)
if (printer_technology == ptFFF) { if (printer_technology == ptFFF) {
fff_print_config.apply(m_print_config, true); fff_print_config.apply(m_print_config, true);
m_print_config.apply(fff_print_config, true); m_print_config.apply(fff_print_config, true);
//ORCA: an option no preset or project defines has just come in as its single default value, and a
// command line override may hold one value per filament.
normalize_filament_values_to_variants(m_print_config);
} else { } else {
boost::nowide::cerr << "invalid printer_technology " << std::endl; boost::nowide::cerr << "invalid printer_technology " << std::endl;
record_exit_reson(outfile_dir, CLI_INVALID_PRINTER_TECH, 0, cli_errors[CLI_INVALID_PRINTER_TECH], sliced_info); record_exit_reson(outfile_dir, CLI_INVALID_PRINTER_TECH, 0, cli_errors[CLI_INVALID_PRINTER_TECH], sliced_info);
+4
View File
@@ -511,6 +511,10 @@ void AppConfig::set_defaults()
set_bool("enable_high_low_temp_mixed_printing", false); set_bool("enable_high_low_temp_mixed_printing", false);
} }
if (get("remember_print_action").empty()) {
set_bool("remember_print_action", false);
}
if (get("ignore_ext_filament_in_filament_map").empty()){ if (get("ignore_ext_filament_in_filament_map").empty()){
set_bool("ignore_ext_filament_in_filament_map", false); set_bool("ignore_ext_filament_in_filament_map", false);
} }
+1
View File
@@ -355,6 +355,7 @@ set(lisbslic3r_sources
Optimize/Optimizer.hpp Optimize/Optimizer.hpp
Orient.cpp Orient.cpp
Orient.hpp Orient.hpp
ParallelResolve.hpp
ParameterUtils.cpp ParameterUtils.cpp
ParameterUtils.hpp ParameterUtils.hpp
pchheader.cpp pchheader.cpp
+26 -13
View File
@@ -849,6 +849,19 @@ ConfigSubstitutions ConfigBase::load_from_json(const std::string &file, ForwardC
return std::move(substitutions_ctxt.substitutions); return std::move(substitutions_ctxt.substitutions);
} }
// Case-insensitive compare of a JSON key against a fixed ASCII one, without
// boost::iequals, whose std::locale() takes a lock the whole process shares in the
// MSVC runtime.
static bool ascii_iequals(const std::string &key, const char *literal)
{
auto lower = [](char c) { return (c >= 'A' && c <= 'Z') ? char(c - 'A' + 'a') : c; };
size_t i = 0;
for (; i < key.size() && literal[i] != '\0'; ++ i)
if (lower(key[i]) != lower(literal[i]))
return false;
return i == key.size() && literal[i] == '\0';
}
int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContext& substitution_context, bool load_inherits_to_config, std::map<std::string, std::string>& key_values, std::string& reason) int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContext& substitution_context, bool load_inherits_to_config, std::map<std::string, std::string>& key_values, std::string& reason)
{ {
json j; json j;
@@ -916,44 +929,44 @@ int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContex
} }
//parse the json elements //parse the json elements
for (auto it = j.begin(); it != j.end(); it++) { for (auto it = j.begin(); it != j.end(); it++) {
if (boost::iequals(it.key(),BBL_JSON_KEY_VERSION)) { if (ascii_iequals(it.key(), BBL_JSON_KEY_VERSION)) {
key_values.emplace(BBL_JSON_KEY_VERSION, it.value()); key_values.emplace(BBL_JSON_KEY_VERSION, it.value());
} }
else if (boost::iequals(it.key(), BBL_JSON_KEY_IS_CUSTOM)) { else if (ascii_iequals(it.key(), BBL_JSON_KEY_IS_CUSTOM)) {
//skip it //skip it
} }
else if (boost::iequals(it.key(), BBL_JSON_KEY_NAME)) { else if (ascii_iequals(it.key(), BBL_JSON_KEY_NAME)) {
key_values.emplace(BBL_JSON_KEY_NAME, it.value()); key_values.emplace(BBL_JSON_KEY_NAME, it.value());
if (it.value() == "project_settings") if (it.value() == "project_settings")
is_project_settings = true; is_project_settings = true;
} }
else if (boost::iequals(it.key(), BBL_JSON_KEY_URL)) { else if (ascii_iequals(it.key(), BBL_JSON_KEY_URL)) {
key_values.emplace(BBL_JSON_KEY_URL, it.value()); key_values.emplace(BBL_JSON_KEY_URL, it.value());
} }
else if (boost::iequals(it.key(), BBL_JSON_KEY_TYPE)) { else if (ascii_iequals(it.key(), BBL_JSON_KEY_TYPE)) {
key_values.emplace(BBL_JSON_KEY_TYPE, it.value()); key_values.emplace(BBL_JSON_KEY_TYPE, it.value());
} }
else if (boost::iequals(it.key(), BBL_JSON_KEY_SETTING_ID)) { else if (ascii_iequals(it.key(), BBL_JSON_KEY_SETTING_ID)) {
key_values.emplace(BBL_JSON_KEY_SETTING_ID, it.value()); key_values.emplace(BBL_JSON_KEY_SETTING_ID, it.value());
} }
else if (boost::iequals(it.key(), BBL_JSON_KEY_FILAMENT_ID)) { else if (ascii_iequals(it.key(), BBL_JSON_KEY_FILAMENT_ID)) {
key_values.emplace(BBL_JSON_KEY_FILAMENT_ID, it.value()); key_values.emplace(BBL_JSON_KEY_FILAMENT_ID, it.value());
} }
else if (boost::iequals(it.key(), BBL_JSON_KEY_FROM)) { else if (ascii_iequals(it.key(), BBL_JSON_KEY_FROM)) {
key_values.emplace(BBL_JSON_KEY_FROM, it.value()); key_values.emplace(BBL_JSON_KEY_FROM, it.value());
} }
else if (boost::iequals(it.key(), BBL_JSON_KEY_DESCRIPTION)) { else if (ascii_iequals(it.key(), BBL_JSON_KEY_DESCRIPTION)) {
key_values.emplace(BBL_JSON_KEY_DESCRIPTION, it.value()); key_values.emplace(BBL_JSON_KEY_DESCRIPTION, it.value());
} }
else if (boost::iequals(it.key(), BBL_JSON_KEY_INSTANTIATION)) { else if (ascii_iequals(it.key(), BBL_JSON_KEY_INSTANTIATION)) {
key_values.emplace(BBL_JSON_KEY_INSTANTIATION, it.value()); key_values.emplace(BBL_JSON_KEY_INSTANTIATION, it.value());
} }
else if (!load_inherits_to_config && boost::iequals(it.key(), BBL_JSON_KEY_INHERITS)) { else if (!load_inherits_to_config && ascii_iequals(it.key(), BBL_JSON_KEY_INHERITS)) {
key_values.emplace(BBL_JSON_KEY_INHERITS, it.value()); key_values.emplace(BBL_JSON_KEY_INHERITS, it.value());
} }
else if (!load_inherits_to_config && boost::iequals(it.key(), BBL_JSON_KEY_INCLUDES)) { else if (!load_inherits_to_config && ascii_iequals(it.key(), BBL_JSON_KEY_INCLUDES)) {
key_values.emplace(BBL_JSON_KEY_INCLUDES, it.value().dump()); key_values.emplace(BBL_JSON_KEY_INCLUDES, it.value().dump());
} else if (boost::iequals(it.key(), ORCA_JSON_KEY_RENAMED_FROM)) { } else if (ascii_iequals(it.key(), ORCA_JSON_KEY_RENAMED_FROM)) {
key_values.emplace(ORCA_JSON_KEY_RENAMED_FROM, it.value()); key_values.emplace(ORCA_JSON_KEY_RENAMED_FROM, it.value());
} else { } else {
t_config_option_key opt_key = it.key(); t_config_option_key opt_key = it.key();
+54 -57
View File
@@ -5,6 +5,7 @@
#include <cmath> #include <cmath>
#include <algorithm> #include <algorithm>
#include <iostream> #include <iostream>
#include <limits>
#include "FillBase.hpp" #include "FillBase.hpp"
#include "FillGyroid.hpp" #include "FillGyroid.hpp"
@@ -123,44 +124,41 @@ static inline double f(double x, double z_sin, double z_cos, bool vertical, bool
} }
} }
// Repeats one period of a wave from the last sample at or before x_min to the first one at or after x_max.
static inline Polyline make_wave( static inline Polyline make_wave(
const std::vector<Vec2d>& one_period, double width, double height, double offset, double scaleFactor, const std::vector<Vec2d>& one_period, double x_min, double x_max, double offset, double scaleFactor, bool vertical)
double z_cos, double z_sin, bool vertical, bool flip)
{ {
std::vector<Vec2d> points = one_period; const double period = one_period.back().x();
double period = points.back()(0); // The last sample of a period is the first one of the next.
if (width != period) // do not extend if already truncated const size_t n = one_period.size() - 1;
{ double x0 = std::floor(x_min / period) * period;
points.reserve(one_period.size() * size_t(floor(width / period))); size_t i = 0;
points.pop_back(); while (i + 1 < n && x0 + one_period[i + 1].x() <= x_min)
++i;
size_t n = points.size();
do {
points.emplace_back(points[points.size()-n].x() + period, points[points.size()-n].y());
} while (points.back()(0) < width - EPSILON);
points.emplace_back(Vec2d(width, f(width, z_sin, z_cos, vertical, flip)));
}
// and construct the final polyline to return:
Polyline polyline; Polyline polyline;
polyline.points.reserve(points.size()); polyline.points.reserve(size_t((x_max - x0) / period + 1.) * n + 1);
for (auto& point : points) { for (;;) {
point(1) += offset; Vec2d point(x0 + one_period[i].x(), one_period[i].y() + offset);
point(1) = std::clamp(double(point.y()), 0., height); const bool last = point.x() >= x_max;
if (vertical) if (vertical)
std::swap(point(0), point(1)); std::swap(point(0), point(1));
polyline.points.emplace_back((point * scaleFactor).cast<coord_t>()); polyline.points.emplace_back((point * scaleFactor).cast<coord_t>());
if (last)
break;
if (++i == n) {
i = 0;
x0 += period;
}
} }
return polyline; return polyline;
} }
static std::vector<Vec2d> make_one_period(double width, double scaleFactor, double z_cos, double z_sin, bool vertical, bool flip, double tolerance) static std::vector<Vec2d> make_one_period(double scaleFactor, double z_cos, double z_sin, bool vertical, bool flip, double tolerance)
{ {
std::vector<Vec2d> points; std::vector<Vec2d> points;
double dx = M_PI_2; // exact coordinates on main inflexion lobes double dx = M_PI_2; // exact coordinates on main inflexion lobes
double limit = std::min(2*M_PI, width); double limit = 2*M_PI;
points.reserve(coord_t(ceil(limit / tolerance / 3))); points.reserve(coord_t(ceil(limit / tolerance / 3)));
for (double x = 0.; x < limit - EPSILON; x += dx) { for (double x = 0.; x < limit - EPSILON; x += dx) {
@@ -239,7 +237,8 @@ static inline double compute_omega_factor(double density_adjusted, double line_s
return std::clamp(raw, 1.0, 2.0); return std::clamp(raw, 1.0, 2.0);
} }
static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double line_spacing, double width, double height) // Waves covering bbox, with the pattern anchored at origin.
static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double line_spacing, const BoundingBox &bbox, const Point &origin)
{ {
const double scaleFactor = scale_(line_spacing) / density_adjusted; const double scaleFactor = scale_(line_spacing) / density_adjusted;
@@ -254,29 +253,36 @@ static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double
const double z_cos = cos(z); const double z_cos = cos(z);
bool vertical = (std::abs(z_sin) <= std::abs(z_cos)); bool vertical = (std::abs(z_sin) <= std::abs(z_cos));
// Range to cover in pattern units, with the waves running along x.
Vec2d lo = (bbox.min - origin).cast<double>() / scaleFactor;
Vec2d hi = (bbox.max - origin).cast<double>() / scaleFactor;
double lower_bound = 0.; double lower_bound = 0.;
double upper_bound = height;
bool flip = true; bool flip = true;
if (vertical) { if (vertical) {
flip = false; flip = false;
lower_bound = -M_PI; lower_bound = -M_PI;
upper_bound = width - M_PI_2; std::swap(lo(0), lo(1));
std::swap(width,height); std::swap(hi(0), hi(1));
} }
std::vector<Vec2d> one_period_odd = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip, tolerance); // creates one period of the waves, so it doesn't have to be recalculated all the time std::vector<Vec2d> one_period_odd = make_one_period(scaleFactor, z_cos, z_sin, vertical, flip, tolerance); // creates one period of the waves, so it doesn't have to be recalculated all the time
flip = !flip; // even polylines are a bit shifted flip = !flip; // even polylines are a bit shifted
std::vector<Vec2d> one_period_even = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip, tolerance); std::vector<Vec2d> one_period_even = make_one_period(scaleFactor, z_cos, z_sin, vertical, flip, tolerance);
Polylines result;
for (double y0 = lower_bound; y0 < upper_bound + EPSILON; y0 += M_PI) { // Every wave spans [offset + f_min, offset + f_max] across.
// creates odd polylines double f_min = std::numeric_limits<double>::max();
result.emplace_back(make_wave(one_period_odd, width, height, y0, scaleFactor, z_cos, z_sin, vertical, flip)); double f_max = std::numeric_limits<double>::lowest();
// creates even polylines for (const std::vector<Vec2d> *one_period : { &one_period_odd, &one_period_even })
y0 += M_PI; for (const Vec2d &point : *one_period) {
if (y0 < upper_bound + EPSILON) { f_min = std::min(f_min, point.y());
result.emplace_back(make_wave(one_period_even, width, height, y0, scaleFactor, z_cos, z_sin, vertical, flip)); f_max = std::max(f_max, point.y());
} }
Polylines result;
for (int i = int(std::ceil((lo.y() - f_max - lower_bound) / M_PI)); lower_bound + i * M_PI + f_min <= hi.y(); ++i) {
Polyline &wave = result.emplace_back(make_wave(i % 2 == 0 ? one_period_odd : one_period_even, lo.x(), hi.x(),
lower_bound + i * M_PI, scaleFactor, vertical));
wave.translate(origin);
} }
return result; return result;
@@ -302,12 +308,12 @@ void FillGyroid::_fill_surface_single(
// Distance between the gyroid waves in scaled coordinates. // Distance between the gyroid waves in scaled coordinates.
coord_t distance = coord_t(scale_(this->spacing) / density_adjusted); coord_t distance = coord_t(scale_(this->spacing) / density_adjusted);
// align bounding box to a multiple of our grid module // Anchor the pattern to our grid module; the 10-line shift keeps its established phase.
bb.merge(align_to_grid(bb.min, Point(2*M_PI*distance, 2*M_PI*distance))); const coord_t shift = coord_t(10 * scale_(this->spacing));
const Point origin = align_to_grid(bb.min, Point(2*M_PI*distance, 2*M_PI*distance)) - Point(shift, shift);
// Expand the bounding box to avoid artifacts at the edges // Keep the pattern ends and the multiline copies outside the contour.
coord_t expand = 10 * (scale_(this->spacing)); bb.offset(scale_(this->spacing * params.multiline));
bb.offset(expand);
// generate pattern // generate pattern
Polylines polylines; Polylines polylines;
@@ -327,23 +333,14 @@ void FillGyroid::_fill_surface_single(
const float density_factor = std::max(0.001f, float(params.density * DensityAdjust / params.multiline)); const float density_factor = std::max(0.001f, float(params.density * DensityAdjust / params.multiline));
const float period = float(2.0 * M_PI) * float(this->spacing) / density_factor; const float period = float(2.0 * M_PI) * float(this->spacing) / density_factor;
// bb is already expanded above by 10 * scale_(spacing) for edge artifacts; // A cell of margin for the rings closed along the raster border, and a fixed sampling grid for every region.
// skip a second offset here to avoid raster-area bloat in the marching squares pass. const coord_t cell = scaled(marchsq::GyroidField::gsizef);
bb.offset(cell);
bb.merge(align_to_grid(bb.min, Point(cell, cell)));
marchsq::GyroidField sf(bb, this->z, period, float(omega)); marchsq::GyroidField sf(bb, this->z, period, float(omega));
polylines = marchsq::get_gyroid_polylines(sf, SCALED_SPARSE_INFILL_RESOLUTION); polylines = marchsq::get_gyroid_polylines(sf, SCALED_SPARSE_INFILL_RESOLUTION);
} else { } else {
polylines = make_gyroid_waves( polylines = make_gyroid_waves(scale_(this->z), density_adjusted, this->spacing, bb, origin);
scale_(this->z),
density_adjusted,
this->spacing,
ceil(bb.size()(0) / distance) + 1.,
ceil(bb.size()(1) / distance) + 1.);
// The parametric generator produces wave coords relative to the grid origin;
// shift them into absolute layer coords. The marching-squares branch above
// already emits absolute coords via GyroidField::to_Point, so it skips this.
for (Polyline &pl : polylines)
pl.translate(bb.min);
} }
// Apply multiline offset if needed // Apply multiline offset if needed
+12 -8
View File
@@ -141,7 +141,7 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s
its.indices.emplace_back(indices[0], indices[1], indices[2]); its.indices.emplace_back(indices[0], indices[1], indices[2]);
int face_index =its.indices.size() - 1; int face_index =its.indices.size() - 1;
RGBA face_color; RGBA face_color;
auto set_face_color = [&uvs, &data, &mtl_data, &obj_info, &face_color](int face_index, const std::string mtl_name) { auto set_face_color = [&uvs, &data, &mtl_data, &obj_info, &face_color](int face_index, const std::string mtl_name, const std::array<int, 3> &corners) {
if (mtl_data.new_mtl_unmap.find(mtl_name) != mtl_data.new_mtl_unmap.end()) { if (mtl_data.new_mtl_unmap.find(mtl_name) != mtl_data.new_mtl_unmap.end()) {
bool is_merge_ka_kd = true; bool is_merge_ka_kd = true;
for (size_t n = 0; n < 3; n++) { for (size_t n = 0; n < 3; n++) {
@@ -174,7 +174,7 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s
return Vec2f::Zero(); return Vec2f::Zero();
return Vec2f(data.textureCoordinates[idx * OBJ_TEXCOORD_LENGTH], data.textureCoordinates[idx * OBJ_TEXCOORD_LENGTH + 1]); return Vec2f(data.textureCoordinates[idx * OBJ_TEXCOORD_LENGTH], data.textureCoordinates[idx * OBJ_TEXCOORD_LENGTH + 1]);
}; };
std::array<Vec2f, 3> uv_array{uv_at(uvs[0]), uv_at(uvs[1]), uv_at(uvs[2])}; std::array<Vec2f, 3> uv_array{uv_at(uvs[corners[0]]), uv_at(uvs[corners[1]]), uv_at(uvs[corners[2]])};
obj_info.uvs.emplace_back(uv_array); obj_info.uvs.emplace_back(uv_array);
} }
obj_info.face_colors.emplace_back(face_color); obj_info.face_colors.emplace_back(face_color);
@@ -185,27 +185,27 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s
} }
} }
}; };
auto set_face_color_by_mtl = [&data, &set_face_color](int face_index) { auto set_face_color_by_mtl = [&data, &set_face_color](int face_index, const std::array<int, 3> &corners) {
if (data.usemtls.size() == 1) { if (data.usemtls.size() == 1) {
set_face_color(face_index, data.usemtls[0].name); set_face_color(face_index, data.usemtls[0].name, corners);
} else { } else {
for (size_t k = 0; k < data.usemtls.size(); k++) { for (size_t k = 0; k < data.usemtls.size(); k++) {
auto mtl = data.usemtls[k]; auto mtl = data.usemtls[k];
if (face_index >= mtl.face_start && face_index <= mtl.face_end) { if (face_index >= mtl.face_start && face_index <= mtl.face_end) {
set_face_color(face_index, data.usemtls[k].name); set_face_color(face_index, data.usemtls[k].name, corners);
break; break;
} }
} }
} }
}; };
if (exist_mtl) { if (exist_mtl) {
set_face_color_by_mtl(face_index); set_face_color_by_mtl(face_index, {0, 1, 2});
} }
if (cnt == 4) { if (cnt == 4) {
its.indices.emplace_back(indices[0], indices[2], indices[3]); its.indices.emplace_back(indices[0], indices[2], indices[3]);
int face_index = its.indices.size() - 1; int face_index = its.indices.size() - 1;
if (exist_mtl) { if (exist_mtl) {
set_face_color_by_mtl(face_index); set_face_color_by_mtl(face_index, {0, 2, 3});
} }
} }
} }
@@ -217,8 +217,12 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s
message = _L("This OBJ file couldn't be read because it's empty."); message = _L("This OBJ file couldn't be read because it's empty.");
return false; return false;
} }
if (meshptr->volume() < 0) if (meshptr->volume() < 0) {
meshptr->flip_triangles(); meshptr->flip_triangles();
// Flipping swaps corners 1 and 2 of every face, so the UVs have to follow.
for (std::array<Vec2f, 3> &uv : obj_info.uvs)
std::swap(uv[1], uv[2]);
}
// Hand the parsed material table back so callers can build a TexturedMesh from it. // Hand the parsed material table back so callers can build a TexturedMesh from it.
if (out_mtl) if (out_mtl)
*out_mtl = mtl_data; *out_mtl = mtl_data;
+70 -88
View File
@@ -1497,12 +1497,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
check_add_eol(toolchange_gcode_str); check_add_eol(toolchange_gcode_str);
// SoftFever: set new PA for new filament // SoftFever: set new PA for new filament
if (gcodegen.config().enable_pressure_advance.get_at(new_filament_id)) { gcode += gcodegen.set_filament_pressure_advance(new_filament_id);
gcode += gcodegen.writer().set_pressure_advance(gcodegen.config().pressure_advance.get_at(new_filament_id));
// Orca: Adaptive PA
// Reset Adaptive PA processor last PA value
gcodegen.m_pa_processor->resetPreviousPA(gcodegen.config().pressure_advance.get_at(new_filament_id));
}
// A phony move to the end position at the wipe tower. // A phony move to the end position at the wipe tower.
gcodegen.writer().travel_to_xy((end_pos + plate_origin_2d).cast<double>()); gcodegen.writer().travel_to_xy((end_pos + plate_origin_2d).cast<double>());
@@ -1826,12 +1821,8 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
check_add_eol(toolchange_gcode_str); check_add_eol(toolchange_gcode_str);
// SoftFever: set new PA for new filament // SoftFever: set new PA for new filament
if (new_extruder_id != -1 && gcodegen.config().enable_pressure_advance.get_at(new_extruder_id)) { if (new_extruder_id != -1)
gcode += gcodegen.writer().set_pressure_advance(gcodegen.config().pressure_advance.get_at(new_extruder_id)); gcode += gcodegen.set_filament_pressure_advance(new_extruder_id);
// Orca: Adaptive PA
// Reset Adaptive PA processor last PA value
gcodegen.m_pa_processor->resetPreviousPA(gcodegen.config().pressure_advance.get_at(new_extruder_id));
}
// A phony move to the end position at the wipe tower. // A phony move to the end position at the wipe tower.
gcodegen.writer().travel_to_xy((end_pos + plate_origin_2d).cast<double>()); gcodegen.writer().travel_to_xy((end_pos + plate_origin_2d).cast<double>());
@@ -3232,7 +3223,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
m_cooling_buffer->set_current_extruder(initial_extruder_id, extruder_id); m_cooling_buffer->set_current_extruder(initial_extruder_id, extruder_id);
// Orca: Initialise AdaptivePA processor filter // Orca: Initialise AdaptivePA processor filter
m_pa_processor = std::make_unique<AdaptivePAProcessor>(*this, tool_ordering.all_extruders()); m_pa_processor = std::make_unique<AdaptivePAProcessor>(*this);
// Update output variables after the extruders were initialized. // Update output variables after the extruders were initialized.
m_placeholder_parser_integration.init(m_writer); m_placeholder_parser_integration.init(m_writer);
@@ -3797,12 +3788,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
} }
} }
// Orca: add missing PA settings for initial filament // Orca: add missing PA settings for initial filament
if (m_config.enable_pressure_advance.get_at(initial_non_support_extruder_id)) { file.write(set_filament_pressure_advance(initial_non_support_extruder_id));
file.write(m_writer.set_pressure_advance(m_config.pressure_advance.get_at(initial_non_support_extruder_id)));
// Orca: Adaptive PA
// Reset Adaptive PA processor last PA value
m_pa_processor->resetPreviousPA(m_config.pressure_advance.get_at(initial_non_support_extruder_id));
}
} }
//flush FanMover buffer to avoid modifying the start gcode if it's manual. //flush FanMover buffer to avoid modifying the start gcode if it's manual.
@@ -4188,6 +4174,28 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
print.throw_if_canceled(); print.throw_if_canceled();
// Some firmwares only scan the last N lines for the time estimate, so the stats are written
// after the config block.
if (!is_bbl_printers && !skip_config_block) {
file.write("; CONFIG_BLOCK_START\n");
std::string full_config;
append_full_config(print, full_config);
if (!full_config.empty())
file.write(full_config);
// SoftFever: write compatiple info
int first_layer_bed_temperature = get_bed_temperature(0, true, print.config().curr_bed_type);
file.write_format("; first_layer_bed_temperature = %d\n", first_layer_bed_temperature);
file.write_format("; bed_shape = %s\n", print.full_print_config().opt_serialize("printable_area").c_str());
file.write_format("; first_layer_temperature = %d\n", print.config().nozzle_temperature_initial_layer.get_at(0));
file.write_format("; first_layer_height = %.3f\n", print.config().initial_layer_print_height.value);
//SF TODO
// file.write_format("; variable_layer_height = %d\n", print.ad.adaptive_layer_height ? 1 : 0);
file.write("; CONFIG_BLOCK_END\n\n");
}
// Get filament stats. // Get filament stats.
file.write(DoExport::update_print_stats_and_format_filament_stats( file.write(DoExport::update_print_stats_and_format_filament_stats(
// Const inputs // Const inputs
@@ -4214,26 +4222,6 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder) GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder)
.c_str()); .c_str());
file.write("\n"); file.write("\n");
if (!skip_config_block) {
file.write("; CONFIG_BLOCK_START\n");
std::string full_config;
append_full_config(print, full_config);
if (!full_config.empty())
file.write(full_config);
// SoftFever: write compatiple info
int first_layer_bed_temperature = get_bed_temperature(0, true, print.config().curr_bed_type);
file.write_format("; first_layer_bed_temperature = %d\n", first_layer_bed_temperature);
file.write_format("; bed_shape = %s\n", print.full_print_config().opt_serialize("printable_area").c_str());
file.write_format("; first_layer_temperature = %d\n", print.config().nozzle_temperature_initial_layer.get_at(0));
file.write_format("; first_layer_height = %.3f\n", print.config().initial_layer_print_height.value);
//SF TODO
// file.write_format("; variable_layer_height = %d\n", print.ad.adaptive_layer_height ? 1 : 0);
file.write("; CONFIG_BLOCK_END\n\n");
} // !skip_config_block
} }
file.write("\n"); file.write("\n");
@@ -8390,12 +8378,16 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// Orca: Dynamic PA // Orca: Dynamic PA
// If adaptive PA is enabled, by default evaluate PA on all extrusion moves // If adaptive PA is enabled, by default evaluate PA on all extrusion moves
// The PA settings and the PA_CHANGE tags use the filament config index of the filament's extruder variant
const unsigned int pa_config_index = (unsigned int) get_filament_config_index(m_writer.filament()->id());
const bool adaptive_pa = m_config.adaptive_pressure_advance.get_at(pa_config_index) && m_config.enable_pressure_advance.get_at(pa_config_index);
const bool adaptive_pa_overhangs = adaptive_pa && m_config.adaptive_pressure_advance_overhangs.get_at(pa_config_index);
bool is_pa_calib = m_curr_print->calib_mode() == CalibMode::Calib_PA_Line || bool is_pa_calib = m_curr_print->calib_mode() == CalibMode::Calib_PA_Line ||
m_curr_print->calib_mode() == CalibMode::Calib_PA_Pattern || m_curr_print->calib_mode() == CalibMode::Calib_PA_Pattern ||
m_curr_print->calib_mode() == CalibMode::Calib_PA_Tower; m_curr_print->calib_mode() == CalibMode::Calib_PA_Tower;
bool evaluate_adaptive_pa = false; bool evaluate_adaptive_pa = false;
bool role_change = (m_last_extrusion_role != path.role()); bool role_change = (m_last_extrusion_role != path.role());
if (!is_pa_calib && FILAMENT_CONFIG(adaptive_pressure_advance) && FILAMENT_CONFIG(enable_pressure_advance)) { if (!is_pa_calib && adaptive_pa) {
evaluate_adaptive_pa = true; evaluate_adaptive_pa = true;
// If we have already emmited a PA change because the m_multi_flow_segment_path_pa_set is set // If we have already emmited a PA change because the m_multi_flow_segment_path_pa_set is set
// skip re-issuing the PA change tag. // skip re-issuing the PA change tag.
@@ -8478,7 +8470,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// Post processor flag generation code segment when option to emit only at role changes is enabled // Post processor flag generation code segment when option to emit only at role changes is enabled
// Variables published to the post processor: // Variables published to the post processor:
// 1) Tag to trigger a PA evaluation (because a role change was identified and the user has requested dynamic PA adjustments) // 1) Tag to trigger a PA evaluation (because a role change was identified and the user has requested dynamic PA adjustments)
// 2) Current extruder ID (to identify the PA model for the currently used extruder) // 2) Current filament config index (to identify the PA model for the currently used filament and its extruder variant)
// 3) mm3_per_mm value (to then multiply by the final model print speed after slowdown for cooling is applied) // 3) mm3_per_mm value (to then multiply by the final model print speed after slowdown for cooling is applied)
// 4) the current acceleration (to pass to the model for evaluation) // 4) the current acceleration (to pass to the model for evaluation)
// 5) whether this is an external perimeter (for future use) // 5) whether this is an external perimeter (for future use)
@@ -8489,7 +8481,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
if (m_multi_flow_segment_path_average_mm3_per_mm > 0) { if (m_multi_flow_segment_path_average_mm3_per_mm > 0) {
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n", sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
m_writer.filament()->id(), pa_config_index,
m_multi_flow_segment_path_average_mm3_per_mm, m_multi_flow_segment_path_average_mm3_per_mm,
acceleration_i, acceleration_i,
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)), ((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
@@ -8502,7 +8494,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// to issue a zero flow PA change command for this // to issue a zero flow PA change command for this
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n", sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
m_writer.filament()->id(), pa_config_index,
_mm3_per_mm, _mm3_per_mm,
acceleration_i, acceleration_i,
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)), ((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
@@ -8610,11 +8602,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// or a flow change, so emit the flag to evaluate PA for the upcomming extrusion // or a flow change, so emit the flag to evaluate PA for the upcomming extrusion
// Emit tag before new speed is set so the post processor reads the next speed immediately and uses it. // Emit tag before new speed is set so the post processor reads the next speed immediately and uses it.
// Dont emit tag if it has just already been emitted from a role change above // Dont emit tag if it has just already been emitted from a role change above
if(_mm3_per_mm >0 && if(_mm3_per_mm >0 && adaptive_pa_overhangs && !evaluate_adaptive_pa){
FILAMENT_CONFIG(adaptive_pressure_advance) &&
FILAMENT_CONFIG(enable_pressure_advance) &&
FILAMENT_CONFIG(adaptive_pressure_advance_overhangs) &&
!evaluate_adaptive_pa){
if(writer().get_current_speed() > F){ // Ramping down speed - use overhang logic where the minimum speed is used between current and upcoming extrusion if(writer().get_current_speed() > F){ // Ramping down speed - use overhang logic where the minimum speed is used between current and upcoming extrusion
if(m_config.gcode_comments){ if(m_config.gcode_comments){
sprintf(buf, "; Ramp down-non-variable\n"); sprintf(buf, "; Ramp down-non-variable\n");
@@ -8622,7 +8610,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
} }
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n", sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
m_writer.filament()->id(), pa_config_index,
_mm3_per_mm, _mm3_per_mm,
acceleration_i, acceleration_i,
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)), ((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
@@ -8637,7 +8625,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
} }
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n", sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
m_writer.filament()->id(), pa_config_index,
_mm3_per_mm, _mm3_per_mm,
acceleration_i, acceleration_i,
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)), ((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
@@ -8843,10 +8831,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// ORCA: Adaptive PA code segment when adjusting PA within the same feature // ORCA: Adaptive PA code segment when adjusting PA within the same feature
// There is a speed change or flow change so emit the flag to evaluate PA for the upcomming extrusion // There is a speed change or flow change so emit the flag to evaluate PA for the upcomming extrusion
// Emit tag before new speed is set so the post processor reads the next speed immediately and uses it. // Emit tag before new speed is set so the post processor reads the next speed immediately and uses it.
if(_mm3_per_mm >0 && if(_mm3_per_mm >0 && adaptive_pa_overhangs){
EXTRUDER_CONFIG(adaptive_pressure_advance) &&
EXTRUDER_CONFIG(enable_pressure_advance) &&
EXTRUDER_CONFIG(adaptive_pressure_advance_overhangs) ){
if(last_set_speed > new_speed){ // Ramping down speed - use overhang logic where the minimum speed is used between current and upcoming extrusion if(last_set_speed > new_speed){ // Ramping down speed - use overhang logic where the minimum speed is used between current and upcoming extrusion
if(m_config.gcode_comments) { if(m_config.gcode_comments) {
sprintf(buf, "; Ramp up-variable\n"); sprintf(buf, "; Ramp up-variable\n");
@@ -8854,7 +8839,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
} }
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n", sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
m_writer.filament()->id(), pa_config_index,
_mm3_per_mm, _mm3_per_mm,
acceleration_i, acceleration_i,
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)), ((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
@@ -8869,7 +8854,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
} }
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n", sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
m_writer.filament()->id(), pa_config_index,
_mm3_per_mm, _mm3_per_mm,
acceleration_i, acceleration_i,
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)), ((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
@@ -9422,33 +9407,29 @@ void GCode::update_placeholder_parser_with_variant_params()
if (num_filaments == 0) if (num_filaments == 0)
return; return;
// Helpers: remap config arrays from variant index space to filament_id index space. // Helper: remap config arrays from variant index space to filament_id index space.
// After remapping, gcode templates can use param[filament_id] directly. // After remapping, gcode templates can use param[filament_id] directly.
auto remap_floats_by_filament = [&](const auto &src) { auto remap_by_filament = [&](const auto &src) {
std::vector<double> dst(num_filaments); std::decay_t<decltype(src.values)> dst(num_filaments);
for (size_t i = 0; i < num_filaments; ++i)
dst[i] = src.get_at(get_filament_config_index(i));
return dst;
};
auto remap_ints_by_filament = [&](const auto &src) {
std::vector<int> dst(num_filaments);
for (size_t i = 0; i < num_filaments; ++i) for (size_t i = 0; i < num_filaments; ++i)
dst[i] = src.get_at(get_filament_config_index(i)); dst[i] = src.get_at(get_filament_config_index(i));
return dst; return dst;
}; };
// --- filament_options_with_variant: gcode indexes by filament_id --- // --- filament_options_with_variant: gcode indexes by filament_id ---
this->placeholder_parser().set("filament_max_volumetric_speed", new ConfigOptionFloats(remap_floats_by_filament(m_config.filament_max_volumetric_speed))); this->placeholder_parser().set("filament_max_volumetric_speed", new ConfigOptionFloats(remap_by_filament(m_config.filament_max_volumetric_speed)));
this->placeholder_parser().set("filament_pre_cooling_temperature", new ConfigOptionInts(remap_ints_by_filament(m_config.filament_pre_cooling_temperature))); this->placeholder_parser().set("filament_pre_cooling_temperature", new ConfigOptionInts(remap_by_filament(m_config.filament_pre_cooling_temperature)));
this->placeholder_parser().set("filament_pre_cooling_temperature_nc", new ConfigOptionInts(remap_ints_by_filament(m_config.filament_pre_cooling_temperature_nc))); this->placeholder_parser().set("filament_pre_cooling_temperature_nc", new ConfigOptionInts(remap_by_filament(m_config.filament_pre_cooling_temperature_nc)));
this->placeholder_parser().set("filament_cooling_before_tower", new ConfigOptionFloats(remap_floats_by_filament(m_config.filament_cooling_before_tower))); this->placeholder_parser().set("filament_cooling_before_tower", new ConfigOptionFloats(remap_by_filament(m_config.filament_cooling_before_tower)));
this->placeholder_parser().set("nozzle_temperature_initial_layer", new ConfigOptionInts(remap_ints_by_filament(m_config.nozzle_temperature_initial_layer))); this->placeholder_parser().set("nozzle_temperature_initial_layer", new ConfigOptionInts(remap_by_filament(m_config.nozzle_temperature_initial_layer)));
this->placeholder_parser().set("nozzle_temperature", new ConfigOptionInts(remap_ints_by_filament(m_config.nozzle_temperature))); this->placeholder_parser().set("nozzle_temperature", new ConfigOptionInts(remap_by_filament(m_config.nozzle_temperature)));
// first_layer_temperature is a legacy alias of nozzle_temperature_initial_layer // first_layer_temperature is a legacy alias of nozzle_temperature_initial_layer
this->placeholder_parser().set("first_layer_temperature", new ConfigOptionInts(remap_ints_by_filament(m_config.nozzle_temperature_initial_layer))); this->placeholder_parser().set("first_layer_temperature", new ConfigOptionInts(remap_by_filament(m_config.nozzle_temperature_initial_layer)));
this->placeholder_parser().set("pressure_advance", new ConfigOptionFloats(remap_by_filament(m_config.pressure_advance)));
this->placeholder_parser().set("enable_pressure_advance", new ConfigOptionBools(remap_by_filament(m_config.enable_pressure_advance)));
// --- printer_options_with_variant_1: in m_config these are already merged as filament-indexed --- // --- printer_options_with_variant_1: in m_config these are already merged as filament-indexed ---
this->placeholder_parser().set("retraction_distances_when_cut", new ConfigOptionFloats(remap_floats_by_filament(m_config.retraction_distances_when_cut))); this->placeholder_parser().set("retraction_distances_when_cut", new ConfigOptionFloats(remap_by_filament(m_config.retraction_distances_when_cut)));
// hotend_cooling_rate / hotend_heating_rate: gcode uses [filament_map[x]-1] (extruder_id), no remap needed // hotend_cooling_rate / hotend_heating_rate: gcode uses [filament_map[x]-1] (extruder_id), no remap needed
// --- filament_map: per-layer dynamic, sync from m_config to placeholder_parser --- // --- filament_map: per-layer dynamic, sync from m_config to placeholder_parser ---
@@ -9458,9 +9439,9 @@ void GCode::update_placeholder_parser_with_variant_params()
{ {
// Fast purge mode uses filament_flush_temp_fast; Default is inert. // Fast purge mode uses filament_flush_temp_fast; Default is inert.
bool use_fast_flush = m_config.prime_volume_mode == PrimeVolumeMode::pvmFast; bool use_fast_flush = m_config.prime_volume_mode == PrimeVolumeMode::pvmFast;
auto flush_v_speed = remap_floats_by_filament(m_config.filament_flush_volumetric_speed); auto flush_v_speed = remap_by_filament(m_config.filament_flush_volumetric_speed);
auto filament_max_v = remap_floats_by_filament(m_config.filament_max_volumetric_speed); auto filament_max_v = remap_by_filament(m_config.filament_max_volumetric_speed);
auto flush_temps = remap_ints_by_filament(use_fast_flush ? m_config.filament_flush_temp_fast auto flush_temps = remap_by_filament(use_fast_flush ? m_config.filament_flush_temp_fast
: m_config.filament_flush_temp); : m_config.filament_flush_temp);
for (size_t i = 0; i < num_filaments; ++i) { for (size_t i = 0; i < num_filaments; ++i) {
if (flush_v_speed[i] == 0) if (flush_v_speed[i] == 0)
@@ -9513,12 +9494,7 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
gcode += this->placeholder_parser_process("filament_start_gcode", filament_start_gcode, new_filament_id, &config); gcode += this->placeholder_parser_process("filament_start_gcode", filament_start_gcode, new_filament_id, &config);
check_add_eol(gcode); check_add_eol(gcode);
} }
if (m_config.enable_pressure_advance.get_at(new_filament_id)) { gcode += set_filament_pressure_advance(new_filament_id);
gcode += m_writer.set_pressure_advance(m_config.pressure_advance.get_at(new_filament_id));
// Orca: Adaptive PA
// Reset Adaptive PA processor last PA value
m_pa_processor->resetPreviousPA(m_config.pressure_advance.get_at(new_filament_id));
}
gcode += m_writer.toolchange(new_filament_id, new_extruder_id); gcode += m_writer.toolchange(new_filament_id, new_extruder_id);
if (Extruder *fil = m_writer.filament()) if (Extruder *fil = m_writer.filament())
@@ -9909,18 +9885,24 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
if (m_ooze_prevention.enable && !defer_temp_wait) if (m_ooze_prevention.enable && !defer_temp_wait)
gcode += m_ooze_prevention.post_toolchange(*this); gcode += m_ooze_prevention.post_toolchange(*this);
if (m_config.enable_pressure_advance.get_at(new_filament_id)) { gcode += set_filament_pressure_advance(new_filament_id);
gcode += m_writer.set_pressure_advance(m_config.pressure_advance.get_at(new_filament_id));
// Orca: Adaptive PA
// Reset Adaptive PA processor last PA value
m_pa_processor->resetPreviousPA(m_config.pressure_advance.get_at(new_filament_id));
}
//Orca: tool changer or IDEX's firmware may change Z position, so we set it to unknown/undefined //Orca: tool changer or IDEX's firmware may change Z position, so we set it to unknown/undefined
m_last_pos_defined = false; m_last_pos_defined = false;
return gcode; return gcode;
} }
std::string GCode::set_filament_pressure_advance(unsigned int filament_id)
{
const size_t fi = get_filament_config_index(filament_id);
if (!m_config.enable_pressure_advance.get_at(fi))
return {};
// Orca: Adaptive PA
// Reset Adaptive PA processor last PA value
m_pa_processor->resetPreviousPA(m_config.pressure_advance.get_at(fi));
return m_writer.set_pressure_advance(m_config.pressure_advance.get_at(fi));
}
inline std::string polygon_to_string(const Polygon &polygon, Print *print, bool is_print_space = false) { inline std::string polygon_to_string(const Polygon &polygon, Print *print, bool is_print_space = false) {
std::ostringstream gcode; std::ostringstream gcode;
gcode << "["; gcode << "[";
+2
View File
@@ -281,6 +281,8 @@ public:
// extra_retract forwards a PETG pre-extrusion over-extrusion; default 0 -> identical to the plain deretract. // extra_retract forwards a PETG pre-extrusion over-extrusion; default 0 -> identical to the plain deretract.
std::string unretract(float extra_retract = 0.f) { return m_writer.unlift() + m_writer.unretract(extra_retract); } std::string unretract(float extra_retract = 0.f) { return m_writer.unlift() + m_writer.unretract(extra_retract); }
std::string set_extruder(unsigned int extruder_id, double print_z, bool by_object=false, int toolchange_temp_override = -1, bool defer_temp_wait = false); std::string set_extruder(unsigned int extruder_id, double print_z, bool by_object=false, int toolchange_temp_override = -1, bool defer_temp_wait = false);
// Sets the pressure advance of the filament's extruder variant, if enabled for it.
std::string set_filament_pressure_advance(unsigned int filament_id);
bool is_BBL_Printer(); bool is_BBL_Printer();
WipeTowerType wipe_tower_type(); WipeTowerType wipe_tower_type();
+26 -33
View File
@@ -21,38 +21,30 @@ namespace Slic3r {
* *
* @param gcodegen A reference to the GCode object that generates the G-code. * @param gcodegen A reference to the GCode object that generates the G-code.
*/ */
AdaptivePAProcessor::AdaptivePAProcessor(GCode &gcodegen, const std::vector<unsigned int> &tools_used) AdaptivePAProcessor::AdaptivePAProcessor(GCode &gcodegen)
: m_gcodegen(gcodegen), : m_gcodegen(gcodegen),
m_config(gcodegen.config()), m_config(gcodegen.config()),
m_last_predicted_pa(0.0), m_last_predicted_pa(0.0),
m_max_next_feedrate(0.0), m_max_next_feedrate(0.0),
m_next_feedrate(0.0), m_next_feedrate(0.0),
m_current_feedrate(0.0), m_current_feedrate(0.0),
m_last_extruder_id(-1), m_last_config_index(-1),
m_pa_change_pattern(R"(; PA_CHANGE:T(\d+) MM3MM:([0-9]*\.[0-9]+) ACCEL:(\d+) BR:(\d+) RC:(\d+) OV:(\d+))"), m_pa_change_pattern(R"(; PA_CHANGE:T(\d+) MM3MM:([0-9]*\.[0-9]+) ACCEL:(\d+) BR:(\d+) RC:(\d+) OV:(\d+))"),
m_g1_f_pattern(R"(G1 F([0-9]+))") m_g1_f_pattern(R"(G1 F([0-9]+))")
{ {
// Constructor body can be used for further initialization if necessary
for (unsigned int tool : tools_used) {
// Only enable model for the tool if both PA and adaptive PA options are enabled
if(m_config.adaptive_pressure_advance.get_at(tool) && m_config.enable_pressure_advance.get_at(tool)){
auto interpolator = std::make_unique<AdaptivePAInterpolator>();
// Get calibration values from extruder
std::string pa_calibration_values = m_config.adaptive_pressure_advance_model.get_at(tool);
// Setup the model and store it in the tool-interpolation model map
interpolator->parseAndSetData(pa_calibration_values);
m_AdaptivePAInterpolators[tool] = std::move(interpolator);
}
}
} }
// Method to get the interpolator for a specific tool ID // Method to get the interpolator for a specific filament config index.
AdaptivePAInterpolator* AdaptivePAProcessor::getInterpolator(unsigned int tool_id) { // The model is built the first time an index is requested, as the indices in use depend on
auto it = m_AdaptivePAInterpolators.find(tool_id); // the extruder variant each filament prints with.
if (it != m_AdaptivePAInterpolators.end()) { AdaptivePAInterpolator* AdaptivePAProcessor::getInterpolator(unsigned int config_index) {
return it->second.get(); auto [it, inserted] = m_AdaptivePAInterpolators.try_emplace(config_index);
// Only enable model for the index if both PA and adaptive PA options are enabled
if (inserted && m_config.adaptive_pressure_advance.get_at(config_index) && m_config.enable_pressure_advance.get_at(config_index)) {
it->second = std::make_unique<AdaptivePAInterpolator>();
it->second->parseAndSetData(m_config.adaptive_pressure_advance_model.get_at(config_index));
} }
return nullptr; // Handle the case where the tool_id was not found return it->second.get();
} }
/** /**
@@ -108,16 +100,17 @@ std::string AdaptivePAProcessor::process_layer(std::string &&gcode) {
// the PA for that material is set. As no tag below will be found for this extruder, the original PA is retained. // the PA for that material is set. As no tag below will be found for this extruder, the original PA is retained.
if (line.find("; PA_CHANGE") == 0) { // prune lines quickly before running regex check as regex is more expensive to run if (line.find("; PA_CHANGE") == 0) { // prune lines quickly before running regex check as regex is more expensive to run
if (std::regex_search(line, m_match, m_pa_change_pattern)) { if (std::regex_search(line, m_match, m_pa_change_pattern)) {
int extruder_id = std::stoi(m_match[1].str()); // The tag carries the filament config index, which selects the PA settings of the filament's extruder variant
int config_index = std::stoi(m_match[1].str());
mm3mm_value = std::stod(m_match[2].str()); mm3mm_value = std::stod(m_match[2].str());
accel_value = std::stod(m_match[3].str()); accel_value = std::stod(m_match[3].str());
int isBridge = std::stoi(m_match[4].str()); int isBridge = std::stoi(m_match[4].str());
int roleChange = std::stoi(m_match[5].str()); int roleChange = std::stoi(m_match[5].str());
int isOverhang = std::stoi(m_match[6].str()); int isOverhang = std::stoi(m_match[6].str());
// Check if the extruder ID has changed // Check if the filament config index has changed
bool extruder_changed = (extruder_id != m_last_extruder_id); bool config_index_changed = (config_index != m_last_config_index);
m_last_extruder_id = extruder_id; m_last_config_index = config_index;
// Save the PA_CHANGE line to output later after finding feedrate // Save the PA_CHANGE line to output later after finding feedrate
pa_change_line = line; pa_change_line = line;
@@ -212,23 +205,23 @@ std::string AdaptivePAProcessor::process_layer(std::string &&gcode) {
// Calculate the predicted PA using the upcomming feature maximum feedrate // Calculate the predicted PA using the upcomming feature maximum feedrate
// Get the interpolator for the active tool // Get the interpolator for the active tool
AdaptivePAInterpolator* interpolator = getInterpolator(m_last_extruder_id); AdaptivePAInterpolator* interpolator = getInterpolator(m_last_config_index);
double predicted_pa = 0; double predicted_pa = 0;
double adaptive_PA_speed = 0; double adaptive_PA_speed = 0;
if(!interpolator){ // Tool not found in the interpolator map if(!interpolator){ // Tool not found in the interpolator map
// Tool not found in the PA interpolator to tool map // Tool not found in the PA interpolator to tool map
predicted_pa = m_config.enable_pressure_advance.get_at(m_last_extruder_id) ? m_config.pressure_advance.get_at(m_last_extruder_id) : 0; predicted_pa = m_config.enable_pressure_advance.get_at(m_last_config_index) ? m_config.pressure_advance.get_at(m_last_config_index) : 0;
if(m_config.gcode_comments) output << "; APA: Tool doesnt have APA enabled\n"; if(m_config.gcode_comments) output << "; APA: Tool doesnt have APA enabled\n";
} else if (!interpolator->isInitialised() || (!m_config.adaptive_pressure_advance.get_at(m_last_extruder_id)) ) } else if (!interpolator->isInitialised() || (!m_config.adaptive_pressure_advance.get_at(m_last_config_index)) )
// Check if the model is not initialised by the constructor for the active extruder // Check if the model is not initialised by the constructor for the active extruder
// Also check that adaptive PA is enabled for that extruder. This should not be needed // Also check that adaptive PA is enabled for that extruder. This should not be needed
// as the PA change flag should not be set upstream (in the GCode.cpp file) if adaptive PA is disabled // as the PA change flag should not be set upstream (in the GCode.cpp file) if adaptive PA is disabled
// however check for robustness sake. // however check for robustness sake.
{ {
// Model failed or adaptive pressure advance not enabled - use default value from m_config // Model failed or adaptive pressure advance not enabled - use default value from m_config
predicted_pa = m_config.enable_pressure_advance.get_at(m_last_extruder_id) ? m_config.pressure_advance.get_at(m_last_extruder_id) : 0; predicted_pa = m_config.enable_pressure_advance.get_at(m_last_config_index) ? m_config.pressure_advance.get_at(m_last_config_index) : 0;
if(m_config.gcode_comments) output << "; APA: Interpolator setup failed, using default pressure advance\n"; if(m_config.gcode_comments) output << "; APA: Interpolator setup failed, using default pressure advance\n";
} else { // Model setup succeeded } else { // Model setup succeeded
// Proceed to identify the print speed to use to calculate the adaptive PA value // Proceed to identify the print speed to use to calculate the adaptive PA value
@@ -249,18 +242,18 @@ std::string AdaptivePAProcessor::process_layer(std::string &&gcode) {
predicted_pa = (*interpolator)(mm3mm_value * adaptive_PA_speed, accel_value); predicted_pa = (*interpolator)(mm3mm_value * adaptive_PA_speed, accel_value);
// This is a bridge, use the dedicated PA setting. // This is a bridge, use the dedicated PA setting.
if(isBridge && m_config.adaptive_pressure_advance_bridges.get_at(m_last_extruder_id) > EPSILON) if(isBridge && m_config.adaptive_pressure_advance_bridges.get_at(m_last_config_index) > EPSILON)
predicted_pa = m_config.adaptive_pressure_advance_bridges.get_at(m_last_extruder_id); predicted_pa = m_config.adaptive_pressure_advance_bridges.get_at(m_last_config_index);
if (predicted_pa < 0) { // If extrapolation fails, fall back to the default PA for the extruder. if (predicted_pa < 0) { // If extrapolation fails, fall back to the default PA for the extruder.
predicted_pa = m_config.enable_pressure_advance.get_at(m_last_extruder_id) ? m_config.pressure_advance.get_at(m_last_extruder_id) : 0; predicted_pa = m_config.enable_pressure_advance.get_at(m_last_config_index) ? m_config.pressure_advance.get_at(m_last_config_index) : 0;
if(m_config.gcode_comments) output << "; APA: Interpolation failed, using fallback pressure advance value\n"; if(m_config.gcode_comments) output << "; APA: Interpolation failed, using fallback pressure advance value\n";
} }
} }
if(m_config.gcode_comments) { if(m_config.gcode_comments) {
// Output debug GCode comments // Output debug GCode comments
output << pa_change_line << '\n'; // Output PA change command tag output << pa_change_line << '\n'; // Output PA change command tag
if(isBridge && m_config.adaptive_pressure_advance_bridges.get_at(m_last_extruder_id) > EPSILON) if(isBridge && m_config.adaptive_pressure_advance_bridges.get_at(m_last_config_index) > EPSILON)
output << "; APA Model Override (bridge)\n"; output << "; APA Model Override (bridge)\n";
output << "; APA Current Speed: " << std::to_string(m_current_feedrate) << "\n"; output << "; APA Current Speed: " << std::to_string(m_current_feedrate) << "\n";
output << "; APA Next Speed: " << std::to_string(m_next_feedrate) << "\n"; output << "; APA Next Speed: " << std::to_string(m_next_feedrate) << "\n";
@@ -269,7 +262,7 @@ std::string AdaptivePAProcessor::process_layer(std::string &&gcode) {
output << "; APA Flow rate: " << std::to_string(mm3mm_value * m_max_next_feedrate) << "\n"; output << "; APA Flow rate: " << std::to_string(mm3mm_value * m_max_next_feedrate) << "\n";
output << "; APA Prev PA: " << std::to_string(m_last_predicted_pa) << " New PA: " << std::to_string(predicted_pa) << "\n"; output << "; APA Prev PA: " << std::to_string(m_last_predicted_pa) << " New PA: " << std::to_string(predicted_pa) << "\n";
} }
if (extruder_changed || std::fabs(predicted_pa - m_last_predicted_pa) > EPSILON) { if (config_index_changed || std::fabs(predicted_pa - m_last_predicted_pa) > EPSILON) {
output << m_gcodegen.writer().set_pressure_advance(predicted_pa); // Use m_writer to set pressure advance output << m_gcodegen.writer().set_pressure_advance(predicted_pa); // Use m_writer to set pressure advance
m_last_predicted_pa = predicted_pa; // Update the last predicted PA value m_last_predicted_pa = predicted_pa; // Update the last predicted PA value
} }
+7 -11
View File
@@ -11,7 +11,6 @@
#include <regex> #include <regex>
#include <memory> #include <memory>
#include <map> #include <map>
#include <vector>
#include "AdaptivePAInterpolator.hpp" #include "AdaptivePAInterpolator.hpp"
namespace Slic3r { namespace Slic3r {
@@ -33,7 +32,7 @@ public:
* *
* @param gcodegen A reference to the GCode object that generates the G-code. * @param gcodegen A reference to the GCode object that generates the G-code.
*/ */
AdaptivePAProcessor(GCode &gcodegen, const std::vector<unsigned int> &tools_used); AdaptivePAProcessor(GCode &gcodegen);
/** /**
* @brief Processes a layer of G-code and applies adaptive pressure advance. * @brief Processes a layer of G-code and applies adaptive pressure advance.
@@ -70,28 +69,25 @@ public:
private: private:
GCode &m_gcodegen; ///< Reference to the GCode object. GCode &m_gcodegen; ///< Reference to the GCode object.
std::unordered_map<unsigned int, std::unique_ptr<AdaptivePAInterpolator>> m_AdaptivePAInterpolators; ///< Map between Interpolator objects and tool ID's std::unordered_map<unsigned int, std::unique_ptr<AdaptivePAInterpolator>> m_AdaptivePAInterpolators; ///< Map between Interpolator objects and filament config indices (null when adaptive PA is off)
const PrintConfig &m_config; ///< Reference to the print configuration. const PrintConfig &m_config; ///< Reference to the print configuration.
double m_last_predicted_pa; ///< Last predicted pressure advance value. double m_last_predicted_pa; ///< Last predicted pressure advance value.
double m_max_next_feedrate; ///< Maximum feed rate (speed) for the upcomming island. If no speed is found, the previous island speed is used. double m_max_next_feedrate; ///< Maximum feed rate (speed) for the upcomming island. If no speed is found, the previous island speed is used.
double m_next_feedrate; ///< First feed rate (speed) for the upcomming island. double m_next_feedrate; ///< First feed rate (speed) for the upcomming island.
double m_current_feedrate; ///< Current, latest feedrate. double m_current_feedrate; ///< Current, latest feedrate.
int m_last_extruder_id; ///< Last used extruder ID. int m_last_config_index; ///< Filament config index of the last PA_CHANGE tag.
std::regex m_pa_change_pattern; ///< Regular expression to detect PA_CHANGE pattern. std::regex m_pa_change_pattern; ///< Regular expression to detect PA_CHANGE pattern.
std::regex m_g1_f_pattern; ///< Regular expression to detect G1 F pattern. std::regex m_g1_f_pattern; ///< Regular expression to detect G1 F pattern.
std::smatch m_match; ///< Match results for regular expressions. std::smatch m_match; ///< Match results for regular expressions.
/** /**
* @brief Get the PA interpolator attached to the specified tool ID. * @brief Get the PA interpolator attached to the specified filament config index.
* *
* This method manually sets the adaptive PA internally held value. * @param config_index The filament config index (one per filament and extruder variant) for which the PA interpolation model is to be returned.
* Call this when changing tools or in any other case where the internally assumed last PA value may be incorrect * @return The Adaptive PA Interpolator object corresponding to that index, or nullptr when adaptive PA is off for it.
*
* @param An integer with the tool ID for which the PA interpolation model is to be returned.
* @return The Adaptive PA Interpolator object corresponding to that tool.
*/ */
AdaptivePAInterpolator* getInterpolator(unsigned int tool_id); AdaptivePAInterpolator* getInterpolator(unsigned int config_index);
}; };
} // namespace Slic3r } // namespace Slic3r
+16
View File
@@ -13,8 +13,19 @@
namespace Slic3r { namespace Slic3r {
// How many setters this thread holds, so the ones nested in another can skip
// setlocale, which takes a lock the whole process shares on Windows.
static thread_local int s_numeric_locale_depth = 0;
CNumericLocalesSetter::CNumericLocalesSetter() CNumericLocalesSetter::CNumericLocalesSetter()
{ {
// Nested in another setter on this thread, whose "C" the separator check
// confirms is still set.
if (s_numeric_locale_depth > 0 && is_decimal_separator_point()) {
m_nested = true;
++ s_numeric_locale_depth;
return;
}
#ifdef _WIN32 #ifdef _WIN32
_configthreadlocale(_ENABLE_PER_THREAD_LOCALE); _configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
m_orig_numeric_locale = std::setlocale(LC_NUMERIC, nullptr); m_orig_numeric_locale = std::setlocale(LC_NUMERIC, nullptr);
@@ -29,12 +40,17 @@ CNumericLocalesSetter::CNumericLocalesSetter()
m_new_locale = newlocale(LC_NUMERIC_MASK, "C", m_new_locale); m_new_locale = newlocale(LC_NUMERIC_MASK, "C", m_new_locale);
uselocale(m_new_locale); uselocale(m_new_locale);
#endif #endif
// Counted last, since the destructor does not run for a constructor that throws.
++ s_numeric_locale_depth;
} }
CNumericLocalesSetter::~CNumericLocalesSetter() CNumericLocalesSetter::~CNumericLocalesSetter()
{ {
-- s_numeric_locale_depth;
if (m_nested)
return;
#ifdef _WIN32 #ifdef _WIN32
std::setlocale(LC_NUMERIC, m_orig_numeric_locale.data()); std::setlocale(LC_NUMERIC, m_orig_numeric_locale.data());
#else #else
+5
View File
@@ -19,8 +19,13 @@ class CNumericLocalesSetter {
public: public:
CNumericLocalesSetter(); CNumericLocalesSetter();
~CNumericLocalesSetter(); ~CNumericLocalesSetter();
// A copy would restore the locale twice, and count down once more than up.
CNumericLocalesSetter(const CNumericLocalesSetter&) = delete;
CNumericLocalesSetter& operator=(const CNumericLocalesSetter&) = delete;
private: private:
// Inside another setter on this thread, which does the setting and restoring.
bool m_nested { false };
#ifdef _WIN32 #ifdef _WIN32
std::string m_orig_numeric_locale; std::string m_orig_numeric_locale;
#else #else
+65
View File
@@ -0,0 +1,65 @@
#pragma once
#include <algorithm>
#include <cstddef>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>
#include <tbb/blocked_range.h>
#include <tbb/parallel_for.h>
#include <tbb/task_arena.h>
#include <tbb/task_group.h>
#include "Exception.hpp"
namespace Slic3r {
// A batch of resolved presets, each a whole config, is what resolve_then_commit adds
// to peak memory, so it stays far below a vendor's preset count and above any core count.
inline constexpr size_t resolve_batch_size = 64;
// Resolve `count` items that do not depend on each other and install them one at
// a time.
//
// `resolve(i)` runs on any thread and must read only, since the items are
// resolved side by side. `commit(i, resolved)` is called for every item in index
// order, on the calling thread, and is where shared state is written.
//
// The items are worked through in batches, so what is resolved and held at once
// does not grow with `count`. An exception from either callable propagates after
// the batches before it have been committed. A cancellation of the caller's task
// group, which stops a batch partway without an exception, throws RuntimeError
// before that batch commits.
//
// `ChunkSetup`, when given, is constructed once for each piece of a batch TBB hands
// out, for per-thread state a resolve would otherwise set up per item, such as the
// C numeric locale, whose setting takes a lock the whole process shares.
template<class ChunkSetup = std::monostate, class Resolve, class Commit>
void resolve_then_commit(size_t count, Resolve resolve, Commit commit)
{
using Resolved = std::invoke_result_t<Resolve&, size_t>;
std::vector<Resolved> resolved(std::min(count, resolve_batch_size));
for (size_t first = 0; first < count; first += resolve_batch_size) {
const size_t last = std::min(first + resolve_batch_size, count);
// Isolated, so a thread waiting on the batch runs none of the caller's other
// tasks before finishing it.
tbb::this_task_arena::isolate([&] {
tbb::parallel_for(tbb::blocked_range<size_t>(first, last),
[&](const tbb::blocked_range<size_t>& range) {
ChunkSetup setup;
(void) setup;
for (size_t i = range.begin(); i < range.end(); ++ i)
resolved[i - first] = resolve(i);
});
});
if (tbb::is_current_task_group_canceling())
throw RuntimeError("resolve_then_commit: canceled before the batch was resolved");
for (size_t i = first; i < last; ++ i)
commit(i, std::move(resolved[i - first]));
}
}
} // namespace Slic3r
+281 -182
View File
@@ -5,6 +5,8 @@
#include "Preset.hpp" #include "Preset.hpp"
#include "PresetBundle.hpp" #include "PresetBundle.hpp"
#include "AppConfig.hpp" #include "AppConfig.hpp"
#include "LocalesUtils.hpp"
#include "ParallelResolve.hpp"
#ifdef _MSC_VER #ifdef _MSC_VER
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
@@ -506,10 +508,11 @@ void Preset::normalize(DynamicPrintConfig &config)
handle_legacy_sla(config); handle_legacy_sla(config);
} }
std::string Preset::remove_invalid_keys(DynamicPrintConfig &config, const DynamicPrintConfig &default_config) std::string Preset::remove_invalid_keys(DynamicPrintConfig &config, const DynamicPrintConfig &default_config,
const DynamicPrintConfig *added)
{ {
std::string incorrect_keys; std::string incorrect_keys;
for (const std::string &key : config.keys()) for (const std::string &key : (added != nullptr ? *added : config).keys())
if (! default_config.has(key)) { if (! default_config.has(key)) {
if (incorrect_keys.empty()) if (incorrect_keys.empty())
incorrect_keys = key; incorrect_keys = key;
@@ -621,7 +624,6 @@ void Preset::load_info(const std::string& file)
} }
else if (v.first.compare("base_id") == 0) { else if (v.first.compare("base_id") == 0) {
this->base_id = v.second.get_value<std::string>(); this->base_id = v.second.get_value<std::string>();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " load info from: " << file << " and base_id: " << this->base_id;
if (this->base_id.compare("null") == 0) if (this->base_id.compare("null") == 0)
this->base_id.clear(); this->base_id.clear();
} }
@@ -974,6 +976,37 @@ void Preset::get_extruder_names_and_keysets(Type type, std::string& extruder_id_
} }
} }
DynamicPrintConfig Preset::load_external_config(Type type, const DynamicPrintConfig &default_config, const DynamicPrintConfig &project_config,
const std::set<std::string> &different_settings_list,
const std::function<DynamicPrintConfig *(const std::string &inherits)> &find_base,
t_config_option_keys *keys)
{
// Load the preset over a default preset, so that the missing fields are filled in from the default preset.
DynamicPrintConfig cfg(default_config);
// SoftFever: ignore print connection info from project
auto cfg_keys = cfg.keys();
cfg_keys.erase(std::remove_if(cfg_keys.begin(), cfg_keys.end(),
[](std::string &val) {
return val == "print_host" || val == "print_host_webui" || val == "printhost_apikey" ||
val == "printhost_cafile" || val == "printhost_user" || val == "printhost_password" || val == "printhost_port";
}),
cfg_keys.end());
cfg.apply_only(project_config, cfg_keys, true);
//add different settings check logic, replace the old system preset's default value with new system preset's default values
if (!different_settings_list.empty()) {
if (DynamicPrintConfig *base_config = find_base(Preset::inherits(cfg))) {
std::string extruder_id_name, extruder_variant_name;
std::set<std::string> *key_set1 = nullptr, *key_set2 = nullptr;
Preset::get_extruder_names_and_keysets(type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2);
cfg.update_non_diff_values_to_base_config(*base_config, cfg_keys, different_settings_list, extruder_id_name, extruder_variant_name, *key_set1, *key_set2);
}
}
if (keys)
*keys = std::move(cfg_keys);
return cfg;
}
bool Preset::has_lidar(PresetBundle *preset_bundle) bool Preset::has_lidar(PresetBundle *preset_bundle)
{ {
bool has_lidar = false; bool has_lidar = false;
@@ -1669,6 +1702,169 @@ std::string PresetCollection::canonical_preset_name(const std::string &name, con
return get_preset_canonical_name(parsed.bare, origin); return get_preset_canonical_name(parsed.bare, origin);
} }
PresetCollection::UserPresetLoad PresetCollection::resolve_user_preset(
const boost::filesystem::path &file, const std::string &canonical_name,
const PresetOrigin &load_origin, ForwardCompatibilitySubstitutionRule substitution_rule,
const std::string &extruder_id_name, const std::string &extruder_variant_name,
std::set<std::string> *key_set1, std::set<std::string> *key_set2) const
{
UserPresetLoad out;
out.preset = Preset(m_type, canonical_name, false);
Preset &preset = out.preset;
preset.bundle_id = load_origin.bundle_id;
preset.file = file.string();
// Load the preset file, apply preset values on top of defaults.
try {
fs::path idx_path(preset.file);
idx_path.replace_extension(".info");
if (fs::exists(idx_path)) {
out.info_file = idx_path.string();
preset.load_info(out.info_file);
}
DynamicPrintConfig config;
//BBS: change to json format
//ConfigSubstitutions config_substitutions = config.load_from_ini(preset.file, substitution_rule);
std::map<std::string, std::string> key_values;
std::string reason;
ConfigSubstitutions config_substitutions = config.load_from_json(preset.file, substitution_rule, key_values, reason);
if (! config_substitutions.empty())
out.substitutions.push_back({ preset.name, m_type, PresetConfigSubstitutions::Source::UserFile, preset.file, std::move(config_substitutions) });
if (!reason.empty()) {
out.discard_file = true;
out.errors.push_back((boost::format("parse config %1% failed") % preset.file).str());
return out;
}
std::string version_str = key_values[BBL_JSON_KEY_VERSION];
boost::optional<Semver> version = Semver::parse(version_str);
if (!version) return out;
preset.version = *version;
if (key_values.find(BBL_JSON_KEY_FILAMENT_ID) != key_values.end())
preset.filament_id = key_values[BBL_JSON_KEY_FILAMENT_ID];
if (key_values.find(BBL_JSON_KEY_DESCRIPTION) != key_values.end())
preset.description = key_values[BBL_JSON_KEY_DESCRIPTION];
if (key_values.find(BBL_JSON_KEY_INSTANTIATION) != key_values.end())
preset.is_visible = key_values[BBL_JSON_KEY_INSTANTIATION] != "false";
//Orca: find and use the inherit config as the base
const Preset* inherit_preset = nullptr;
ConfigOption* inherits_config = config.option(BBL_JSON_KEY_INHERITS);
// check inherits_config
if (inherits_config) {
ConfigOptionString * option_str = dynamic_cast<ConfigOptionString *> (inherits_config);
std::string inherits_value = option_str->value;
// Orca: try to find if the parent preset has been renamed
inherit_preset = this->find_preset2(inherits_value);
Preset::normalize_inherits(config, inherit_preset);
}
const Preset& default_preset = this->default_preset_for(config);
if (inherit_preset) {
preset.config = inherit_preset->config;
preset.filament_id = inherit_preset->filament_id;
extend_default_config_length(config, false, {});
preset.config.update_diff_values_to_child_config(config, extruder_id_name, extruder_variant_name, *key_set1, *key_set2);
}
else {
auto inherits_config2 = dynamic_cast<ConfigOptionString *>(inherits_config);
if ((inherits_config2 && !inherits_config2->value.empty())) {
out.errors.push_back((boost::format("can not find parent %1% for config %2%!") % inherits_config2->value % preset.file).str());
return out;
}
// We support custom root preset now
// Find a default preset for the config. The PrintPresetCollection provides different default preset based on the "printer_technology" field.
preset.config = default_preset.config;
preset.config.apply(std::move(config));
extend_default_config_length(preset.config, true, default_preset.config);
}
Preset::normalize(preset.config);
// Report configuration fields, which are misplaced into a wrong group.
std::string incorrect_keys = Preset::remove_invalid_keys(preset.config, default_preset.config);
if (!incorrect_keys.empty())
out.errors.push_back("Error in a preset file: The preset \"" + preset.file +
"\" contains the following incorrect keys: " + incorrect_keys + ", which were removed");
if (preset.type == Preset::TYPE_FILAMENT && preset.is_user() && preset.inherits().empty()) {
auto compatible_printers = dynamic_cast<ConfigOptionStrings *>(preset.config.option("compatible_printers", true));
if (compatible_printers && compatible_printers->values.empty()) {
size_t at_pos = canonical_name.find('@');
if (at_pos != std::string::npos && at_pos + 1 < canonical_name.length()) {
compatible_printers->values.push_back(canonical_name.substr(at_pos + 1));
out.save_compatible_printers = true;
}
}
}
preset.loaded = true;
out.complete = true;
} catch (const std::ifstream::failure &err) {
out.discard_file = true;
out.errors.push_back((boost::format("The user-config cannot be loaded: %1%. Reason: %2%") % preset.file % err.what()).str());
//throw Slic3r::RuntimeError(std::string("The selected preset cannot be loaded: ") + preset.file + "\n\tReason: " + err.what());
} catch (const std::runtime_error &err) {
out.discard_file = true;
out.errors.push_back((boost::format("Failed loading the user-config file: %1%. Reason: %2%") % preset.file % err.what()).str());
//throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + preset.file + "\n\tReason: " + err.what());
}
out.install = true;
return out;
}
void PresetCollection::commit_user_preset(UserPresetLoad &&loaded, std::deque<Preset> &presets_loaded,
PresetsConfigSubstitutions &substitutions,
const std::function<void(Preset&)> &preset_loaded_fn,
bool read_only)
{
Preset &preset = loaded.preset;
if (! loaded.info_file.empty())
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " load info from: " << loaded.info_file << " and base_id: " << preset.base_id;
append(substitutions, std::move(loaded.substitutions));
for (const std::string &error : loaded.errors) {
++m_errors;
BOOST_LOG_TRIVIAL(error) << error;
}
if (loaded.discard_file && !read_only) {
fs::path file_path(loaded.preset.file);
if (fs::exists(file_path))
fs::remove(file_path);
file_path.replace_extension(".info");
if (fs::exists(file_path))
fs::remove(file_path);
}
if (!loaded.install)
return;
if (loaded.complete) {
if (loaded.save_compatible_printers) {
// A filesystem error from the rewrite is counted, and the preset still loads.
try {
if (!read_only)
preset.save(nullptr);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " added compatible_printers for preset: " << preset.name;
} catch (const std::runtime_error &err) {
++m_errors;
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " can not write compatible_printers back to " << preset.file << ": " << err.what();
}
}
//BBS: add some workaround for previous incorrect settings
if ((!preset.setting_id.empty())&&(preset.setting_id == preset.base_id))
preset.setting_id.clear();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " load preset: " << preset.name << " and filament_id: " << preset.filament_id << " and base_id: " << preset.base_id;
//BBS: add config related logs
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(", preset type %1%, name %2%, path %3%, is_system %4%, is_default %5% is_visible %6%")%Preset::get_type_string(m_type) %preset.name %preset.file %preset.is_system %preset.is_default %preset.is_visible;
// add alias for custom filament preset
set_custom_preset_alias(preset);
}
if (preset_loaded_fn != nullptr)
preset_loaded_fn(preset);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " load config successful and preset name is:" << preset.name;
presets_loaded.emplace_back(std::move(preset));
}
// Load all presets found in dir_path. // Load all presets found in dir_path.
// Throws an exception on error. // Throws an exception on error.
void PresetCollection::load_presets( void PresetCollection::load_presets(
@@ -1706,6 +1902,8 @@ void PresetCollection::load_presets(
std::set<std::string> *key_set1 = nullptr, *key_set2 = nullptr; std::set<std::string> *key_set1 = nullptr, *key_set2 = nullptr;
Preset::get_extruder_names_and_keysets(m_type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2); Preset::get_extruder_names_and_keysets(m_type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2);
struct UserPresetFile { fs::path path; std::string canonical_name; };
std::vector<UserPresetFile> files;
//BBS: change to json format //BBS: change to json format
for (auto &dir_entry : boost::filesystem::directory_iterator(dir)) for (auto &dir_entry : boost::filesystem::directory_iterator(dir))
{ {
@@ -1721,149 +1919,26 @@ void PresetCollection::load_presets(
BOOST_LOG_TRIVIAL(warning) << "Preset already present, not loading: " << canonical_name; BOOST_LOG_TRIVIAL(warning) << "Preset already present, not loading: " << canonical_name;
continue; continue;
} }
files.push_back({ dir_entry.path(), std::move(canonical_name) });
}
}
resolve_then_commit<CNumericLocalesSetter>(files.size(),
[&](size_t i) {
return this->resolve_user_preset(files[i].path, files[i].canonical_name, resolved_origin, substitution_rule,
extruder_id_name, extruder_variant_name, key_set1, key_set2);
},
[&](size_t, UserPresetLoad &&loaded) {
// Committing can remove an unreadable preset's file, and a filesystem error
// there is reported without stopping the rest of the directory.
try { try {
Preset preset(m_type, canonical_name, false); this->commit_user_preset(std::move(loaded), presets_loaded, substitutions, preset_loaded_fn, read_only);
preset.bundle_id = resolved_origin.bundle_id;
preset.file = dir_entry.path().string();
// Load the preset file, apply preset values on top of defaults.
try {
fs::path idx_path(preset.file);
idx_path.replace_extension(".info");
if (fs::exists(idx_path)) {
preset.load_info(idx_path.string());
}
DynamicPrintConfig config;
//BBS: change to json format
//ConfigSubstitutions config_substitutions = config.load_from_ini(preset.file, substitution_rule);
std::map<std::string, std::string> key_values;
std::string reason;
ConfigSubstitutions config_substitutions = config.load_from_json(preset.file, substitution_rule, key_values, reason);
if (! config_substitutions.empty())
substitutions.push_back({ preset.name, m_type, PresetConfigSubstitutions::Source::UserFile, preset.file, std::move(config_substitutions) });
if (!reason.empty()) {
fs::path file_path(preset.file);
if (!read_only && fs::exists(file_path))
fs::remove(file_path);
file_path.replace_extension(".info");
if (!read_only && fs::exists(file_path))
fs::remove(file_path);
BOOST_LOG_TRIVIAL(error) << boost::format("parse config %1% failed")%preset.file;
++m_errors;
continue;
}
std::string version_str = key_values[BBL_JSON_KEY_VERSION];
boost::optional<Semver> version = Semver::parse(version_str);
if (!version) continue;
preset.version = *version;
if (key_values.find(BBL_JSON_KEY_FILAMENT_ID) != key_values.end())
preset.filament_id = key_values[BBL_JSON_KEY_FILAMENT_ID];
if (key_values.find(BBL_JSON_KEY_DESCRIPTION) != key_values.end())
preset.description = key_values[BBL_JSON_KEY_DESCRIPTION];
if (key_values.find(BBL_JSON_KEY_INSTANTIATION) != key_values.end())
preset.is_visible = key_values[BBL_JSON_KEY_INSTANTIATION] != "false";
//Orca: find and use the inherit config as the base
Preset* inherit_preset = nullptr;
ConfigOption* inherits_config = config.option(BBL_JSON_KEY_INHERITS);
// check inherits_config
if (inherits_config) {
ConfigOptionString * option_str = dynamic_cast<ConfigOptionString *> (inherits_config);
std::string inherits_value = option_str->value;
// Orca: try to find if the parent preset has been renamed
inherit_preset = this->find_preset2(inherits_value);
Preset::normalize_inherits(config, inherit_preset);
} else {
;
}
const Preset& default_preset = this->default_preset_for(config);
if (inherit_preset) {
preset.config = inherit_preset->config;
preset.filament_id = inherit_preset->filament_id;
extend_default_config_length(config, false, {});
preset.config.update_diff_values_to_child_config(config, extruder_id_name, extruder_variant_name, *key_set1, *key_set2);
}
else {
auto inherits_config2 = dynamic_cast<ConfigOptionString *>(inherits_config);
if ((inherits_config2 && !inherits_config2->value.empty())) {
BOOST_LOG_TRIVIAL(error) << boost::format("can not find parent %1% for config %2%!")%inherits_config2->value %preset.file;
++m_errors;
continue;
}
// We support custom root preset now
// Find a default preset for the config. The PrintPresetCollection provides different default preset based on the "printer_technology" field.
preset.config = default_preset.config;
preset.config.apply(std::move(config));
extend_default_config_length(preset.config, true, default_preset.config);
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " load preset: " << name << " and filament_id: " << preset.filament_id << " and base_id: " << preset.base_id;
Preset::normalize(preset.config);
// Report configuration fields, which are misplaced into a wrong group.
std::string incorrect_keys = Preset::remove_invalid_keys(preset.config, default_preset.config);
if (!incorrect_keys.empty()) {
++m_errors;
BOOST_LOG_TRIVIAL(error)
<< "Error in a preset file: The preset \"" << preset.file
<< "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed";
}
if (preset.type == Preset::TYPE_FILAMENT && preset.is_user() && preset.inherits().empty()) {
auto compatible_printers = dynamic_cast<ConfigOptionStrings *>(preset.config.option("compatible_printers", true));
if (compatible_printers && compatible_printers->values.empty()) {
size_t at_pos = name.find('@');
if (at_pos != std::string::npos && at_pos + 1 < name.length()) {
compatible_printers->values.push_back(name.substr(at_pos + 1));
if (!read_only)
preset.save(nullptr);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " added compatible_printers for preset: " << name;
}
}
}
preset.loaded = true;
//BBS: add some workaround for previous incorrect settings
if ((!preset.setting_id.empty())&&(preset.setting_id == preset.base_id))
preset.setting_id.clear();
//BBS: add config related logs
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(", preset type %1%, name %2%, path %3%, is_system %4%, is_default %5% is_visible %6%")%Preset::get_type_string(m_type) %preset.name %preset.file %preset.is_system %preset.is_default %preset.is_visible;
// add alias for custom filament preset
set_custom_preset_alias(preset);
} catch (const std::ifstream::failure &err) {
++m_errors;
BOOST_LOG_TRIVIAL(error) << boost::format("The user-config cannot be loaded: %1%. Reason: %2%")%preset.file %err.what();
fs::path file_path(preset.file);
if (!read_only && fs::exists(file_path))
fs::remove(file_path);
file_path.replace_extension(".info");
if (!read_only && fs::exists(file_path))
fs::remove(file_path);
//throw Slic3r::RuntimeError(std::string("The selected preset cannot be loaded: ") + preset.file + "\n\tReason: " + err.what());
} catch (const std::runtime_error &err) {
++m_errors;
BOOST_LOG_TRIVIAL(error) << boost::format("Failed loading the user-config file: %1%. Reason: %2%")%preset.file %err.what();
//throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + preset.file + "\n\tReason: " + err.what());
fs::path file_path(preset.file);
if (!read_only && fs::exists(file_path))
fs::remove(file_path);
file_path.replace_extension(".info");
if (!read_only && fs::exists(file_path))
fs::remove(file_path);
}
if (preset_loaded_fn != nullptr)
preset_loaded_fn(preset);
presets_loaded.emplace_back(preset);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " load config successful and preset name is:" << preset.name;
} catch (const std::runtime_error &err) { } catch (const std::runtime_error &err) {
errors_cummulative += err.what(); errors_cummulative += err.what();
errors_cummulative += "\n"; errors_cummulative += "\n";
} }
} });
}
if (presets_loaded.size() > 0) if (presets_loaded.size() > 0)
m_presets.insert(m_presets.end(), std::make_move_iterator(presets_loaded.begin()), std::make_move_iterator(presets_loaded.end())); m_presets.insert(m_presets.end(), std::make_move_iterator(presets_loaded.begin()), std::make_move_iterator(presets_loaded.end()));
sort_presets(); sort_presets();
@@ -2596,20 +2671,6 @@ std::pair<Preset*, bool> PresetCollection::load_external_preset(
const Semver file_version, const Semver file_version,
const std::string filament_id) const std::string filament_id)
{ {
// Load the preset over a default preset, so that the missing fields are filled in from the default preset.
DynamicPrintConfig cfg(this->default_preset_for(combined_config).config);
// SoftFever: ignore print connection info from project
auto keys = cfg.keys();
keys.erase(std::remove_if(keys.begin(), keys.end(),
[](std::string &val) {
return val == "print_host" || val == "print_host_webui" || val == "printhost_apikey" ||
val == "printhost_cafile" || val == "printhost_user" || val == "printhost_password" || val == "printhost_port";
}),
keys.end());
cfg.apply_only(combined_config, keys, true);
std::string &inherits = Preset::inherits(cfg);
//add different settings check logic, replace the old system preset's default value with new system preset's default values
std::deque<Preset>::iterator it = this->find_preset_internal(original_name); std::deque<Preset>::iterator it = this->find_preset_internal(original_name);
bool found = it != m_presets.end() && it->name == original_name; bool found = it != m_presets.end() && it->name == original_name;
if (! found) { if (! found) {
@@ -2618,24 +2679,25 @@ std::pair<Preset*, bool> PresetCollection::load_external_preset(
found = it != m_presets.end(); found = it != m_presets.end();
} }
std::string extruder_id_name, extruder_variant_name; t_config_option_keys keys;
std::set<std::string> *key_set1 = nullptr, *key_set2 = nullptr; DynamicPrintConfig cfg = Preset::load_external_config(m_type, this->default_preset_for(combined_config).config, combined_config, different_settings_list,
Preset::get_extruder_names_and_keysets(m_type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2); [this, &original_name, &path, found, it](const std::string &inherits) -> DynamicPrintConfig * {
if (!inherits.empty()) {
if (!inherits.empty() && (different_settings_list.size() > 0)) {
auto iter = this->find_preset_internal(inherits); auto iter = this->find_preset_internal(inherits);
if (iter == m_presets.end() || iter->name != inherits) if (iter == m_presets.end() || iter->name != inherits)
iter = this->find_preset_renamed(inherits); iter = this->find_preset_renamed(inherits);
if (iter != m_presets.end()) { if (iter == m_presets.end())
//std::vector<std::string> dirty_options = cfg.diff(iter->config); return nullptr;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": change preset %1% inherit %2% 's value to %3% 's values")%original_name %inherits %path; BOOST_LOG_TRIVIAL(info) << "load_external_preset" << boost::format(": change preset %1% inherit %2% 's value to %3% 's values")%original_name %inherits %path;
cfg.update_non_diff_values_to_base_config(iter->config, keys, different_settings_list, extruder_id_name, extruder_variant_name, *key_set1, *key_set2); return &iter->config;
} }
if (found && it->is_system) {
BOOST_LOG_TRIVIAL(info) << "load_external_preset" << boost::format(": change preset %1% 's value to %2% 's values")%original_name %path;
return &it->config;
} }
else if (found && it->is_system && (different_settings_list.size() > 0)) { return nullptr;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": change preset %1% 's value to %2% 's values")%original_name %path; }, &keys);
cfg.update_non_diff_values_to_base_config(it->config, keys, different_settings_list, extruder_id_name, extruder_variant_name, *key_set1, *key_set2); std::string &inherits = Preset::inherits(cfg);
}
//BBS: add config related logs //BBS: add config related logs
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" enter, type %1% , path %2%, name %3%, original_name %4%, inherits %5%")%Preset::get_type_string(m_type) %path %name %original_name %inherits; BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" enter, type %1% , path %2%, name %3%, original_name %4%, inherits %5%")%Preset::get_type_string(m_type) %path %name %original_name %inherits;
@@ -2827,6 +2889,18 @@ std::pair<Preset*, bool> PresetCollection::load_external_preset(
return std::make_pair(&preset, false); return std::make_pair(&preset, false);
} }
Preset& PresetCollection::append_preset(std::string &&path, const std::string &name, DynamicPrintConfig &&config)
{
lock();
Preset &preset = m_presets.emplace_back(m_type, name, false);
preset.file = std::move(path);
preset.config = std::move(config);
preset.loaded = true;
preset.is_dirty = false;
unlock();
return preset;
}
Preset& PresetCollection::load_preset(const std::string &path, const std::string &name, DynamicPrintConfig &&config, bool select, Semver file_version) Preset& PresetCollection::load_preset(const std::string &path, const std::string &name, DynamicPrintConfig &&config, bool select, Semver file_version)
{ {
lock(); lock();
@@ -3846,28 +3920,53 @@ bool PresetCollection::select_preset_by_name_strict(const std::string &name)
return false; return false;
} }
// Merge one vendor's presets with the other vendor's presets, report duplicates. std::vector<std::vector<std::string>> PresetCollection::merge_presets(const std::vector<PresetCollection*> &others, const VendorMap &new_vendors)
std::vector<std::string> PresetCollection::merge_presets(PresetCollection &&other, const VendorMap &new_vendors)
{ {
std::vector<std::string> duplicates; auto less = [this](const Preset &a, const Preset &b) {
for (Preset &preset : other.m_presets) { return m_type == Preset::TYPE_FILAMENT ? filament_preset_less(a, b) : a < b;
if (preset.is_default || preset.is_external) };
continue; struct Incoming { Preset *preset; size_t source; };
Preset key(m_type, preset.name); auto incoming_less = [&less](const Incoming &a, const Incoming &b) { return less(*a.preset, *b.preset); };
auto it = (m_type == Preset::TYPE_FILAMENT) // Each of `others` is sorted, so its presets form one sorted run.
? std::lower_bound(m_presets.begin() + m_num_default_presets, m_presets.end(), key, filament_preset_less) std::vector<Incoming> incoming;
: std::lower_bound(m_presets.begin() + m_num_default_presets, m_presets.end(), key); std::vector<size_t> run_ends { 0 };
if (it == m_presets.end() || it->name != preset.name) { for (size_t source = 0; source < others.size(); ++ source) {
for (Preset &preset : others[source]->m_presets)
if (! preset.is_default && ! preset.is_external)
incoming.push_back({ &preset, source });
assert(std::is_sorted(incoming.begin() + run_ends.back(), incoming.end(), incoming_less));
run_ends.push_back(incoming.size());
}
// Merged pairwise and stably, so equal names stay in the order of `others`.
const size_t runs = others.size();
for (size_t width = 1; width < runs; width *= 2)
for (size_t i = 0; i + width < runs; i += 2 * width)
std::inplace_merge(incoming.begin() + run_ends[i], incoming.begin() + run_ends[i + width],
incoming.begin() + run_ends[std::min(i + 2 * width, runs)], incoming_less);
std::vector<std::vector<std::string>> duplicates(others.size());
std::deque<Preset> merged;
auto own = m_presets.begin() + m_num_default_presets;
std::move(m_presets.begin(), own, std::back_inserter(merged));
// On equal names this collection's preset is kept, else the earliest of `others`,
// and each repeat is listed under the collection it came from.
for (auto next = incoming.begin(); own != m_presets.end() || next != incoming.end();) {
if (next == incoming.end() || (own != m_presets.end() && ! less(*next->preset, *own)))
merged.emplace_back(std::move(*own ++));
else {
Preset &preset = *(next ++)->preset;
if (preset.vendor != nullptr) { if (preset.vendor != nullptr) {
// Re-assign a pointer to the vendor structure in the new PresetBundle. // Re-assign a pointer to the vendor structure in the new PresetBundle.
auto it = new_vendors.find(preset.vendor->id); auto it = new_vendors.find(preset.vendor->id);
assert(it != new_vendors.end()); assert(it != new_vendors.end());
preset.vendor = &it->second; preset.vendor = &it->second;
} }
m_presets.emplace(it, std::move(preset)); merged.emplace_back(std::move(preset));
} else
duplicates.emplace_back(std::move(preset.name));
} }
for (; next != incoming.end() && next->preset->name == merged.back().name; ++ next)
duplicates[next->source].emplace_back(next->preset->name);
}
m_presets = std::move(merged);
return duplicates; return duplicates;
} }
+60 -3
View File
@@ -403,6 +403,15 @@ public:
std::string get_current_printer_type(PresetBundle *preset_bundle); // get current preset type std::string get_current_printer_type(PresetBundle *preset_bundle); // get current preset type
static void get_extruder_names_and_keysets(Type type, std::string& extruder_id_name, std::string& extruder_variant_name, std::set<std::string>** p_key_set1, std::set<std::string>** p_key_set2); static void get_extruder_names_and_keysets(Type type, std::string& extruder_id_name, std::string& extruder_variant_name, std::set<std::string>** p_key_set1, std::set<std::string>** p_key_set2);
// Config of a preset loaded from a project or config file: the project's values over the type's
// default preset config, without the print-host keys. When different_settings_list is not empty,
// every key not listed in it is then refreshed from the base system preset, which find_base returns
// for the project's "inherits" (nullptr when there is none), with the listed per-variant values
// mapped onto the base's extruder variants. keys, if given, receives the keys taken from the project.
static DynamicPrintConfig load_external_config(Type type, const DynamicPrintConfig &default_config, const DynamicPrintConfig &project_config,
const std::set<std::string> &different_settings_list,
const std::function<DynamicPrintConfig *(const std::string &inherits)> &find_base,
t_config_option_keys *keys = nullptr);
std::string get_printer_id() const { return vendor ? vendor->id : ""; } std::string get_printer_id() const { return vendor ? vendor->id : ""; }
bool has_lidar(PresetBundle *preset_bundle); bool has_lidar(PresetBundle *preset_bundle);
@@ -445,7 +454,10 @@ public:
static std::string remove_suffix_modified(const std::string& name); static std::string remove_suffix_modified(const std::string& name);
static void normalize(DynamicPrintConfig &config); static void normalize(DynamicPrintConfig &config);
// Report configuration fields, which are misplaced into a wrong group, remove them from the config. // Report configuration fields, which are misplaced into a wrong group, remove them from the config.
static std::string remove_invalid_keys(DynamicPrintConfig &config, const DynamicPrintConfig &default_config); // `added`, when given, is the diff applied over a copy of default_config, and only
// its keys are checked, since no other key can be missing from default_config.
static std::string remove_invalid_keys(DynamicPrintConfig &config, const DynamicPrintConfig &default_config,
const DynamicPrintConfig *added = nullptr);
// BBS: move constructor to public // BBS: move constructor to public
Preset(Type type, const std::string &name, bool is_default = false) : type(type), is_default(is_default), name(name) {} Preset(Type type, const std::string &name, bool is_default = false) : type(type), is_default(is_default), name(name) {}
@@ -801,6 +813,8 @@ public:
// Return number of presets including the "- default -" preset. // Return number of presets including the "- default -" preset.
size_t size() const { return m_presets.size(); } size_t size() const { return m_presets.size(); }
bool has_defaults_only() const { return m_presets.size() <= m_num_default_presets; } bool has_defaults_only() const { return m_presets.size() <= m_num_default_presets; }
// How many presets this collection refused or repaired while loading.
int error_count() const { return m_errors; }
// For Print / Filament presets, disable those, which are not compatible with the printer. // For Print / Filament presets, disable those, which are not compatible with the printer.
template<typename PreferedCondition> template<typename PreferedCondition>
@@ -874,8 +888,10 @@ protected:
// This is a temporary state, which shall be fixed immediately by the following step. // This is a temporary state, which shall be fixed immediately by the following step.
bool select_preset_by_name_strict(const std::string &name); bool select_preset_by_name_strict(const std::string &name);
// Merge one vendor's presets with the other vendor's presets, report duplicates. // Move the presets of `others` into this collection in one pass. A name this
std::vector<std::string> merge_presets(PresetCollection &&other, const VendorMap &new_vendors); // collection or an earlier one of `others` already has is left out, and reported
// in the list of the collection that repeats it.
std::vector<std::vector<std::string>> merge_presets(const std::vector<PresetCollection*> &others, const VendorMap &new_vendors);
// Update m_map_alias_to_profile_name from loaded system profiles. // Update m_map_alias_to_profile_name from loaded system profiles.
void update_map_alias_to_profile_name(); void update_map_alias_to_profile_name();
@@ -890,6 +906,43 @@ protected:
void set_custom_preset_alias(Preset &preset); void set_custom_preset_alias(Preset &preset);
private: private:
// One preset file read and flattened against the presets already in this
// collection, before anything the collection shares has been touched.
struct UserPresetLoad
{
Preset preset;
// Joins the collection. A file that threw partway still joins it, without
// the steps that did not run.
bool install { false };
// The whole of the load ran, so the preset is ready to be aliased.
bool complete { false };
// A filament preset that named no compatible printer and was given one from
// its name, which commit writes back to its file.
bool save_compatible_printers { false };
// Unreadable, so commit removes it and its .info file.
bool discard_file { false };
// The .info file read beside the preset, which commit logs.
std::string info_file;
// Counted and logged by commit, in the order the directory listed the files.
std::vector<std::string> errors;
PresetsConfigSubstitutions substitutions;
};
// Read and flatten one preset file. It reads only, and resolves against the presets
// loaded before this pass, never another file of the same pass, so the files of a
// pass are independent of each other.
UserPresetLoad resolve_user_preset(const boost::filesystem::path &file, const std::string &canonical_name,
const PresetOrigin &load_origin, ForwardCompatibilitySubstitutionRule substitution_rule,
const std::string &extruder_id_name, const std::string &extruder_variant_name,
std::set<std::string> *key_set1, std::set<std::string> *key_set2) const;
// Install one resolved preset. The collection, its alias maps, the error count
// and the preset files on disk are touched here and only here.
void commit_user_preset(UserPresetLoad &&loaded, std::deque<Preset> &presets_loaded,
PresetsConfigSubstitutions &substitutions,
const std::function<void(Preset&)> &preset_loaded_fn,
bool read_only);
std::string canonical_preset_name(const std::string &name, const PresetOrigin &load_origin = PresetOrigin()) const; std::string canonical_preset_name(const std::string &name, const PresetOrigin &load_origin = PresetOrigin()) const;
// Comparator that sorts "Generic " prefixed presets before others, then alphabetically within each group. // Comparator that sorts "Generic " prefixed presets before others, then alphabetically within each group.
@@ -901,6 +954,10 @@ private:
return a.name < b.name; return a.name < b.name;
} }
// Append a preset without keeping the collection sorted, for a caller installing
// many at once; find_preset() is unusable until sort_presets() runs.
Preset& append_preset(std::string &&path, const std::string &name, DynamicPrintConfig &&config);
// Sort presets: filament presets use generic-first ordering, others sort alphabetically. // Sort presets: filament presets use generic-first ordering, others sort alphabetically.
void sort_presets() { void sort_presets() {
if (m_type == Preset::TYPE_FILAMENT) if (m_type == Preset::TYPE_FILAMENT)
File diff suppressed because it is too large Load Diff
+182 -29
View File
@@ -15,6 +15,7 @@
#include <unordered_map> #include <unordered_map>
#include <optional> #include <optional>
#include <array> #include <array>
#include <atomic>
#include <boost/filesystem/path.hpp> #include <boost/filesystem/path.hpp>
#include <unordered_set> #include <unordered_set>
@@ -234,6 +235,9 @@ public:
// ORCA: utility function to find the vendor for a given preset name // ORCA: utility function to find the vendor for a given preset name
static std::string find_preset_vendor(const std::string& preset_name, Preset::Type type); static std::string find_preset_vendor(const std::string& preset_name, Preset::Type type);
// Keys a project keeps when its presets are loaded: those listed in its escaped
// "different_settings_to_system" entry for the preset, plus the preset bookkeeping keys.
static std::set<std::string> project_different_keys(const std::string &different_settings);
PresetBundle(); PresetBundle();
PresetBundle(const PresetBundle &rhs); PresetBundle(const PresetBundle &rhs);
@@ -273,6 +277,10 @@ public:
const std::string &source_file, const std::string &source_file,
ForwardCompatibilitySubstitutionRule compatibility_rule, ForwardCompatibilitySubstitutionRule compatibility_rule,
std::string &error, bool allow_source_manifest = true); std::string &error, bool allow_source_manifest = true);
// Resolve a system preset by name. The vendor tree is read from data_dir()/system when installed
// there, as the GUI reads it, and from the bundled profiles otherwise.
bool resolve_system_preset(DynamicPrintConfig &config, Preset::Type type, const std::string &name,
ForwardCompatibilitySubstitutionRule compatibility_rule, std::string &error);
// Load selections (current print, current filaments, current printer) from config.ini // Load selections (current print, current filaments, current printer) from config.ini
// This is done just once on application start up. // This is done just once on application start up.
@@ -622,36 +630,180 @@ public:
// default_filament_profile must resolve to a system filament. // default_filament_profile must resolve to a system filament.
bool check_printer_default_materials() const; bool check_printer_default_materials() const;
// Merge one vendor's presets with the other vendor's presets, report duplicates. // One vendor to load, and the directory it is installed in.
// Public so per-vendor-cache consumers (e.g. the setup wizard) can assemble a struct VendorSource
// bundle out of several per-vendor caches loaded into separate PresetBundle instances. {
std::vector<std::string> merge_presets(PresetBundle &&other); std::string name;
boost::filesystem::path dir;
};
// Load `vendors` into this bundle, the Orca filament library directly and every
// other vendor in parallel into a bundle of its own that inherits from it, merged
// in the order given. A vendor that cannot be loaded has its error added to the
// returned text, or thrown in validation mode, and its name to `failed`; it is
// left out, except for the library, which keeps what it installed before the
// failure. Once `cancel` is set, no further vendor starts loading.
std::pair<PresetsConfigSubstitutions, std::string> load_vendors(const std::vector<VendorSource>& vendors,
ForwardCompatibilitySubstitutionRule compatibility_rule, bool allow_cache,
const std::atomic<bool>* cancel = nullptr, std::vector<std::string>* failed = nullptr);
private: private:
// Load one vendor from the preset cache installed in `dir`, judged against // Move the presets and vendor profiles of `others` into this bundle, in one pass
// the vendor profile there. False, with this bundle left clean, when there // over each collection. A preset whose name this bundle or an earlier one of
// is no usable cache and the vendor has to be parsed. This is how // `others` already has is left out and listed under the bundle that repeats it.
// load_vendor_configs_from_json reads a cache. std::vector<std::vector<std::string>> merge_presets(const std::vector<PresetBundle*> &others);
bool load_vendor_cache(const boost::filesystem::path& dir, const std::string& vendor_name, const PresetBundle* base_bundle);
// Load one source-form preset entry into this bundle: resolve `inherits` // What parsing one entry's JSON sub-file reported. Its errors and warnings are
// and `include`, flatten, validate and register the preset. Returns the // logged when the entry installs, so they come out in listing order with the
// reason loading failed, empty on success. See the definition for the // entry's install errors, as parsing and installing one entry at a time leaves them.
// sharing contract between the JSON parse and the cache load. struct EntryParse
// retain_configs / retain_includes, when non-null, name the only presets {
// registered into config_maps / include_maps (a config copy each). The ConfigSubstitutions substitutions;
// cache load passes the names its entries inherit / include — the only // Counted in the bundle's error count.
// ones ever looked up again; the JSON parse retains all, not knowing what std::vector<std::string> errors;
// later subfiles name. std::vector<std::string> warnings;
std::string load_vendor_preset(const CachedPreset& entry, };
const std::string& path, const std::string& vendor_name,
const PresetBundle* base_bundle, // An EntryParse for each entry of the VendorCacheData list of the same name.
LoadConfigBundleAttributes flags, struct VendorParse
ConfigSubstitutionContext& substitution_context, PresetsConfigSubstitutions& substitutions, {
std::map<std::string, DynamicPrintConfig>& config_maps, std::map<std::string, DynamicPrintConfig>& include_maps, std::vector<EntryParse> process_entries;
std::map<std::string, std::string>& filament_id_maps, std::vector<EntryParse> filament_entries;
PresetCollection* presets_collection, size_t& count, bool is_from_lib, std::vector<EntryParse> machine_entries;
const std::set<std::string>* retain_configs = nullptr, const std::set<std::string>* retain_includes = nullptr); };
// One vendor read from its cache or its JSONs by read_vendor, for
// install_vendor_read to install.
struct VendorRead
{
std::string dir;
std::string vendor_name;
LoadConfigBundleAttributes flags;
ForwardCompatibilitySubstitutionRule compatibility_rule;
// The errors this bundle had counted before the read, which the cache stamp leaves out.
int errors_at_entry { 0 };
// A whole-vendor load, which can read a cache and write one.
bool cacheable { false };
// Read from the cache at cache_path, which install can still reject.
bool from_cache { false };
std::string cache_path;
// Only the vendor profile was asked for.
bool vendor_only { false };
VendorCacheData data;
// What each entry's JSON parse reported, and whether and with which version
// the cache is written once the entries install.
VendorParse parsed;
bool will_cache { false };
std::string version;
// The sub-file the parse stopped at, its kind, why, and the errors it reported.
std::string reason;
std::string failed_subfile;
const char* failed_kind { nullptr };
std::vector<std::string> failed_errors;
};
// Read a vendor into this bundle's vendor profiles and `data`, from its cache
// when one covers it, else from its JSONs; nothing is installed. Throws
// ConfigurationError when the vendor's own JSON cannot be parsed.
VendorRead read_vendor(const std::string& dir, const std::string& vendor_name, LoadConfigBundleAttributes flags,
ForwardCompatibilitySubstitutionRule compatibility_rule, bool allow_cache);
// Parse the vendor's JSONs into `read`, up to the first sub-file that fails.
void parse_vendor_json(VendorRead& read);
// Install what read_vendor read, against base_bundle's filament library. A cache
// that cannot be installed is replaced by a parse of the JSONs. Throws
// ConfigurationError at the first entry that cannot be installed, or after
// installing the entries before a sub-file that could not be parsed.
std::pair<PresetsConfigSubstitutions, size_t> install_vendor_read(VendorRead&& read, const PresetBundle* base_bundle);
// Install a cache's entries. False, with this bundle left clean, when one of them
// cannot be installed.
bool install_vendor_cache(const std::string& cache_path, const std::string& vendor_name, VendorCacheData&& data,
const PresetBundle* base_bundle);
// Log and count errors reported by a resolve or a parse.
void log_errors(const std::vector<std::string>& errors);
// The state of installing one collection of one vendor, which
// resolve_vendor_preset reads through a const reference and only
// commit_vendor_preset writes.
struct VendorInstall
{
// The directory holding <vendor_name>/, whose sub-paths the entries name.
std::string path;
std::string vendor_name;
const VendorProfile* vendor_profile;
const PresetBundle* base_bundle;
LoadConfigBundleAttributes flags;
PresetCollection* presets;
// The Orca filament library, which keeps every config for other vendors to
// resolve against.
bool is_from_lib;
PresetsConfigSubstitutions* substitutions;
// The names some entry inherits / includes, the only ones whose configs /
// include diffs are looked up again.
std::set<std::string> inherited;
std::set<std::string> included;
std::map<std::string, DynamicPrintConfig> config_maps;
std::map<std::string, DynamicPrintConfig> include_maps;
std::map<std::string, std::string> filament_id_maps;
std::unordered_set<std::string> installed_names;
size_t count { 0 };
};
// Install a vendor's source-form entries, parsed from its JSON or read from its
// cache: processes, then filaments, then printers. Both loads go through here,
// so a cache-loaded bundle cannot come out different from a JSON-loaded one.
// `parsed` is given for entries parsed just now. `complete` says the entries
// are the vendor's whole lists; only then are the filament library's configs
// and filament ids left in m_config_maps and m_filament_id_maps. Returns the
// number of presets installed, and throws ConfigurationError at the first
// entry that cannot be installed.
size_t install_vendor(const std::string& path, const std::string& vendor_name, const PresetBundle* base_bundle,
LoadConfigBundleAttributes flags, const VendorCacheData& entries,
VendorParse* parsed, bool complete, PresetsConfigSubstitutions& substitutions);
// Install one collection's entries in the order they are listed.
void install_vendor_entries(VendorInstall& install, const std::vector<CachedPreset>& entries,
std::vector<EntryParse>* parsed);
// One entry flattened against the preset it inherits, before anything this
// bundle shares has been touched.
struct PresetInstall
{
DynamicPrintConfig config;
std::string file_path;
// Empty when the preset is its own alias.
std::string alias;
std::string filament_id;
std::vector<std::string> renamed_from;
// Reported by commit, so resolving entries together leaves the log and
// the error count as one entry at a time produces them.
std::vector<std::string> errors;
// What a base states for the presets that include it, when it is retained.
std::optional<DynamicPrintConfig> included;
// The config kept for the entries that inherit this one, or for other
// vendors when this is the filament library.
std::optional<DynamicPrintConfig> retained;
// Not instantiated, so it contributes a config and no preset.
bool config_only { false };
// Non-empty when the entry is rejected, and says why.
std::string reason;
};
// Flatten one entry against the config it inherits, from this collection's
// config_maps or base_bundle's filament library, with the include diffs it
// names layered in. It looks up nothing but the names the entry inherits and
// includes, and writes nothing.
PresetInstall resolve_vendor_preset(const CachedPreset& entry, const VendorInstall& install) const;
// Install a resolved entry. The collections, the maps in `install` and the
// error count are touched here and only here, one entry at a time.
// Presets are appended, so a repeated name is caught with `installed_names`,
// and the collection is sorted once every entry is in.
std::string commit_vendor_preset(const CachedPreset& entry, PresetInstall&& resolved,
ConfigSubstitutions&& substitutions, VendorInstall& install);
// Clear every collection's m_printer_hold_alias, which reset() leaves alone. // Clear every collection's m_printer_hold_alias, which reset() leaves alone.
void clear_printer_hold_aliases(); void clear_printer_hold_aliases();
@@ -663,13 +815,14 @@ private:
// Vendor trees loaded by resolve_preset_config's manifest path, so every preset // Vendor trees loaded by resolve_preset_config's manifest path, so every preset
// resolved through this bundle shares one load per source root and vendor. The // resolved through this bundle shares one load per source root and vendor. The
// filament library is one such tree, shared by every vendor under its root. // filament library is one such tree, shared by every vendor under its root.
std::map<std::tuple<std::string, std::string, ForwardCompatibilitySubstitutionRule>, std::unique_ptr<PresetBundle>> // A tree read from its preset cache is kept apart: its presets carry no source file.
std::map<std::tuple<std::string, std::string, ForwardCompatibilitySubstitutionRule, bool>, std::unique_ptr<PresetBundle>>
m_source_vendor_bundles; m_source_vendor_bundles;
const PresetBundle *load_source_vendor(const boost::filesystem::path &root_dir, const PresetBundle *load_source_vendor(const boost::filesystem::path &root_dir,
const std::string &vendor_id, const std::string &vendor_id,
ForwardCompatibilitySubstitutionRule compatibility_rule, ForwardCompatibilitySubstitutionRule compatibility_rule,
std::string &error); std::string &error, bool allow_cache = false);
// Orca: validation only - flag any printer with two or more compatible // Orca: validation only - flag any printer with two or more compatible
// filament presets sharing one filament_id (ambiguous AMS subtype match). // filament presets sharing one filament_id (ambiguous AMS subtype match).
+1 -1
View File
@@ -109,7 +109,7 @@ void skip_config(cereal::BinaryInputArchive& ar, const CacheDictionary& dict);
// One preset as its JSON subfile states it: the config diff, the names of the // One preset as its JSON subfile states it: the config diff, the names of the
// preset it inherits and the presets it includes, and the parse metadata — // preset it inherits and the presets it includes, and the parse metadata —
// everything the parse phase of load_vendor_configs_from_json extracts and // everything PresetBundle::parse_vendor_json extracts and
// nothing it derives. Inheritance and includes are resolved when the entry is // nothing it derives. Inheritance and includes are resolved when the entry is
// installed, against whatever filament library is loaded then, so a cache // installed, against whatever filament library is loaded then, so a cache
// carries no other vendor's values and no other vendor's update can make it // carries no other vendor's values and no other vendor's update can make it
+11 -5
View File
@@ -2439,12 +2439,18 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
if (m_default_region_config.precise_outer_wall && m_default_region_config.wall_sequence != WallSequence::InnerOuter) if (m_default_region_config.precise_outer_wall && m_default_region_config.wall_sequence != WallSequence::InnerOuter)
warn(L("The precise wall option will be ignored for outer-inner or inner-outer-inner wall sequences."), "precise_outer_wall"); warn(L("The precise wall option will be ignored for outer-inner or inner-outer-inner wall sequences."), "precise_outer_wall");
// check adaptive pressure advance model // check adaptive pressure advance model of every extruder variant column of the used filaments
for (unsigned int extruder_id : extruders) { const std::vector<int> &self_index = m_config.filament_self_index.values;
if (m_config.adaptive_pressure_advance.get_at(extruder_id) && const size_t pa_columns = std::max(m_config.adaptive_pressure_advance_model.size(), size_t(extruders.back()) + 1);
m_config.enable_pressure_advance.get_at(extruder_id)) { for (size_t column = 0; column < pa_columns; ++column) {
// filament_self_index maps a column to its filament once the filament arrays hold one column per variant
const unsigned int filament_id = self_index.size() == pa_columns ? self_index[column] - 1 : column;
if (!std::binary_search(extruders.begin(), extruders.end(), filament_id))
continue;
if (m_config.adaptive_pressure_advance.get_at(column) &&
m_config.enable_pressure_advance.get_at(column)) {
const std::string pa_model = m_config.adaptive_pressure_advance_model.get_at(extruder_id); const std::string pa_model = m_config.adaptive_pressure_advance_model.get_at(column);
if (!pa_model.empty()) { if (!pa_model.empty()) {
std::string validation_error = AdaptivePAProcessor::validate_adaptive_pa_model(pa_model); std::string validation_error = AdaptivePAProcessor::validate_adaptive_pa_model(pa_model);
if (!validation_error.empty()) { if (!validation_error.empty()) {
+31
View File
@@ -9403,6 +9403,10 @@ void PrintConfigDef::handle_legacy_composite(DynamicPrintConfig &config)
} }
config.set_key_value("wiping_volumes_use_custom_matrix", new ConfigOptionBool(custom)); config.set_key_value("wiping_volumes_use_custom_matrix", new ConfigOptionBool(custom));
} }
// Orca: a config saved before a key joined filament_options_with_variant stores it once per filament
// rather than once per filament variant, and one exported by an older CLI may store a single value.
normalize_filament_values_to_variants(config);
} }
const PrintConfigDef print_config_def; const PrintConfigDef print_config_def;
@@ -9505,6 +9509,13 @@ std::set<std::string> filament_options_with_variant = {
"filament_ironing_spacing", "filament_ironing_spacing",
"filament_ironing_inset", "filament_ironing_inset",
"filament_ironing_speed", "filament_ironing_speed",
// Orca: pressure advance
"enable_pressure_advance",
"pressure_advance",
"adaptive_pressure_advance",
"adaptive_pressure_advance_model",
"adaptive_pressure_advance_overhangs",
"adaptive_pressure_advance_bridges",
"activate_air_filtration", "activate_air_filtration",
"activate_air_filtration_during_print", "activate_air_filtration_during_print",
"activate_air_filtration_on_completion", "activate_air_filtration_on_completion",
@@ -10712,6 +10723,25 @@ void set_variant_override(ConfigOptionVectorBase &target, const ConfigOptionVect
target.set_to_index(&source, indices, stride); target.set_to_index(&source, indices, stride);
} }
void normalize_filament_values_to_variants(DynamicPrintConfig &config)
{
const auto *self_index = config.option<ConfigOptionInts>("filament_self_index");
if (self_index == nullptr || self_index->empty())
return;
const int filament_count = *std::max_element(self_index->values.begin(), self_index->values.end());
if (filament_count <= 0 || size_t(filament_count) >= self_index->size())
return;
for (const std::string &key : filament_options_with_variant) {
auto *opt = dynamic_cast<ConfigOptionVectorBase *>(config.option(key));
if (opt == nullptr || (opt->size() != size_t(filament_count) && opt->size() != 1))
continue;
std::unique_ptr<ConfigOption> per_filament(opt->clone());
// set_at() takes the first value for a filament past the end of a single-value vector
for (size_t variant = 0; variant < self_index->size(); ++variant)
opt->set_at(per_filament.get(), variant, self_index->values[variant] - 1);
}
}
//used for object/region config //used for object/region config
//use the smallest of multiple to single //use the smallest of multiple to single
@@ -12565,6 +12595,7 @@ OtherSlicingStatesConfigDef::OtherSlicingStatesConfigDef()
new_def("initial_no_support_extruder", coInt, "Initial no support extruder", "Zero-based index of the first extruder used for printing without support. Same as initial_no_support_tool."); new_def("initial_no_support_extruder", coInt, "Initial no support extruder", "Zero-based index of the first extruder used for printing without support. Same as initial_no_support_tool.");
new_def("in_head_wrap_detect_zone", coBool, "In head wrap detect zone", "Indicates if the first layer overlaps with the head wrap zone."); new_def("in_head_wrap_detect_zone", coBool, "In head wrap detect zone", "Indicates if the first layer overlaps with the head wrap zone.");
new_def("curr_bed_type", coString, "Current bed type", "Name of the currently selected bed plate type (e.g. 'Textured PEI Plate', 'Smooth High Temp Plate').");
} }
PrintStatisticsConfigDef::PrintStatisticsConfigDef() PrintStatisticsConfigDef::PrintStatisticsConfigDef()
+5
View File
@@ -884,6 +884,11 @@ extern std::set<std::string> empty_options;
void set_variant_override(ConfigOptionVectorBase &target, const ConfigOptionVectorBase &source, void set_variant_override(ConfigOptionVectorBase &target, const ConfigOptionVectorBase &source,
const std::vector<int> &variant_index, int stride = 1); const std::vector<int> &variant_index, int stride = 1);
// Orca: lays every filament_options_with_variant option out one value per filament variant, as
// filament_self_index maps the variants to filaments. An option holding one value per filament, or a
// single value, gives every variant of a filament that filament's value; other lengths are left alone.
void normalize_filament_values_to_variants(DynamicPrintConfig &config);
extern std::set<std::string> filament_dev_options; extern std::set<std::string> filament_dev_options;
extern void update_static_print_config_from_dynamic(ConfigBase& config, const DynamicPrintConfig& dest_config, std::vector<int> variant_index, std::set<std::string>& key_set1, int stride = 1); extern void update_static_print_config_from_dynamic(ConfigBase& config, const DynamicPrintConfig& dest_config, std::vector<int> variant_index, std::set<std::string>& key_set1, int stride = 1);
+10 -1
View File
@@ -256,10 +256,19 @@ extern bool is_gallery_file(const std::string& path, char const* type);
extern bool is_shapes_dir(const std::string& dir); extern bool is_shapes_dir(const std::string& dir);
//BBS: add json support //BBS: add json support
extern bool is_json_file(const std::string& path); extern bool is_json_file(const std::string& path);
// True if rel_path is relative, has no ".." component and, joined to root, still resolves inside it. // True if rel_path is relative, has no ".." component or embedded NUL and, joined to root, still resolves inside it.
// Both '/' and '\\' are treated as separators on every platform, so an archive rejected on one OS // Both '/' and '\\' are treated as separators on every platform, so an archive rejected on one OS
// is rejected on all of them. // is rejected on all of them.
extern bool is_path_within_root(const std::string &rel_path, const boost::filesystem::path &root); extern bool is_path_within_root(const std::string &rel_path, const boost::filesystem::path &root);
// True if a symlink stored at link_rel_path (relative to root) with this target stays inside root: the target
// must be relative, and joined to the link's directory it must pass is_path_within_root.
extern bool is_symlink_target_within_root(const std::string &link_rel_path, const std::string &target, const boost::filesystem::path &root);
// True if path names an entry strictly inside root: it must be spelled with root as its prefix,
// and must still resolve inside root once symlinks are followed.
extern bool is_absolute_path_within_root(const boost::filesystem::path &path, const boost::filesystem::path &root);
// True if a file with this name is of a type that the desktop opens as plain content, so it cannot run code.
// Anything unknown is not safe.
extern bool is_safe_to_open_file_name(const std::string &file_name);
// Orca: custom protocal support utils // Orca: custom protocal support utils
inline bool is_orca_open(const std::string& url) { return boost::starts_with(url, "orcaslicer://open"); } inline bool is_orca_open(const std::string& url) { return boost::starts_with(url, "orcaslicer://open"); }
+63
View File
@@ -4,6 +4,9 @@
#include "miniz_extension.hpp" #include "miniz_extension.hpp"
#include "Utils.hpp" #include "Utils.hpp"
#include <boost/filesystem.hpp>
#include <boost/log/trivial.hpp>
#if defined(_MSC_VER) || defined(__MINGW64__) #if defined(_MSC_VER) || defined(__MINGW64__)
#include "boost/nowide/cstdio.hpp" #include "boost/nowide/cstdio.hpp"
#endif #endif
@@ -115,6 +118,66 @@ std::string decode_archive_entry_path(mz_zip_archive *zip, const mz_zip_archive_
return decode_zip_unicode_path_extra_field(extra.substr(0, extra_size > 0 ? extra_size - 1 : 0), stat.m_filename); return decode_zip_unicode_path_extra_field(extra.substr(0, extra_size > 0 ? extra_size - 1 : 0), stat.m_filename);
} }
bool extract_archive_confined(const std::string &zip_path_utf8, const std::string &dest_dir)
{
mz_zip_archive archive;
mz_zip_zero_struct(&archive);
if (!open_zip_reader(&archive, zip_path_utf8)) {
BOOST_LOG_TRIVIAL(error) << "Unable to open zip reader for " << zip_path_utf8;
return false;
}
const mz_uint num_entries = mz_zip_reader_get_num_files(&archive);
mz_zip_archive_file_stat stat;
// Validate every entry first so an archive with a single escaping entry leaves no partial output behind.
const boost::filesystem::path root(dest_dir);
for (mz_uint i = 0; i < num_entries; ++i) {
if (mz_zip_reader_file_stat(&archive, i, &stat) && !is_path_within_root(stat.m_filename, root)) {
BOOST_LOG_TRIVIAL(error) << "Unzip: rejecting " << zip_path_utf8 << ", entry " << stat.m_filename << " resolves outside " << dest_dir;
close_zip_reader(&archive);
return false;
}
}
for (mz_uint i = 0; i < num_entries; ++i) {
if (!mz_zip_reader_file_stat(&archive, i, &stat)) {
BOOST_LOG_TRIVIAL(warning) << "Unzip: read file stat failed";
continue;
}
const std::string dest_file = dest_dir + "/" + stat.m_filename;
try {
if (stat.m_is_directory) {
const boost::filesystem::path dest_path(dest_file);
if (!boost::filesystem::exists(dest_path))
boost::filesystem::create_directories(dest_path);
continue;
}
if (stat.m_uncomp_size == 0) {
BOOST_LOG_TRIVIAL(warning) << "Unzip: invalid size for file " << stat.m_filename;
continue;
}
// Replace a symlink at the destination rather than writing through it.
const boost::filesystem::path dest_path(dest_file);
if (boost::filesystem::is_symlink(boost::filesystem::symlink_status(dest_path)))
boost::filesystem::remove(dest_path);
if (!mz_zip_reader_extract_to_file(&archive, stat.m_file_index, dest_file.c_str(), 0)) {
BOOST_LOG_TRIVIAL(error) << "Unzip: extract file " << stat.m_filename << " to dest " << dest_file << " failed";
close_zip_reader(&archive);
return false;
}
BOOST_LOG_TRIVIAL(info) << "Unzip: successfully extract file " << stat.m_file_index << " to " << dest_file;
} catch (const std::exception &e) {
close_zip_reader(&archive);
BOOST_LOG_TRIVIAL(error) << "Unzip: archive read exception: " << e.what();
return false;
}
}
close_zip_reader(&archive);
return true;
}
MZ_Archive::MZ_Archive() MZ_Archive::MZ_Archive()
{ {
mz_zip_zero_struct(&arch); mz_zip_zero_struct(&arch);
+2
View File
@@ -11,6 +11,8 @@ bool open_zip_writer(mz_zip_archive *zip, const std::string &fname_utf8);
bool close_zip_reader(mz_zip_archive *zip); bool close_zip_reader(mz_zip_archive *zip);
bool close_zip_writer(mz_zip_archive *zip); bool close_zip_writer(mz_zip_archive *zip);
std::string decode_archive_entry_path(mz_zip_archive *zip, const mz_zip_archive_file_stat &stat); std::string decode_archive_entry_path(mz_zip_archive *zip, const mz_zip_archive_file_stat &stat);
// Extracts every entry of the archive under dest_dir. Nothing is written if any entry would resolve outside dest_dir.
bool extract_archive_confined(const std::string &zip_path_utf8, const std::string &dest_dir);
class MZ_Archive { class MZ_Archive {
public: public:
+39 -1
View File
@@ -70,6 +70,7 @@
#include <boost/shared_ptr.hpp> #include <boost/shared_ptr.hpp>
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <boost/filesystem/path.hpp> #include <boost/filesystem/path.hpp>
#include <boost/nowide/fstream.hpp> #include <boost/nowide/fstream.hpp>
@@ -1093,6 +1094,9 @@ bool is_path_within_root(const std::string &rel_path, const boost::filesystem::p
auto is_separator = [](char c) { return c == '/' || c == '\\'; }; auto is_separator = [](char c) { return c == '/' || c == '\\'; };
if (rel_path.empty() || is_separator(rel_path.front()) || (rel_path.size() > 1 && rel_path[1] == ':')) if (rel_path.empty() || is_separator(rel_path.front()) || (rel_path.size() > 1 && rel_path[1] == ':'))
return false; return false;
// The filesystem calls stop at a NUL, so they would act on a shorter path than the one checked here.
if (rel_path.find('\0') != std::string::npos)
return false;
for (size_t start = 0; start <= rel_path.size();) { for (size_t start = 0; start <= rel_path.size();) {
size_t end = start; size_t end = start;
while (end < rel_path.size() && !is_separator(rel_path[end])) while (end < rel_path.size() && !is_separator(rel_path[end]))
@@ -1103,7 +1107,10 @@ bool is_path_within_root(const std::string &rel_path, const boost::filesystem::p
} }
// Resolve against the canonical root so a symlink inside it cannot lead back out. // Resolve against the canonical root so a symlink inside it cannot lead back out.
try { try {
const std::string root_str = boost::filesystem::weakly_canonical(root).string(); std::string root_str = boost::filesystem::weakly_canonical(root).string();
// A trailing separator on root would otherwise fail the prefix match below for every path.
while (!root_str.empty() && (root_str.back() == '/' || root_str.back() == boost::filesystem::path::preferred_separator))
root_str.pop_back();
const std::string full_str = boost::filesystem::weakly_canonical(root / rel_path).string(); const std::string full_str = boost::filesystem::weakly_canonical(root / rel_path).string();
return full_str.compare(0, root_str.size(), root_str) == 0 && return full_str.compare(0, root_str.size(), root_str) == 0 &&
(full_str.size() == root_str.size() || full_str[root_str.size()] == boost::filesystem::path::preferred_separator); (full_str.size() == root_str.size() || full_str[root_str.size()] == boost::filesystem::path::preferred_separator);
@@ -1112,6 +1119,37 @@ bool is_path_within_root(const std::string &rel_path, const boost::filesystem::p
} }
} }
bool is_symlink_target_within_root(const std::string &link_rel_path, const std::string &target, const boost::filesystem::path &root)
{
if (target.empty() || target.front() == '/' || target.front() == '\\' || (target.size() > 1 && target[1] == ':'))
return false;
// A relative target without ".." only descends from the link's directory, so no chain of such links can leave root.
const size_t sep = link_rel_path.find_last_of("/\\");
return is_path_within_root((sep == std::string::npos ? std::string() : link_rel_path.substr(0, sep + 1)) + target, root);
}
bool is_absolute_path_within_root(const boost::filesystem::path &path, const boost::filesystem::path &root)
{
const boost::filesystem::path rel = path.lexically_relative(root);
return !rel.empty() && rel != "." && is_path_within_root(rel.string(), root);
}
bool is_safe_to_open_file_name(const std::string &file_name)
{
// Formats that cannot carry macros or scripts. Legacy and OpenDocument office files, HTML and SVG are left out on purpose.
static const std::vector<std::string> safe_extensions = {
"jpg", "jpeg", "jfif", "pjpeg", "pjp", "png", "gif", "bmp", "webp", "tif", "tiff",
"pdf", "txt", "md", "csv", "docx", "xlsx", "pptx",
"stl", "obj", "3mf", "amf", "ply", "step", "stp", "iges", "igs", "dxf",
"mp4", "mov", "webm"};
// The name must end in the extension itself: Windows drops trailing dots and spaces and reads ':' as a stream separator.
const size_t dot = file_name.find_last_of('.');
if (dot == std::string::npos || file_name.find_first_of("/\\:") != std::string::npos)
return false;
const std::string extension = boost::algorithm::to_lower_copy(file_name.substr(dot + 1));
return std::find(safe_extensions.begin(), safe_extensions.end(), extension) != safe_extensions.end();
}
bool is_img_file(const std::string &path) bool is_img_file(const std::string &path)
{ {
return boost::iends_with(path, ".png") || boost::iends_with(path, ".svg"); return boost::iends_with(path, ".png") || boost::iends_with(path, ".svg");
+22 -14
View File
@@ -236,6 +236,7 @@ set(SLIC3R_GUI_SOURCES
GUI/GLToolbar.hpp GUI/GLToolbar.hpp
GUI/ImageDPIFrame.cpp GUI/ImageDPIFrame.cpp
GUI/ImageDPIFrame.hpp GUI/ImageDPIFrame.hpp
GUI/IMediaController.hpp
GUI/GUI_App.cpp GUI/GUI_App.cpp
GUI/GUI_App.hpp GUI/GUI_App.hpp
GUI/GUI_AuxiliaryList.cpp GUI/GUI_AuxiliaryList.cpp
@@ -365,6 +366,8 @@ set(SLIC3R_GUI_SOURCES
GUI/MediaFilePanel.h GUI/MediaFilePanel.h
GUI/MediaPlayCtrl.cpp GUI/MediaPlayCtrl.cpp
GUI/MediaPlayCtrl.h GUI/MediaPlayCtrl.h
GUI/WebRtcMediaController.cpp
GUI/WebRtcMediaController.hpp
GUI/MeshUtils.cpp GUI/MeshUtils.cpp
GUI/MeshUtils.hpp GUI/MeshUtils.hpp
GUI/ModelMall.cpp GUI/ModelMall.cpp
@@ -560,6 +563,8 @@ set(SLIC3R_GUI_SOURCES
GUI/WebUserLoginDialog.hpp GUI/WebUserLoginDialog.hpp
GUI/WebViewDialog.cpp GUI/WebViewDialog.cpp
GUI/WebViewDialog.hpp GUI/WebViewDialog.hpp
GUI/WebMediaController.hpp
GUI/WebMediaController.cpp
GUI/Widgets/AMSControl.cpp GUI/Widgets/AMSControl.cpp
GUI/Widgets/AMSControl.hpp GUI/Widgets/AMSControl.hpp
GUI/Widgets/AMSItem.cpp GUI/Widgets/AMSItem.cpp
@@ -754,10 +759,15 @@ set(SLIC3R_GUI_SOURCES
Utils/NetworkAgentFactory.cpp Utils/NetworkAgentFactory.cpp
Utils/ICloudServiceAgent.hpp Utils/ICloudServiceAgent.hpp
Utils/IPrinterAgent.hpp Utils/IPrinterAgent.hpp
Utils/ICameraSignalingChannel.hpp
Utils/OrcaCloudServiceAgent.cpp Utils/OrcaCloudServiceAgent.cpp
Utils/OrcaCloudServiceAgent.hpp Utils/OrcaCloudServiceAgent.hpp
Utils/OrcaMqttConnection.cpp
Utils/OrcaMqttConnection.hpp
Utils/OrcaPrinterAgent.cpp Utils/OrcaPrinterAgent.cpp
Utils/OrcaPrinterAgent.hpp Utils/OrcaPrinterAgent.hpp
Utils/OrcaCloudSignalingChannel.cpp
Utils/OrcaCloudSignalingChannel.hpp
Utils/QidiPrinterAgent.cpp Utils/QidiPrinterAgent.cpp
Utils/QidiPrinterAgent.hpp Utils/QidiPrinterAgent.hpp
Utils/SnapmakerPrinterAgent.cpp Utils/SnapmakerPrinterAgent.cpp
@@ -924,7 +934,7 @@ else()
set(_opengl_link_lib OpenGL::GL) set(_opengl_link_lib OpenGL::GL)
endif() endif()
target_link_libraries(libslic3r_gui libslic3r cereal::cereal imgui imguizmo minilzo libvgcode md4c-html glad ${_opengl_link_lib} hidapi mdns ${wxWidgets_LIBRARIES} glfw libcurl OpenSSL::SSL OpenSSL::Crypto noise::noise pybind11::embed) target_link_libraries(libslic3r_gui libslic3r cereal::cereal imgui imguizmo minilzo libvgcode md4c-html glad ${_opengl_link_lib} hidapi mdns ${wxWidgets_LIBRARIES} glfw libcurl OpenSSL::SSL OpenSSL::Crypto LibDataChannel::LibDataChannel noise::noise pybind11::embed)
if (CMAKE_SYSTEM_NAME STREQUAL "Linux") if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
# Linux finds wxWidgets in module mode, whose include dirs and definitions # Linux finds wxWidgets in module mode, whose include dirs and definitions
@@ -981,29 +991,32 @@ endif ()
if (APPLE) if (APPLE)
# Static FFmpeg from the deps install: nothing to bundle into the .app, # Static FFmpeg from the deps install: nothing to bundle into the .app,
# no rpath/install_name handling. Order matters: avcodec -> swscale -> avutil. # no rpath/install_name handling. Order matters: avformat -> avcodec -> swscale -> avutil.
find_library(LIBAVFORMAT_LIBRARY NAMES libavformat.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
find_library(LIBAVCODEC_LIBRARY NAMES libavcodec.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH) find_library(LIBAVCODEC_LIBRARY NAMES libavcodec.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
find_library(LIBSWSCALE_LIBRARY NAMES libswscale.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH) find_library(LIBSWSCALE_LIBRARY NAMES libswscale.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
find_library(LIBAVUTIL_LIBRARY NAMES libavutil.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH) find_library(LIBAVUTIL_LIBRARY NAMES libavutil.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
if (NOT LIBAVCODEC_LIBRARY OR NOT LIBSWSCALE_LIBRARY OR NOT LIBAVUTIL_LIBRARY) if (NOT LIBAVFORMAT_LIBRARY OR NOT LIBAVCODEC_LIBRARY OR NOT LIBSWSCALE_LIBRARY OR NOT LIBAVUTIL_LIBRARY)
message(FATAL_ERROR "Static FFmpeg (libavcodec.a/libswscale.a/libavutil.a) not found under ${CMAKE_PREFIX_PATH}/lib. Rebuild the deps — FFMPEG builds static-only on macOS.") message(FATAL_ERROR "Static FFmpeg (libavformat.a/libavcodec.a/libswscale.a/libavutil.a) not found under ${CMAKE_PREFIX_PATH}/lib. Rebuild the deps — FFMPEG builds static-only on macOS.")
endif () endif ()
target_link_libraries(libslic3r_gui ${LIBAVCODEC_LIBRARY} ${LIBSWSCALE_LIBRARY} ${LIBAVUTIL_LIBRARY}) target_link_libraries(libslic3r_gui ${LIBAVFORMAT_LIBRARY} ${LIBAVCODEC_LIBRARY} ${LIBSWSCALE_LIBRARY} ${LIBAVUTIL_LIBRARY})
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${CMAKE_PREFIX_PATH}/include) target_include_directories(libslic3r_gui SYSTEM PRIVATE ${CMAKE_PREFIX_PATH}/include)
elseif (WIN32) elseif (WIN32)
# Prebuilt shared FFmpeg from the deps install. Windows has no pkg-config, # Prebuilt shared FFmpeg from the deps install. Windows has no pkg-config,
# so resolve the import libraries out of the deps prefix directly; the DLLs # so resolve the import libraries out of the deps prefix directly; the DLLs
# are copied next to the executable by the top level CMakeLists. # are copied next to the executable by the top level CMakeLists.
find_library(LIBAVFORMAT_LIBRARY NAMES avformat PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
find_library(LIBAVCODEC_LIBRARY NAMES avcodec PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH) find_library(LIBAVCODEC_LIBRARY NAMES avcodec PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
find_library(LIBSWSCALE_LIBRARY NAMES swscale PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH) find_library(LIBSWSCALE_LIBRARY NAMES swscale PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
find_library(LIBAVUTIL_LIBRARY NAMES avutil PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH) find_library(LIBAVUTIL_LIBRARY NAMES avutil PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
if (NOT LIBAVCODEC_LIBRARY OR NOT LIBSWSCALE_LIBRARY OR NOT LIBAVUTIL_LIBRARY) if (NOT LIBAVFORMAT_LIBRARY OR NOT LIBAVCODEC_LIBRARY OR NOT LIBSWSCALE_LIBRARY OR NOT LIBAVUTIL_LIBRARY)
message(FATAL_ERROR "FFmpeg (avcodec/swscale/avutil) not found under ${CMAKE_PREFIX_PATH}/lib. Rebuild the deps.") message(FATAL_ERROR "FFmpeg (avformat/avcodec/swscale/avutil) not found under ${CMAKE_PREFIX_PATH}/lib. Rebuild the deps.")
endif () endif ()
target_link_libraries(libslic3r_gui ${LIBAVCODEC_LIBRARY} ${LIBSWSCALE_LIBRARY} ${LIBAVUTIL_LIBRARY}) target_link_libraries(libslic3r_gui ${LIBAVFORMAT_LIBRARY} ${LIBAVCODEC_LIBRARY} ${LIBSWSCALE_LIBRARY} ${LIBAVUTIL_LIBRARY})
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${CMAKE_PREFIX_PATH}/include) target_include_directories(libslic3r_gui SYSTEM PRIVATE ${CMAKE_PREFIX_PATH}/include)
else () else ()
pkg_check_modules(LIBAV REQUIRED IMPORTED_TARGET pkg_check_modules(LIBAV REQUIRED IMPORTED_TARGET
libavformat
libavcodec libavcodec
libswscale libswscale
libavutil libavutil
@@ -1019,14 +1032,9 @@ if (UNIX AND NOT APPLE)
find_package(GTK${SLIC3R_GTK} REQUIRED) find_package(GTK${SLIC3R_GTK} REQUIRED)
pkg_check_modules(LIBSECRET REQUIRED libsecret-1) pkg_check_modules(LIBSECRET REQUIRED libsecret-1)
pkg_check_modules(webkit2gtk REQUIRED webkit2gtk-4.1) pkg_check_modules(webkit2gtk REQUIRED webkit2gtk-4.1)
if (FLATPAK)
# I don't know why this is needed, but for whatever reason slic3r isn't
# linking to X11 and webkit2gtk. force it.
find_package(X11 REQUIRED) find_package(X11 REQUIRED)
target_link_libraries(libslic3r_gui ${X11_LIBRARIES} ${webkit2gtk_LIBRARIES})
endif()
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${GTK${SLIC3R_GTK}_INCLUDE_DIRS} ${LIBSECRET_INCLUDE_DIRS} ${webkit2gtk_INCLUDE_DIRS}) target_include_directories(libslic3r_gui SYSTEM PRIVATE ${GTK${SLIC3R_GTK}_INCLUDE_DIRS} ${LIBSECRET_INCLUDE_DIRS} ${webkit2gtk_INCLUDE_DIRS})
target_link_libraries(libslic3r_gui ${GTK${SLIC3R_GTK}_LIBRARIES} fontconfig ${LIBSECRET_LIBRARIES}) target_link_libraries(libslic3r_gui ${GTK${SLIC3R_GTK}_LIBRARIES} fontconfig ${LIBSECRET_LIBRARIES} ${webkit2gtk_LIBRARIES} ${X11_LIBRARIES})
# Propagate GDK backend detection results as compile definitions so that # Propagate GDK backend detection results as compile definitions so that
# LinuxDisplayBackend.cpp can include the right GDK headers. # LinuxDisplayBackend.cpp can include the right GDK headers.
+27
View File
@@ -45,6 +45,25 @@ int AVVideoDecoder::open(Bambu_StreamInfo const &info)
return 0; return 0;
} }
int AVVideoDecoder::open(AVCodecParameters const &parameters)
{
if (avcodec_parameters_to_context(codec_ctx_, &parameters) < 0)
return -1;
auto codec = avcodec_find_decoder(codec_ctx_->codec_id);
if (codec == nullptr) {
fprintf(stderr, "AVVideoDecoder: unsupported codec!\n");
return -1;
}
if (avcodec_open2(codec_ctx_, codec, nullptr) < 0) {
fprintf(stderr, "AVVideoDecoder: could not open codec\n");
return -1;
}
frame_ = av_frame_alloc();
return frame_ == nullptr ? -1 : 0;
}
int AVVideoDecoder::decode(const Bambu_Sample &sample) int AVVideoDecoder::decode(const Bambu_Sample &sample)
{ {
int ret = -1; int ret = -1;
@@ -71,6 +90,14 @@ int AVVideoDecoder::decode(const Bambu_Sample &sample)
return ret; return ret;
} }
int AVVideoDecoder::decode(const AVPacket &packet)
{
int ret = avcodec_send_packet(codec_ctx_, &packet);
if (ret == 0)
got_frame_ = avcodec_receive_frame(codec_ctx_, frame_) == 0;
return ret;
}
int AVVideoDecoder::flush() int AVVideoDecoder::flush()
{ {
int ret = avcodec_send_packet(codec_ctx_, nullptr); int ret = avcodec_send_packet(codec_ctx_, nullptr);
+10
View File
@@ -23,8 +23,10 @@ public:
public: public:
int open(Bambu_StreamInfo const &info); int open(Bambu_StreamInfo const &info);
int open(AVCodecParameters const &parameters);
int decode(Bambu_Sample const &sample); int decode(Bambu_Sample const &sample);
int decode(AVPacket const &packet);
int flush(); int flush();
@@ -34,6 +36,14 @@ public:
bool toWxBitmap(wxBitmap &bitmap, wxSize const & size); bool toWxBitmap(wxBitmap &bitmap, wxSize const & size);
// Native size of the most recently decoded frame, or an unspecified size if
// nothing has decoded yet. Lets a caller learn the video dimensions when the
// container/probe could not report them up front.
wxSize decoded_frame_size() const
{
return got_frame_ && frame_ ? wxSize{frame_->width, frame_->height} : wxSize{};
}
private: private:
AVCodecContext *codec_ctx_ = nullptr; AVCodecContext *codec_ctx_ = nullptr;
AVFrame * frame_ = nullptr; AVFrame * frame_ = nullptr;
+1 -1
View File
@@ -428,7 +428,7 @@ void AuFile::on_dclick(wxMouseEvent &evt)
if (m_type == AddFileButton) if (m_type == AddFileButton)
return; return;
else else
wxLaunchDefaultApplication(m_file_path.wstring(), 0); desktop_open_project_attachment(this, m_file_path);
} }
void AuFile::on_mouse_left_up(wxMouseEvent &evt) void AuFile::on_mouse_left_up(wxMouseEvent &evt)
+1 -1
View File
@@ -328,7 +328,7 @@ void SelectMObjectPopup::update_user_devices()
} }
m_bind_machine_list.clear(); m_bind_machine_list.clear();
m_bind_machine_list = dev->get_my_machine_list(); m_bind_machine_list = dev->get_my_machine_list(dev->get_current_printer_agent_id());
//sort list //sort list
std::vector<std::pair<std::string, MachineObject*>> user_machine_list; std::vector<std::pair<std::string, MachineObject*>> user_machine_list;
+16 -29
View File
@@ -6,8 +6,10 @@
#include "Widgets/Label.hpp" #include "Widgets/Label.hpp"
#include "MsgDialog.hpp" #include "MsgDialog.hpp"
#include "libslic3r/Print.hpp" #include "libslic3r/Print.hpp"
#include "PrePrintChecker.hpp"
#include "DeviceCore/DevConfig.h" #include "DeviceCore/DevConfig.h"
#include "DeviceCore/DevConfigUtil.h"
#include "DeviceCore/DevExtruderSystem.h" #include "DeviceCore/DevExtruderSystem.h"
#include "DeviceCore/DevFilaBlackList.h" #include "DeviceCore/DevFilaBlackList.h"
#include "DeviceCore/DevFilaSystem.h" #include "DeviceCore/DevFilaSystem.h"
@@ -1637,29 +1639,6 @@ void CalibrationPresetPage::update_combobox_filaments(MachineObject* obj)
select_default_compatible_filament(); select_default_compatible_filament();
} }
bool CalibrationPresetPage::is_blocking_printing()
{
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
if (!dev) return true;
MachineObject* obj_ = dev->get_selected_machine();
if (obj_ == nullptr) return true;
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
auto source_model = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle);
auto target_model = obj_->printer_type;
if (source_model != target_model) {
std::vector<std::string> compatible_machine = obj_->get_compatible_machine();
vector<std::string>::iterator it = find(compatible_machine.begin(), compatible_machine.end(), source_model);
if (it == compatible_machine.end()) {
return true;
}
}
return false;
}
bool CalibrationPresetPage::is_nozzle_info_synced() const bool CalibrationPresetPage::is_nozzle_info_synced() const
{ {
if (!curr_obj || !curr_obj->is_info_ready()) if (!curr_obj || !curr_obj->is_info_ready())
@@ -1741,11 +1720,13 @@ void CalibrationPresetPage::update_show_status()
} }
} }
//if (is_blocking_printing()) { bool has_optional_printer_model = DevPrinterConfigUtil::is_optional_printer_model_id(obj_->printer_type);
// show_status(CaliPresetPageStatus::CaliPresetStatusUnsupportedPrinter); if (PresetBundle *preset_bundle = wxGetApp().preset_bundle) {
// return; has_optional_printer_model = has_optional_printer_model ||
//} DevPrinterConfigUtil::is_optional_printer_model_id(
//else preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle));
}
if (obj_->is_connecting() || !obj_->is_connected()) { if (obj_->is_connecting() || !obj_->is_connected()) {
show_status(CaliPresetPageStatus::CaliPresetStatusInConnecting); show_status(CaliPresetPageStatus::CaliPresetStatusInConnecting);
return; return;
@@ -1797,7 +1778,9 @@ void CalibrationPresetPage::update_show_status()
return; return;
} }
show_status(CaliPresetPageStatus::CaliPresetStatusNormal); show_status(has_optional_printer_model ?
CaliPresetPageStatus::CaliPresetStatusOptionalPrinterModel :
CaliPresetPageStatus::CaliPresetStatusNormal);
} }
@@ -1851,6 +1834,10 @@ void CalibrationPresetPage::show_status(CaliPresetPageStatus status)
Layout(); Layout();
Fit(); Fit();
} }
else if (status == CaliPresetPageStatus::CaliPresetStatusOptionalPrinterModel) {
update_print_status_msg(PrePrintChecker::get_pre_state_msg(PrintDialogStatus::PrintStatusOptionalPrinterModel), true);
Enable_Send_Button(true);
}
else if (status == CaliPresetPageStatus::CaliPresetStatusNoUserLogin) { else if (status == CaliPresetPageStatus::CaliPresetStatusNoUserLogin) {
wxString msg_text = _L("No login account, only printers in LAN mode are displayed."); wxString msg_text = _L("No login account, only printers in LAN mode are displayed.");
update_print_status_msg(msg_text, false); update_print_status_msg(msg_text, false);
@@ -152,7 +152,8 @@ enum CaliPresetPageStatus
CaliPresetStatusInConnecting, CaliPresetStatusInConnecting,
CaliPresetStatusFilamentIncompatible, CaliPresetStatusFilamentIncompatible,
CaliPresetStatusLanModeSDcardNotAvailable, CaliPresetStatusLanModeSDcardNotAvailable,
CaliPresetStatusDifferentNozzleDiameters CaliPresetStatusDifferentNozzleDiameters,
CaliPresetStatusOptionalPrinterModel
}; };
class CalibrationPresetPage : public CalibrationWizardPage class CalibrationPresetPage : public CalibrationWizardPage
@@ -268,7 +269,6 @@ protected:
bool is_nozzle_info_synced() const; bool is_nozzle_info_synced() const;
void show_status(CaliPresetPageStatus status); void show_status(CaliPresetPageStatus status);
void Enable_Send_Button(bool enable); void Enable_Send_Button(bool enable);
bool is_blocking_printing();
bool need_check_sdcard(MachineObject* obj); bool need_check_sdcard(MachineObject* obj);
CaliPresetPageStatus get_status() { return m_page_status; } CaliPresetPageStatus get_status() { return m_page_status; }
-60
View File
@@ -28,7 +28,6 @@ wxEND_EVENT_TABLE()
wxDEFINE_EVENT(EVT_VCAMERA_SWITCH, wxMouseEvent); wxDEFINE_EVENT(EVT_VCAMERA_SWITCH, wxMouseEvent);
wxDEFINE_EVENT(EVT_SDCARD_ABSENT_HINT, wxCommandEvent); wxDEFINE_EVENT(EVT_SDCARD_ABSENT_HINT, wxCommandEvent);
wxDEFINE_EVENT(EVT_CAM_SOURCE_CHANGE, wxCommandEvent);
#define CAMERAPOPUP_CLICK_INTERVAL 20 #define CAMERAPOPUP_CLICK_INTERVAL 20
@@ -102,34 +101,6 @@ CameraPopup::CameraPopup(wxWindow *parent)
top_sizer->Add(0, 0, wxALL, 0); top_sizer->Add(0, 0, wxALL, 0);
} }
// Orca: custom IP camera source — lets the user point Live Video at any camera URL (Orca feature; not in the reference)
m_custom_camera_input_confirm = new Button(m_panel, _L("Enable"));
m_custom_camera_input_confirm->SetBackgroundColor(wxColour(38, 166, 154));
m_custom_camera_input_confirm->SetBorderColor(wxColour(38, 166, 154));
m_custom_camera_input_confirm->SetTextColor(wxColour(0xFFFFFE));
m_custom_camera_input_confirm->SetFont(Label::Body_14);
m_custom_camera_input_confirm->SetMinSize(wxSize(FromDIP(90), FromDIP(30)));
m_custom_camera_input_confirm->SetPosition(wxDefaultPosition);
m_custom_camera_input_confirm->SetCornerRadius(FromDIP(12));
m_custom_camera_input = new TextInput(m_panel, wxEmptyString, wxEmptyString, wxEmptyString, wxDefaultPosition, wxDefaultSize);
m_custom_camera_input->GetTextCtrl()->SetHint(_L("Hostname or IP"));
m_custom_camera_input->GetTextCtrl()->SetFont(Label::Body_14);
m_custom_camera_hint = new wxStaticText(m_panel, wxID_ANY, _L("Custom camera source"));
m_custom_camera_hint->Wrap(-1);
m_custom_camera_hint->SetFont(Label::Head_14);
m_custom_camera_hint->SetForegroundColour(TEXT_COL);
m_custom_camera_input_confirm->Bind(wxEVT_BUTTON, &CameraPopup::on_camera_source_changed, this);
if (!wxGetApp().app_config->get("camera", "custom_source").empty()) {
m_custom_camera_input->GetTextCtrl()->SetValue(wxGetApp().app_config->get("camera", "custom_source"));
set_custom_cam_button_state(wxGetApp().app_config->get("camera", "enable_custom_source") == "true");
}
top_sizer->Add(m_custom_camera_hint, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxALL, FromDIP(5));
top_sizer->Add(0, 0, wxALL, 0);
top_sizer->Add(m_custom_camera_input, 2, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxALL, FromDIP(5));
top_sizer->Add(m_custom_camera_input_confirm, 1, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, FromDIP(5));
main_sizer->Add(top_sizer, 0, wxALL, FromDIP(10)); main_sizer->Add(top_sizer, 0, wxALL, FromDIP(10));
auto url = wxString(L"https://www.orcaslicer.com/wiki/"); // Orca: neutral wiki link (vendor URL removed) auto url = wxString(L"https://www.orcaslicer.com/wiki/"); // Orca: neutral wiki link (vendor URL removed)
@@ -184,37 +155,6 @@ void CameraPopup::sdcard_absent_hint()
GetEventHandler()->ProcessEvent(evt); GetEventHandler()->ProcessEvent(evt);
} }
void CameraPopup::on_camera_source_changed(wxCommandEvent &event)
{
if (m_obj && !m_custom_camera_input->GetTextCtrl()->IsEmpty()) {
handle_camera_source_change();
}
}
void CameraPopup::handle_camera_source_change()
{
m_custom_camera_enabled = !m_custom_camera_enabled;
set_custom_cam_button_state(m_custom_camera_enabled);
wxGetApp().app_config->set("camera", "custom_source", m_custom_camera_input->GetTextCtrl()->GetValue().ToStdString());
wxGetApp().app_config->set("camera", "enable_custom_source", m_custom_camera_enabled);
wxCommandEvent evt(EVT_CAM_SOURCE_CHANGE);
evt.SetEventObject(this);
GetEventHandler()->ProcessEvent(evt);
}
void CameraPopup::set_custom_cam_button_state(bool state)
{
m_custom_camera_enabled = state;
auto stateColour = state ? wxColour(170, 0, 0) : wxColour(38, 166, 154);
auto stateText = state ? "Disable" : "Enable";
m_custom_camera_input_confirm->SetBackgroundColor(stateColour);
m_custom_camera_input_confirm->SetBorderColor(stateColour);
m_custom_camera_input_confirm->SetLabel(_L(stateText));
}
void CameraPopup::on_switch_recording(wxCommandEvent& event) void CameraPopup::on_switch_recording(wxCommandEvent& event)
{ {
if (!m_obj) return; if (!m_obj) return;
+4 -9
View File
@@ -15,14 +15,12 @@
#include "Widgets/SwitchButton.hpp" #include "Widgets/SwitchButton.hpp"
#include "Widgets/RadioBox.hpp" #include "Widgets/RadioBox.hpp"
#include "Widgets/PopupWindow.hpp" #include "Widgets/PopupWindow.hpp"
#include "Widgets/TextInput.hpp"
namespace Slic3r { namespace Slic3r {
namespace GUI { namespace GUI {
wxDECLARE_EVENT(EVT_VCAMERA_SWITCH, wxMouseEvent); wxDECLARE_EVENT(EVT_VCAMERA_SWITCH, wxMouseEvent);
wxDECLARE_EVENT(EVT_SDCARD_ABSENT_HINT, wxCommandEvent); wxDECLARE_EVENT(EVT_SDCARD_ABSENT_HINT, wxCommandEvent);
wxDECLARE_EVENT(EVT_CAM_SOURCE_CHANGE, wxCommandEvent);
class CameraPopup : public PopupWindow class CameraPopup : public PopupWindow
{ {
@@ -53,9 +51,6 @@ protected:
void on_switch_recording(wxCommandEvent& event); void on_switch_recording(wxCommandEvent& event);
void on_set_resolution(); void on_set_resolution();
void sdcard_absent_hint(); void sdcard_absent_hint();
void on_camera_source_changed(wxCommandEvent& event);
void handle_camera_source_change();
void set_custom_cam_button_state(bool state);
wxWindow * create_item_radiobox(wxString title, wxWindow *parent, wxString tooltip, int padding_left); wxWindow * create_item_radiobox(wxString title, wxWindow *parent, wxString tooltip, int padding_left);
void select_curr_radiobox(int btn_idx); void select_curr_radiobox(int btn_idx);
@@ -76,10 +71,10 @@ private:
wxStaticText* m_text_liveview_retry; wxStaticText* m_text_liveview_retry;
SwitchButton* m_switch_liveview_retry; SwitchButton* m_switch_liveview_retry;
#endif //BBL_RELEASE_TO_PUBLIC #endif //BBL_RELEASE_TO_PUBLIC
wxStaticText* m_custom_camera_hint; // wxStaticText* m_custom_camera_hint;
TextInput* m_custom_camera_input; // TextInput* m_custom_camera_input;
Button* m_custom_camera_input_confirm; // Button* m_custom_camera_input_confirm;
bool m_custom_camera_enabled{ false }; // bool m_custom_camera_enabled{ false };
wxStaticText* m_text_resolution; wxStaticText* m_text_resolution;
wxWindow* m_resolution_options[RESOLUTION_OPTIONS_NUM]; wxWindow* m_resolution_options[RESOLUTION_OPTIONS_NUM];
wxScrolledWindow *m_panel; wxScrolledWindow *m_panel;
+8 -6
View File
@@ -171,14 +171,16 @@ void ConfigManipulation::check_adaptive_pressure_advance_model(DynamicPrintConfi
return; return;
const auto* model = config->option<ConfigOptionStrings>("adaptive_pressure_advance_model"); const auto* model = config->option<ConfigOptionStrings>("adaptive_pressure_advance_model");
if (model == nullptr || model->values.empty()) if (model == nullptr)
return; return;
std::string raw_model; // Each extruder variant holds its own model.
for (const std::string& chunk : model->values) std::string error;
raw_model += chunk; for (const std::string& variant_model : model->values) {
error = AdaptivePAProcessor::validate_adaptive_pa_model(variant_model);
std::string error = AdaptivePAProcessor::validate_adaptive_pa_model(raw_model); if (!error.empty())
break;
}
if (!error.empty()) { if (!error.empty()) {
wxString msg_text = _L("Adaptive Pressure Advance model validation failed:\n"); wxString msg_text = _L("Adaptive Pressure Advance model validation failed:\n");
msg_text += from_u8(error); msg_text += from_u8(error);
+3 -1
View File
@@ -35,7 +35,9 @@ ConnectPrinterDialog::ConnectPrinterDialog(wxWindow *parent, wxWindowID id, cons
sizer_connect = new wxBoxSizer(wxHORIZONTAL); sizer_connect = new wxBoxSizer(wxHORIZONTAL);
m_textCtrl_code = new TextInput(this, wxEmptyString); m_textCtrl_code = new TextInput(this, wxEmptyString);
m_textCtrl_code->GetTextCtrl()->SetMaxLength(10); // OrcaSonar uses a 12-character base32 access code. Keep this field long
// enough for it while retaining the existing validation for LAN codes.
m_textCtrl_code->GetTextCtrl()->SetMaxLength(12);
m_textCtrl_code->SetFont(Label::Body_14); m_textCtrl_code->SetFont(Label::Body_14);
m_textCtrl_code->SetCornerRadius(FromDIP(5)); m_textCtrl_code->SetCornerRadius(FromDIP(5));
m_textCtrl_code->SetSize(wxSize(FromDIP(330), FromDIP(40))); m_textCtrl_code->SetSize(wxSize(FromDIP(330), FromDIP(40)));
@@ -1,5 +1,7 @@
#include "DevConfigUtil.h" #include "DevConfigUtil.h"
#include "slic3r/GUI/DeviceManager.hpp"
#include <wx/dir.h> #include <wx/dir.h>
#include <boost/filesystem/operations.hpp> #include <boost/filesystem/operations.hpp>
#include "../I18N.hpp" #include "../I18N.hpp"
@@ -41,6 +43,19 @@ static void _toolhead_translation_markers()
std::string DevPrinterConfigUtil::m_resource_file_path = ""; std::string DevPrinterConfigUtil::m_resource_file_path = "";
bool DevPrinterConfigUtil::is_printer_model_compatible(const std::string& source_model, MachineObject& machine)
{
const std::string& target_model = machine.printer_type;
if (is_optional_printer_model_id(source_model) || is_optional_printer_model_id(target_model))
return true;
if (source_model == target_model)
return true;
const auto compatible_machine = machine.get_compatible_machine();
return std::find(compatible_machine.begin(), compatible_machine.end(), source_model) != compatible_machine.end();
}
std::map<std::string, std::string> DevPrinterConfigUtil::get_all_model_id_with_name() std::map<std::string, std::string> DevPrinterConfigUtil::get_all_model_id_with_name()
{ {
@@ -25,6 +25,8 @@
namespace Slic3r namespace Slic3r
{ {
class MachineObject;
/// Toolhead component type (extruder / nozzle / hotend) /// Toolhead component type (extruder / nozzle / hotend)
enum class ToolHeadComponent { enum class ToolHeadComponent {
Extruder, Extruder,
@@ -59,6 +61,10 @@ public:
/*printer*/ /*printer*/
// info // info
static std::map<std::string, std::string> get_all_model_id_with_name(); static std::map<std::string, std::string> get_all_model_id_with_name();
// A printer agent may not know the physical model. Keep that case optional so
// model compatibility checks do not turn missing identity into a hard error.
static bool is_printer_model_compatible(const std::string& source_model, MachineObject& machine);
static bool is_optional_printer_model_id(const std::string& model_id) { return model_id.empty(); }
static std::string get_printer_type(const std::string& type_str) { return get_value_from_config<std::string>(type_str, "printer_type"); } static std::string get_printer_type(const std::string& type_str) { return get_value_from_config<std::string>(type_str, "printer_type"); }
static std::string get_printer_display_name(const std::string& type_str) { return get_value_from_config<std::string>(type_str, "display_name"); } static std::string get_printer_display_name(const std::string& type_str) { return get_value_from_config<std::string>(type_str, "display_name"); }
static std::string get_printer_series_str(std::string type_str) { return get_value_from_config<std::string>(type_str, "printer_series"); } static std::string get_printer_series_str(std::string type_str) { return get_value_from_config<std::string>(type_str, "printer_series"); }
+3 -3
View File
@@ -762,9 +762,9 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
{ {
curr_tray->remain = -1; curr_tray->remain = -1;
} }
if (tray_it->contains("tray_slot_placeholder")) { // The tray objects are reused across status updates. Reset this
curr_tray->is_slot_placeholder = true; // state when a previously empty slot receives a filament again.
} curr_tray->is_slot_placeholder = tray_it->contains("tray_slot_placeholder");
int ams_id_int = 0; int ams_id_int = 0;
int tray_id_int = 0; int tray_id_int = 0;
try try
+130 -49
View File
@@ -1,8 +1,10 @@
#include "DevManager.h"
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <exception> #include <exception>
#include "DevManager.h" #include <libslic3r/AppConfig.hpp>
#include "CloudProvider.hpp"
#include "DevUtil.h" #include "DevUtil.h"
// TODO: remove this include // TODO: remove this include
@@ -15,6 +17,8 @@
#include "libslic3r/Time.hpp" #include "libslic3r/Time.hpp"
#include "IPrinterAgent.hpp"
using namespace nlohmann; using namespace nlohmann;
namespace { namespace {
@@ -46,9 +50,11 @@ namespace {
namespace Slic3r namespace Slic3r
{ {
DeviceManager::DeviceManager(NetworkAgent* agent) DeviceManager::DeviceManager(NetworkAgent* agent, bool enable_refresher, AppConfig* app_config)
{ {
m_agent = agent; m_agent = agent;
m_app_config = app_config;
if (enable_refresher)
m_refresher = new DeviceManagerRefresher(this); m_refresher = new DeviceManagerRefresher(this);
DevPrinterConfigUtil::InitFilePath(resources_dir()); DevPrinterConfigUtil::InitFilePath(resources_dir());
@@ -60,9 +66,14 @@ namespace Slic3r
} }
} }
AppConfig* DeviceManager::get_app_config() const
{
return m_app_config ? m_app_config : GUI::wxGetApp().app_config;
}
void DeviceManager::load_local_machines_from_config() void DeviceManager::load_local_machines_from_config()
{ {
AppConfig* config = GUI::wxGetApp().app_config; AppConfig* config = get_app_config();
if (!config) if (!config)
return; return;
const auto local_machines = config->get_local_machines(); const auto local_machines = config->get_local_machines();
@@ -88,9 +99,8 @@ namespace Slic3r
} }
} }
void DeviceManager::update_local_machine(const MachineObject& m) void DeviceManager::update_local_machine(const MachineObject& m, AppConfig* config)
{ {
AppConfig* config = GUI::wxGetApp().app_config;
if (config) { if (config) {
if (m.is_lan_mode_printer()) { if (m.is_lan_mode_printer()) {
if (m.has_access_right()) { if (m.has_access_right()) {
@@ -111,6 +121,7 @@ namespace Slic3r
DeviceManager::~DeviceManager() DeviceManager::~DeviceManager()
{ {
if (m_refresher)
delete m_refresher; delete m_refresher;
for (auto it = localMachineList.begin(); it != localMachineList.end(); it++) for (auto it = localMachineList.begin(); it != localMachineList.end(); it++)
@@ -171,14 +182,22 @@ namespace Slic3r
return printer_agent ? printer_agent->get_agent_info().id : ""; return printer_agent ? printer_agent->get_agent_info().id : "";
} }
std::string DeviceManager::get_current_cloud_provider() const
{
const std::string agent_id = get_current_printer_agent_id();
if (!agent_id.empty())
return agent_id == BBL_PRINTER_AGENT_ID ? BBL_CLOUD_PROVIDER : ORCA_CLOUD_PROVIDER;
return GUI::wxGetApp().get_printer_cloud_provider();
}
void DeviceManager::EnableMultiMachine(bool enable) void DeviceManager::EnableMultiMachine(bool enable)
{ {
m_agent->enable_multi_machine(enable); m_agent->enable_multi_machine(enable);
m_enable_mutil_machine = enable; m_enable_mutil_machine = enable;
} }
void DeviceManager::start_refresher() { m_refresher->Start(); } void DeviceManager::start_refresher() { if (m_refresher) m_refresher->Start(); }
void DeviceManager::stop_refresher() { m_refresher->Stop(); } void DeviceManager::stop_refresher() { if (m_refresher) m_refresher->Stop(); }
void DeviceManager::keep_alive() void DeviceManager::keep_alive()
@@ -265,6 +284,10 @@ namespace Slic3r
/* update userMachineList info */ /* update userMachineList info */
auto it = userMachineList.find(dev_id); auto it = userMachineList.find(dev_id);
if (it != userMachineList.end()) { if (it != userMachineList.end()) {
// A reused entry may have been created while another printer agent was active.
// The response was obtained through the current agent, so move ownership with
// the entry; otherwise agent-scoped lists hide it after a preset switch.
it->second->printer_agent_id = get_current_printer_agent_id();
if (it->second->get_dev_ip() != dev_ip || if (it->second->get_dev_ip() != dev_ip ||
it->second->bind_state != bind_state || it->second->bind_state != bind_state ||
it->second->bind_sec_link != sec_link || it->second->bind_sec_link != sec_link ||
@@ -291,10 +314,15 @@ namespace Slic3r
/* update localMachineList */ /* update localMachineList */
it = localMachineList.find(dev_id); it = localMachineList.find(dev_id);
AppConfig* config = get_app_config();
if (it != localMachineList.end()) { if (it != localMachineList.end()) {
// update properties // update properties
/* ip changed */ /* ip changed */
obj = it->second; obj = it->second;
// A reused LAN entry may have been discovered while another printer agent was
// active. The current discovery message establishes ownership for this agent.
obj->printer_agent_id = get_current_printer_agent_id();
if (obj->get_dev_ip().compare(dev_ip) != 0) { if (obj->get_dev_ip().compare(dev_ip) != 0) {
if ( connection_name.empty() ) { if ( connection_name.empty() ) {
@@ -384,7 +412,6 @@ namespace Slic3r
obj->m_is_online = true; obj->m_is_online = true;
//load access code //load access code
AppConfig* config = Slic3r::GUI::wxGetApp().app_config;
if (config) { if (config) {
obj->set_access_code(get_access_code_with_legacy_fallback(config, dev_id, obj->printer_agent_id), false); obj->set_access_code(get_access_code_with_legacy_fallback(config, dev_id, obj->printer_agent_id), false);
} }
@@ -407,7 +434,7 @@ namespace Slic3r
fire_lifecycle_event(LifecycleEvent::DeviceDiscovered, ctx); fire_lifecycle_event(LifecycleEvent::DeviceDiscovered, ctx);
} }
} }
update_local_machine(*obj); update_local_machine(*obj, config);
} }
catch (...) { catch (...) {
; ;
@@ -422,6 +449,9 @@ namespace Slic3r
auto it = localMachineList.find(machine.dev_id); auto it = localMachineList.find(machine.dev_id);
if (it != localMachineList.end()) { if (it != localMachineList.end()) {
obj = it->second; obj = it->second;
// insert_local_device is called by the active agent, so a reused entry must follow
// that agent as well; otherwise the agent-scoped printer list hides it.
obj->printer_agent_id = get_current_printer_agent_id();
} else { } else {
obj = new MachineObject(this, m_agent, machine.dev_name, machine.dev_id, machine.dev_ip); obj = new MachineObject(this, m_agent, machine.dev_name, machine.dev_id, machine.dev_ip);
obj->printer_agent_id = get_current_printer_agent_id(); obj->printer_agent_id = get_current_printer_agent_id();
@@ -439,11 +469,16 @@ namespace Slic3r
obj->last_alive = Slic3r::Utils::get_current_time_utc(); obj->last_alive = Slic3r::Utils::get_current_time_utc();
obj->set_access_code(access_code, false); obj->set_access_code(access_code, false);
update_local_machine(*obj); update_local_machine(*obj, get_app_config());
return obj; return obj;
} }
void DeviceManager::update_local_machine(const MachineObject& m)
{
update_local_machine(m, GUI::wxGetApp().app_config);
}
int DeviceManager::query_bind_status(std::string& msg, const std::string& provider) int DeviceManager::query_bind_status(std::string& msg, const std::string& provider)
{ {
if (!m_agent) if (!m_agent)
@@ -514,7 +549,7 @@ namespace Slic3r
MachineObject* DeviceManager::get_my_machine(std::string dev_id) MachineObject* DeviceManager::get_my_machine(std::string dev_id)
{ {
auto list = get_my_machine_list(); auto list = get_my_machine_list(get_current_printer_agent_id());
auto it = list.find(dev_id); auto it = list.find(dev_id);
if (it != list.end()) if (it != list.end())
{ {
@@ -551,25 +586,15 @@ namespace Slic3r
OnSelectedMachineChanged(previous_selected_machine, selected_machine); OnSelectedMachineChanged(previous_selected_machine, selected_machine);
} }
void DeviceManager::clear_other_devices(const std::string& target_agent_id) void DeviceManager::clear_other_devices()
{ {
// why: on agent swap, keep "My Devices" but drop the transient "Other Devices" // Device entries are now scoped by printer_agent_id when they are presented. Keep
// Those belong to the previous agent's network scan; the new agent's start_discovery re-populates its own. // agent-owned discoveries across a switch so agents without automatic discovery (and
// // plugins whose devices have not received an access code yet) do not lose their list.
// Also drop "My Devices" stamped by a different agent than the one we're swapping to // Entries without an owner are legacy/unscoped and cannot safely be shown.
// (target_agent_id, passed by the caller since the live agent hasn't been repointed yet
// at this point): otherwise a device first discovered under agent A survives every swap
// with a stale printer_agent_id, stays hidden from every agent's filtered list, and only
// gets re-tagged if something happens to delete and re-create it (e.g. account logout).
// Dropping it here instead lets the new agent's start_discovery re-insert and re-stamp it
// like any other fresh device.
const auto my = get_my_machine_list();
for (auto it = localMachineList.begin(); it != localMachineList.end();) for (auto it = localMachineList.begin(); it != localMachineList.end();)
{ {
const bool is_my_device = my.find(it->first) != my.end(); if (!it->second || it->second->printer_agent_id.empty())
const bool agent_mismatch = !target_agent_id.empty() && it->second &&
it->second->printer_agent_id != target_agent_id;
if (!is_my_device || agent_mismatch)
{ {
delete it->second; delete it->second;
it = localMachineList.erase(it); it = localMachineList.erase(it);
@@ -585,8 +610,22 @@ namespace Slic3r
{ {
BOOST_LOG_TRIVIAL(info) << "set_selected_machine=" << dev_id BOOST_LOG_TRIVIAL(info) << "set_selected_machine=" << dev_id
<< " cur_selected=" << selected_machine; << " cur_selected=" << selected_machine;
auto my_machine_list = get_my_machine_list(); auto my_machine_list = get_my_machine_list(get_current_printer_agent_id());
auto it = my_machine_list.find(dev_id); auto it = my_machine_list.find(dev_id);
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: set_selected_machine lookup dev_id=" << dev_id
<< " found=" << (it != my_machine_list.end())
<< " my_machine_count=" << my_machine_list.size()
<< " current_agent=" << get_current_printer_agent_id()
<< " provider=" << GUI::wxGetApp().get_printer_cloud_provider();
if (it != my_machine_list.end() && it->second) {
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: target machine dev_id=" << it->second->get_dev_id()
<< " printer_agent_id=" << it->second->printer_agent_id
<< " connection_type=" << it->second->connection_type()
<< " dev_connection_type=" << it->second->dev_connection_type;
} else if (!dev_id.empty()) {
BOOST_LOG_TRIVIAL(warning) << "Orca diagnostic: target machine was not found in the current agent's machine list";
return false;
}
// disconnect last if dev_id difference from previous one // disconnect last if dev_id difference from previous one
auto last_selected = my_machine_list.find(selected_machine); auto last_selected = my_machine_list.find(selected_machine);
@@ -597,7 +636,9 @@ namespace Slic3r
m_agent->disconnect_printer(); m_agent->disconnect_printer();
} }
else if (last_selected->second->connection_type() == "cloud") { else if (last_selected->second->connection_type() == "cloud") {
m_agent->set_user_selected_machine(""); const int result = m_agent->set_user_selected_machine("");
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: cleared previous cloud selection dev_id="
<< selected_machine << " result=" << result;
} }
} }
@@ -632,11 +673,7 @@ namespace Slic3r
m_agent->disconnect_printer(); m_agent->disconnect_printer();
it->second->reset(); it->second->reset();
#if !BBL_RELEASE_TO_PUBLIC it->second->connect();
it->second->connect(Slic3r::GUI::wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false);
#else
it->second->connect(it->second->local_use_ssl);
#endif
it->second->set_lan_mode_connection_state(true); it->second->set_lan_mode_connection_state(true);
} }
} }
@@ -649,18 +686,16 @@ namespace Slic3r
{ {
// diff dev_id, cloud => set_user_selected_machine(new) // diff dev_id, cloud => set_user_selected_machine(new)
BOOST_LOG_TRIVIAL(info) << "set_selected_machine: select new cloud machine, dev_id =" << dev_id; BOOST_LOG_TRIVIAL(info) << "set_selected_machine: select new cloud machine, dev_id =" << dev_id;
m_agent->set_user_selected_machine(dev_id); const int result = m_agent->set_user_selected_machine(dev_id);
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: set new cloud selection dev_id="
<< dev_id << " result=" << result;
it->second->reset(); it->second->reset();
} }
else else
{ {
BOOST_LOG_TRIVIAL(info) << "set_selected_machine: select new lan machine, dev_id =" << dev_id; BOOST_LOG_TRIVIAL(info) << "set_selected_machine: select new lan machine, dev_id =" << dev_id;
it->second->reset(); it->second->reset();
#if !BBL_RELEASE_TO_PUBLIC it->second->connect();
it->second->connect(Slic3r::GUI::wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false);
#else
it->second->connect(it->second->local_use_ssl);
#endif
it->second->set_lan_mode_connection_state(true); it->second->set_lan_mode_connection_state(true);
} }
} }
@@ -677,6 +712,8 @@ namespace Slic3r
selected_machine = dev_id; selected_machine = dev_id;
record_user_last_machine(selected_machine); record_user_last_machine(selected_machine);
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: DeviceManager selection complete selected_machine="
<< selected_machine;
return true; return true;
} }
@@ -707,7 +744,9 @@ namespace Slic3r
dev_list.push_back(it->first); dev_list.push_back(it->first);
BOOST_LOG_TRIVIAL(trace) << "add_user_subscribe: " << it->first; BOOST_LOG_TRIVIAL(trace) << "add_user_subscribe: " << it->first;
} }
m_agent->add_subscribe(dev_list); const int result = m_agent->add_subscribe(dev_list);
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: add_user_subscribe count=" << dev_list.size()
<< " result=" << result;
} }
@@ -720,7 +759,9 @@ namespace Slic3r
dev_list.push_back(it->first); dev_list.push_back(it->first);
BOOST_LOG_TRIVIAL(trace) << "del_user_subscribe: " << it->first; BOOST_LOG_TRIVIAL(trace) << "del_user_subscribe: " << it->first;
} }
m_agent->del_subscribe(dev_list); const int result = m_agent->del_subscribe(dev_list);
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: del_user_subscribe count=" << dev_list.size()
<< " result=" << result;
} }
void DeviceManager::subscribe_device_list(std::vector<std::string> dev_list) void DeviceManager::subscribe_device_list(std::vector<std::string> dev_list)
@@ -839,7 +880,24 @@ namespace Slic3r
try try
{ {
json j = json::parse(body); json j = json::parse(body);
const std::string provider = GUI::wxGetApp().get_printer_cloud_provider();
const bool has_request_context = j.contains("provider") && j.contains("agent_id") && j.contains("generation");
const std::string current_provider = get_current_cloud_provider();
const std::string provider = j.contains("provider") ? j["provider"].get<std::string>()
: current_provider;
const std::string agent_id = j.contains("agent_id") ? j["agent_id"].get<std::string>()
: get_current_printer_agent_id();
const std::uint64_t generation = j.value("generation", std::uint64_t(0));
if (has_request_context &&
(provider != current_provider ||
agent_id != get_current_printer_agent_id() ||
generation != (m_agent ? m_agent->get_user_machine_list_generation() : 0))) {
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << ": ignoring stale response provider="
<< provider << " agent_id=" << agent_id
<< " generation=" << generation;
return;
}
#if !BBL_RELEASE_TO_PUBLIC #if !BBL_RELEASE_TO_PUBLIC
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": " << j; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": " << j;
@@ -862,11 +920,14 @@ namespace Slic3r
/* update field */ /* update field */
obj = iter->second; obj = iter->second;
obj->set_dev_id(dev_id); obj->set_dev_id(dev_id);
// A device can be rediscovered by a different agent after a preset
// switch while retaining the same MachineObject instance.
obj->printer_agent_id = agent_id;
} }
else else
{ {
obj = new MachineObject(this, m_agent, "", "", ""); obj = new MachineObject(this, m_agent, "", "", "");
obj->printer_agent_id = get_current_printer_agent_id(); obj->printer_agent_id = agent_id;
if (m_agent) if (m_agent)
{ {
obj->set_bind_status(m_agent->get_user_name(provider)); obj->set_bind_status(m_agent->get_user_name(provider));
@@ -874,13 +935,20 @@ namespace Slic3r
if (obj->get_dev_ip().empty()) if (obj->get_dev_ip().empty())
{ {
obj->get_dev_ip() = Slic3r::GUI::wxGetApp().app_config->get("ip_address", dev_id); if (AppConfig* config = get_app_config())
obj->get_dev_ip() = config->get("ip_address", dev_id);
} }
userMachineList.insert(std::make_pair(dev_id, obj)); userMachineList.insert(std::make_pair(dev_id, obj));
} }
if (!obj) continue; if (!obj) continue;
// Orca cloud printers are only ever delivered through this REST
// account list; tag them so DeviceManager's cloud/lan branches
// (subscribe + deselect in set_selected_machine) treat them right.
if (provider == ORCA_CLOUD_PROVIDER)
obj->dev_connection_type = "cloud";
if (!elem["dev_id"].is_null()) if (!elem["dev_id"].is_null())
obj->set_dev_id(elem["dev_id"].get<std::string>()); obj->set_dev_id(elem["dev_id"].get<std::string>());
if (!elem["dev_name"].is_null()) if (!elem["dev_name"].is_null())
@@ -912,6 +980,12 @@ namespace Slic3r
acc_code.erase(std::remove(acc_code.begin(), acc_code.end(), '\n'), acc_code.end()); acc_code.erase(std::remove(acc_code.begin(), acc_code.end(), '\n'), acc_code.end());
obj->set_access_code(acc_code); obj->set_access_code(acc_code);
} }
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: parsed cloud machine dev_id=" << dev_id
<< " name=" << obj->get_dev_name()
<< " agent_id=" << obj->printer_agent_id
<< " connection_type=" << obj->connection_type()
<< " online=" << obj->m_is_online;
} }
//remove MachineObject from userMachineList //remove MachineObject from userMachineList
@@ -927,6 +1001,9 @@ namespace Slic3r
iterat++; iterat++;
} }
} }
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: parse_user_print_info complete provider=" << provider
<< " parsed_count=" << new_list.size()
<< " stored_count=" << userMachineList.size();
} }
} }
catch (std::exception& e) catch (std::exception& e)
@@ -943,25 +1020,29 @@ namespace Slic3r
unsigned int http_code; unsigned int http_code;
std::string body; std::string body;
int result = m_agent->get_user_print_info(&http_code, &body, provider); int result = m_agent->get_user_print_info(&http_code, &body, provider);
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: get_user_print_info provider=" << provider
<< " result=" << result << " http_code=" << http_code
<< " body_bytes=" << body.size();
if (result == 0) if (result == 0)
{ {
// parse_user_print_info and on_machine_alive (SSDP for discovery) both mutate the same userMachineList map. // parse_user_print_info and on_machine_alive (SSDP for discovery) both mutate the same userMachineList map.
// on_machine_alive mutates the map on the UI thread, do the same for parse_user_print_info. // on_machine_alive mutates the map on the UI thread, do the same for parse_user_print_info.
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: queueing parse_user_print_info on UI thread";
Slic3r::GUI::wxGetApp().CallAfter([this, body]() { parse_user_print_info(body); }); Slic3r::GUI::wxGetApp().CallAfter([this, body]() { parse_user_print_info(body); });
} }
} }
void DeviceManager::record_user_last_machine(const std::string& dev_id) void DeviceManager::record_user_last_machine(const std::string& dev_id)
{ {
if (Slic3r::GUI::wxGetApp().app_config) { if (AppConfig* config = get_app_config()) {
Slic3r::GUI::wxGetApp().app_config->set("user_last_selected_machine", dev_id); config->set("user_last_selected_machine", dev_id);
} }
} }
std::string DeviceManager::get_user_last_machine() const std::string DeviceManager::get_user_last_machine() const
{ {
if (Slic3r::GUI::wxGetApp().app_config) { if (AppConfig* config = get_app_config()) {
const auto& user_last_machine = Slic3r::GUI::wxGetApp().app_config->get("user_last_selected_machine"); const auto& user_last_machine = config->get("user_last_selected_machine");
if (!user_last_machine.empty()) { if (!user_last_machine.empty()) {
return user_last_machine; return user_last_machine;
} else if (m_agent) { } else if (m_agent) {
+10 -5
View File
@@ -12,6 +12,7 @@ namespace Slic3r
struct BBLocalMachine; struct BBLocalMachine;
class MachineObject; class MachineObject;
class NetworkAgent; class NetworkAgent;
class AppConfig;
namespace GUI { namespace GUI {
class GUI_App; class GUI_App;
@@ -24,6 +25,7 @@ class DeviceManager
friend class DeviceManagerRefresher; friend class DeviceManagerRefresher;
private: private:
NetworkAgent* m_agent{ nullptr }; NetworkAgent* m_agent{ nullptr };
AppConfig* m_app_config{ nullptr };
DeviceManagerRefresher* m_refresher{ nullptr }; DeviceManagerRefresher* m_refresher{ nullptr };
bool m_enable_mutil_machine = false; bool m_enable_mutil_machine = false;
@@ -35,11 +37,13 @@ private:
std::map<std::string, MachineObject*> userMachineList; /* dev_id -> MachineObject* cloudMachine of User */ std::map<std::string, MachineObject*> userMachineList; /* dev_id -> MachineObject* cloudMachine of User */
public: public:
DeviceManager(NetworkAgent* agent = nullptr); DeviceManager(NetworkAgent* agent = nullptr, bool enable_refresher = true,
AppConfig* app_config = nullptr);
~DeviceManager(); ~DeviceManager();
public: public:
NetworkAgent* get_agent() const { return m_agent; } NetworkAgent* get_agent() const { return m_agent; }
AppConfig* get_app_config() const;
void set_agent(NetworkAgent* agent); void set_agent(NetworkAgent* agent);
void start_refresher(); void start_refresher();
@@ -74,10 +78,9 @@ public:
void erase_user_machine(std::string dev_id) { userMachineList.erase(dev_id); } void erase_user_machine(std::string dev_id) { userMachineList.erase(dev_id); }
void clean_user_info(bool keep_local_selection = false); void clean_user_info(bool keep_local_selection = false);
// target_agent_id: id of the agent being swapped to (empty = no agent-mismatch check, // Retain agent-owned LAN discoveries across a switch; the active-agent list filter keeps
// just the original "drop Other Devices" behavior). Pass the incoming agent's id, not the // entries from other agents hidden while allowing them to reappear when switched back.
// live one - this runs before the live agent is repointed. void clear_other_devices();
void clear_other_devices(const std::string& target_agent_id = "");
void load_last_machine(); void load_last_machine();
void update_user_machine_list_info(const std::string& provider); void update_user_machine_list_info(const std::string& provider);
@@ -122,6 +125,7 @@ private:
void keep_alive(); void keep_alive();
void check_pushing(); void check_pushing();
std::string get_current_cloud_provider() const;
void OnMachineBindStateChanged(MachineObject* obj, const std::string& new_state); void OnMachineBindStateChanged(MachineObject* obj, const std::string& new_state);
void OnSelectedMachineChanged(const std::string& pre_dev_id, const std::string& new_dev_id); void OnSelectedMachineChanged(const std::string& pre_dev_id, const std::string& new_dev_id);
@@ -134,6 +138,7 @@ public:
std::string connection_type, std::string bind_state, std::string version, std::string connection_type, std::string bind_state, std::string version,
std::string access_code); std::string access_code);
static void update_local_machine(const MachineObject& m); static void update_local_machine(const MachineObject& m);
static void update_local_machine(const MachineObject& m, AppConfig* config);
}; };
class DeviceManagerRefresher : public wxObject class DeviceManagerRefresher : public wxObject
+212 -95
View File
@@ -4,6 +4,7 @@
#include "I18N.hpp" #include "I18N.hpp"
#include "libslic3r/Time.hpp" #include "libslic3r/Time.hpp"
#include "libslic3r/Thread.hpp" #include "libslic3r/Thread.hpp"
#include "slic3r/Utils/Http.hpp"
#include "slic3r/Utils/NetworkAgent.hpp" #include "slic3r/Utils/NetworkAgent.hpp"
#include "slic3r/plugin/PluginManager.hpp" #include "slic3r/plugin/PluginManager.hpp"
#include "slic3r/Utils/NetworkAgentFactory.hpp" #include "slic3r/Utils/NetworkAgentFactory.hpp"
@@ -17,6 +18,7 @@
#include "ReleaseNote.hpp" #include "ReleaseNote.hpp"
#include <thread> #include <thread>
#include <mutex> #include <mutex>
#include <charconv>
#include <codecvt> #include <codecvt>
#include <boost/foreach.hpp> #include <boost/foreach.hpp>
#include <boost/typeof/typeof.hpp> #include <boost/typeof/typeof.hpp>
@@ -54,6 +56,7 @@
#include "DeviceCore/DevStatus.h" #include "DeviceCore/DevStatus.h"
#include "DeviceCore/DevUpgrade.h" #include "DeviceCore/DevUpgrade.h"
#include "IPrinterAgent.hpp"
#define CALI_DEBUG #define CALI_DEBUG
#define MINUTE_30 1800000 //ms #define MINUTE_30 1800000 //ms
@@ -380,8 +383,22 @@ NozzleVolumeType convert_to_nozzle_type(const std::string &str)
wxString MachineObject::get_printer_type_display_str() const wxString MachineObject::get_printer_type_display_str() const
{ {
std::string display_name = DevPrinterConfigUtil::get_printer_display_name(printer_type); std::string display_name = DevPrinterConfigUtil::get_printer_display_name(printer_type);
// Bambu printers use m_resource_file_path + "/printers/" + type_str + ".json", which is a semantic that only works for their profiles.
// For any other profile, we can simply consult preset bundle if the model_id exists.
if (display_name.empty()) {
for (const auto& [vendor_id, vendor] : GUI::wxGetApp().preset_bundle->vendors) {
for (const auto& model : vendor.models) {
if (printer_type == model.model_id)
display_name = model.name;
}
}
}
if (!display_name.empty()) if (!display_name.empty())
return display_name; return display_name;
else if (printer_type == "orcasonar")
return "OrcaSonar Printer";
else else
return _L("Unknown"); return _L("Unknown");
} }
@@ -468,7 +485,7 @@ void MachineObject::set_access_code(std::string code, bool only_refresh)
{ {
this->access_code = code; this->access_code = code;
if (only_refresh) { if (only_refresh) {
AppConfig* config = GUI::wxGetApp().app_config; AppConfig* config = m_manager ? m_manager->get_app_config() : GUI::wxGetApp().app_config;
if (config) { if (config) {
if (is_lan_mode_printer()) { if (is_lan_mode_printer()) {
// why: LAN codes are scoped via BBLocalMachine::access_code, keyed by dev_id and // why: LAN codes are scoped via BBLocalMachine::access_code, keyed by dev_id and
@@ -481,7 +498,7 @@ void MachineObject::set_access_code(std::string code, bool only_refresh)
// fresh from the cloud API's current response, so there's no cross-agent leakage // fresh from the cloud API's current response, so there's no cross-agent leakage
// risk to guard against there. // risk to guard against there.
if (!code.empty()) { if (!code.empty()) {
DeviceManager::update_local_machine(*this); DeviceManager::update_local_machine(*this, config);
} else { } else {
// Only patch an existing record's code - don't persist a brand-new // Only patch an existing record's code - don't persist a brand-new
// never-bound entry just because set_access_code("") was called on it. // never-bound entry just because set_access_code("") was called on it.
@@ -1348,7 +1365,6 @@ int MachineObject::command_get_access_code() {
return this->publish_json(j); return this->publish_json(j);
} }
int MachineObject::command_request_push_all(bool request_now) int MachineObject::command_request_push_all(bool request_now)
{ {
auto curr_time = std::chrono::system_clock::now(); auto curr_time = std::chrono::system_clock::now();
@@ -1480,26 +1496,20 @@ int MachineObject::command_upgrade_module(std::string url, std::string module_ty
int MachineObject::command_xyz_abs() int MachineObject::command_xyz_abs()
{ {
return this->publish_gcode("G90 \n"); if (!m_agent) return -1;
return command_with_dialog(m_agent->command_xyz_abs(get_dev_id(), MachineObject::m_sequence_id++, is_lan_mode_printer()));
} }
int MachineObject::command_auto_leveling() int MachineObject::command_auto_leveling()
{ {
return this->publish_gcode("G29 \n"); if (!m_agent) return -1;
return command_with_dialog(m_agent->command_auto_leveling(get_dev_id(), MachineObject::m_sequence_id++, is_lan_mode_printer()));
} }
int MachineObject::command_go_home() int MachineObject::command_go_home()
{ {
if (m_support_mqtt_homing) if (!m_agent) return -1;
{ return command_with_dialog(m_agent->command_go_home(get_dev_id(), this->is_in_printing(), m_support_mqtt_homing, MachineObject::m_sequence_id++, is_lan_mode_printer()));
json j;
j["print"]["command"] = "back_to_center";
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
return this->publish_json(j);
}
// gcode command
return this->is_in_printing() ? this->publish_gcode("G28 X\n") : this->publish_gcode("G28 \n");
} }
int MachineObject::command_task_partskip(std::vector<int> part_ids) int MachineObject::command_task_partskip(std::vector<int> part_ids)
@@ -1621,23 +1631,14 @@ int MachineObject::command_stop_buzzer()
int MachineObject::command_set_bed(int temp) int MachineObject::command_set_bed(int temp)
{ {
if (m_support_mqtt_bet_ctrl) if (!m_agent) return -1;
{ return command_with_dialog(m_agent->command_set_bed(get_dev_id(), temp, m_support_mqtt_bet_ctrl, MachineObject::m_sequence_id++, is_lan_mode_printer()));
json j;
j["print"]["command"] = "set_bed_temp";
j["print"]["temp"] = temp;
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
return this->publish_json(j);
}
std::string gcode_str = (boost::format("M140 S%1%\n") % temp).str();
return this->publish_gcode(gcode_str);
} }
int MachineObject::command_set_nozzle(int temp) int MachineObject::command_set_nozzle(int temp)
{ {
std::string gcode_str = (boost::format("M104 S%1%\n") % temp).str(); if (!m_agent) return -1;
return this->publish_gcode(gcode_str); return command_with_dialog(m_agent->command_set_nozzle(get_dev_id(), temp, MachineObject::m_sequence_id++, is_lan_mode_printer()));
} }
int MachineObject::command_set_nozzle_new(int nozzle_id, int temp) int MachineObject::command_set_nozzle_new(int nozzle_id, int temp)
@@ -1742,9 +1743,8 @@ int MachineObject::command_ams_user_settings(bool start_read_opt, bool tray_read
int MachineObject::command_ams_calibrate(int ams_id) int MachineObject::command_ams_calibrate(int ams_id)
{ {
std::string gcode_cmd = (boost::format("M620 C%1% \n") % ams_id).str(); if (!m_agent) return -1;
BOOST_LOG_TRIVIAL(trace) << "ams_debug: gcode_cmd" << gcode_cmd; return command_with_dialog(m_agent->command_ams_calibrate(get_dev_id(), ams_id, MachineObject::m_sequence_id++, is_lan_mode_printer()));
return this->publish_gcode(gcode_cmd);
} }
int MachineObject::command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type, int nozzle_temp_min, int nozzle_temp_max) int MachineObject::command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type, int nozzle_temp_min, int nozzle_temp_max)
@@ -1780,29 +1780,23 @@ int MachineObject::command_ams_filament_settings(int ams_id, int slot_id, std::s
return this->publish_json(j); return this->publish_json(j);
} }
int MachineObject::command_ams_refresh_rfid(std::string tray_id) int MachineObject::command_ams_refresh_rfid(int ams_id, int slot_id)
{ {
std::string gcode_cmd = (boost::format("M620 R%1% \n") % tray_id).str(); if (!m_agent) return -1;
BOOST_LOG_TRIVIAL(trace) << "ams_debug: gcode_cmd" << gcode_cmd; return command_with_dialog(m_agent->command_ams_refresh_rfid(get_dev_id(), ams_id, slot_id, MachineObject::m_sequence_id++, is_lan_mode_printer()));
return this->publish_gcode(gcode_cmd);
} }
int MachineObject::command_ams_refresh_rfid2(int ams_id, int slot_id) int MachineObject::command_start_camera()
{ {
json j; if (!m_agent) return -1;
j["print"]["command"] = "ams_get_rfid"; return m_agent->command_start_camera(get_dev_id());
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["print"]["ams_id"] = ams_id;
j["print"]["slot_id"] = slot_id;
return this->publish_json(j);
} }
int MachineObject::command_ams_select_tray(std::string tray_id) int MachineObject::command_ams_select_tray(std::string tray_id)
{ {
std::string gcode_cmd = (boost::format("M620 P%1% \n") % tray_id).str(); if (!m_agent) return -1;
BOOST_LOG_TRIVIAL(trace) << "ams_debug: gcode_cmd" << gcode_cmd; return command_with_dialog(m_agent->command_ams_select_tray(get_dev_id(), tray_id, MachineObject::m_sequence_id++, is_lan_mode_printer()));
return this->publish_gcode(gcode_cmd);
} }
int MachineObject::command_ams_control(std::string action) int MachineObject::command_ams_control(std::string action)
@@ -1961,47 +1955,10 @@ int MachineObject::command_ams_air_print_detect(bool air_print_detect)
int MachineObject::command_axis_control(std::string axis, double unit, double input_val, int speed) int MachineObject::command_axis_control(std::string axis, double unit, double input_val, int speed)
{ {
if (m_support_mqtt_axis_control) if (!m_agent) return -1;
{ return command_with_dialog(m_agent->command_axis_control(get_dev_id(), axis, unit, input_val, speed, is_core_xy(),
int dir = input_val > 0 ? 1 : -1; m_support_mqtt_axis_control, MachineObject::m_sequence_id++,
// i3-arch printers move the bed for Y/Z, so the on-screen direction is is_lan_mode_printer()));
// reversed — same negation the g-code fallback below applies.
if (!is_core_xy() && (axis.compare("Y") == 0 || axis.compare("Z") == 0)) {
dir = -dir;
}
json j;
j["print"]["command"] = "xyz_ctrl";
j["print"]["axis"] = axis;
j["print"]["dir"] = dir;
j["print"]["mode"] = (std::abs(input_val) >= 10) ? 1 : 0;
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
return this->publish_json(j);
}
double value = input_val;
if (!is_core_xy()) {
if ( axis.compare("Y") == 0
|| axis.compare("Z") == 0) {
value = -1.0 * input_val;
}
}
char cmd[256];
if (axis.compare("X") == 0
|| axis.compare("Y") == 0
|| axis.compare("Z") == 0) {
sprintf(cmd, "M211 S \nM211 X1 Y1 Z1\nM1002 push_ref_mode\nG91 \nG1 %s%0.1f F%d\nM1002 pop_ref_mode\nM211 R\n", axis.c_str(), value * unit, speed);
}
else if (axis.compare("E") == 0) {
sprintf(cmd, "M83 \nG0 %s%0.1f F%d\n", axis.c_str(), value * unit, speed);
}
else {
return -1;
}
return this->publish_gcode(cmd);
} }
int MachineObject::command_extruder_control(int nozzle_id, double val) int MachineObject::command_extruder_control(int nozzle_id, double val)
@@ -2626,7 +2583,12 @@ void MachineObject::reset()
vt_slot.erase(vt_slot.begin() + 1); vt_slot.erase(vt_slot.begin() + 1);
} }
} }
// why: reset reuses MachineObject, so release its lazy subtask
// before dropping the pointer to prevent reconnect leaks.
if (subtask_) {
delete subtask_;
subtask_ = nullptr; subtask_ = nullptr;
}
has_extra_flow_type = false; has_extra_flow_type = false;
m_partskip_ids.clear(); m_partskip_ids.clear();
} }
@@ -2644,15 +2606,63 @@ void MachineObject::set_print_state(std::string status)
} }
} }
int MachineObject::connect(bool use_openssl) // why: printer agents can report progress without BBL cloud task identity.
void MachineObject::update_print_progress(const json& value)
{
if (value.is_string()) {
const std::string progress = value.get<std::string>();
int parsed_progress;
const auto result = std::from_chars(progress.data(), progress.data() + progress.size(), parsed_progress);
if (result.ec != std::errc{} || result.ptr != progress.data() + progress.size())
return;
mc_print_percent = parsed_progress;
} else if (value.is_number_integer())
mc_print_percent = value.get<int>();
else
return;
if (BBLSubTask* curr_task = get_subtask())
curr_task->task_progress = mc_print_percent;
}
int MachineObject::connect()
{ {
if (get_dev_ip().empty()) return -1; if (get_dev_ip().empty()) return -1;
std::string username = "bblp"; std::string username = m_agent ? m_agent->default_lan_username() : std::string();
std::string password = get_access_code(); std::string password = get_access_code();
std::string port;
std::string input = get_dev_ip();
const bool use_ssl = input.rfind("https", 0) == 0;
// This strips out the http/https prefix
std::string host = Http::get_host_from_url(input, &port);
std::string ca_file;
if (GUI::wxGetApp().preset_bundle) {
const auto& config = GUI::wxGetApp().preset_bundle->printers.get_edited_preset().config;
if (port.empty())
port = config.opt_string("printhost_port");
ca_file = config.opt_string("printhost_cafile");
}
if (host.empty())
host = get_dev_ip();
if (m_agent) { if (m_agent) {
try { try {
return m_agent->connect_printer(get_dev_id(), get_dev_ip(), username, password, use_openssl); PrinterConnectionParams params{
get_dev_id(),
host,
port,
username,
password,
use_ssl,
ca_file
};
return m_agent->connect_printer(params);
} catch (...) { } catch (...) {
; ;
} }
@@ -2768,6 +2778,14 @@ int MachineObject::publish_json(const json& json_item, int qos, int flag)
BOOST_LOG_TRIVIAL(error) << "publish_json: " << json_item.dump() << " code: " << rtn; BOOST_LOG_TRIVIAL(error) << "publish_json: " << json_item.dump() << " code: " << rtn;
} }
// why: the agent is the only thing that knows what it can translate, so it reports
// not-supported in its return value and this - the single funnel every command_* builder
// passes through - is the one place that turns it into something the user sees. No list of
// unsupported commands is needed anywhere: an agent that has no case for a command says so.
if (rtn == ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED || rtn == ORCA_NETWORK_ERR_CAP_NOT_AVAILABLE) {
show_unsupported_dlg(rtn);
}
return rtn; return rtn;
} }
@@ -3086,6 +3104,13 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
} }
} catch (...) {} } catch (...) {}
try {
if (j.contains("info"))
parse_new_info2(j["info"]);
} catch (...) {
BOOST_LOG_TRIVIAL(error) << "parse_json: failed to parse OrcaSonar capability info";
}
try { try {
if (auto ptr = m_fila_system->GetAmsFirmwareSwitch().lock()) { if (auto ptr = m_fila_system->GetAmsFirmwareSwitch().lock()) {
ptr->ParseFirmwareSwitch(j); ptr->ParseFirmwareSwitch(j);
@@ -3338,10 +3363,7 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
print_type = jj["print_type"].get<std::string>(); print_type = jj["print_type"].get<std::string>();
} }
if (jj.contains("mc_percent")) { if (jj.contains("mc_percent")) {
if (jj["mc_percent"].is_string()) update_print_progress(jj["mc_percent"]);
mc_print_percent = stoi(j["print"]["mc_percent"].get<std::string>());
else if (jj["mc_percent"].is_number_integer())
mc_print_percent = j["print"]["mc_percent"].get<int>();
} }
if (jj.contains("mc_print_sub_stage")) { if (jj.contains("mc_print_sub_stage")) {
if (jj["mc_print_sub_stage"].is_number_integer()) if (jj["mc_print_sub_stage"].is_number_integer())
@@ -3511,6 +3533,9 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
this->task_id_ = jj["task_id"].get<std::string>(); this->task_id_ = jj["task_id"].get<std::string>();
} }
if (jj.contains("thumbnail_url") && jj["thumbnail_url"].is_string())
m_agent_thumbnail_url = jj["thumbnail_url"].get<std::string>();
if (jj.contains("job_attr")) { if (jj.contains("job_attr")) {
int jobAttr = jj["job_attr"].get<int>(); int jobAttr = jj["job_attr"].get<int>();
jobState_ = get_flag_bits(jobAttr, 4, 4); jobState_ = get_flag_bits(jobAttr, 4, 4);
@@ -3556,7 +3581,6 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
update_slice_info(jj["project_id"].get<std::string>(), jj["profile_id"].get<std::string>(), jj["subtask_id"].get<std::string>(), plate_index); update_slice_info(jj["project_id"].get<std::string>(), jj["profile_id"].get<std::string>(), jj["subtask_id"].get<std::string>(), plate_index);
BBLSubTask* curr_task = get_subtask(); BBLSubTask* curr_task = get_subtask();
if (curr_task) { if (curr_task) {
curr_task->task_progress = mc_print_percent;
curr_task->printing_status = print_status; curr_task->printing_status = print_status;
curr_task->task_id = jj["subtask_id"].get<std::string>(); curr_task->task_id = jj["subtask_id"].get<std::string>();
} }
@@ -3859,6 +3883,7 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
has_ipcam = true; has_ipcam = true;
} else { } else {
has_ipcam = false; has_ipcam = false;
webcam_stream_url.clear();
} }
} }
if (ipcam.contains("resolution")) { if (ipcam.contains("resolution")) {
@@ -3893,6 +3918,9 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
liveview_local = local_rtsp_url.empty() ? LVL_None : local_rtsp_url == "disable" liveview_local = local_rtsp_url.empty() ? LVL_None : local_rtsp_url == "disable"
? LVL_Disable : boost::algorithm::starts_with(local_rtsp_url, "rtsps") ? LVL_Rtsps : LVL_Rtsp; ? LVL_Disable : boost::algorithm::starts_with(local_rtsp_url, "rtsps") ? LVL_Rtsps : LVL_Rtsp;
} }
if (ipcam.contains("stream_url") && ipcam["stream_url"].is_string()) {
webcam_stream_url = ipcam["stream_url"].get<std::string>();
}
if (ipcam.contains("tutk_server")) { if (ipcam.contains("tutk_server")) {
tutk_state = ipcam["tutk_server"].get<std::string>(); tutk_state = ipcam["tutk_server"].get<std::string>();
} }
@@ -4651,7 +4679,7 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
if (diff.count() > 10.0f) { if (diff.count() > 10.0f) {
BOOST_LOG_TRIVIAL(trace) << "parse_json timeout = " << diff.count(); BOOST_LOG_TRIVIAL(trace) << "parse_json timeout = " << diff.count();
} }
DeviceManager::update_local_machine(*this); DeviceManager::update_local_machine(*this, m_manager ? m_manager->get_app_config() : GUI::wxGetApp().app_config);
return 0; return 0;
} }
@@ -5460,6 +5488,86 @@ void MachineObject::parse_new_info(json print)
} }
} }
void MachineObject::parse_new_info2(const json& info)
{
if (!info.is_object() || info.value("command", "") != "get_capabilities")
return;
const auto capabilities_it = info.find("capabilities");
if (capabilities_it == info.end() || !capabilities_it->is_object())
return;
const auto flags_it = capabilities_it->find("flags");
if (flags_it == capabilities_it->end() || !flags_it->is_object())
return;
const json& flags = *flags_it;
BOOST_LOG_TRIVIAL(info) << "parse_new_info2: OrcaSonar capability flags=" << flags.dump();
auto parse_bool = [&flags](const char* name, bool& target) {
const auto it = flags.find(name);
if (it != flags.end() && it->is_boolean())
target = it->get<bool>();
};
parse_bool("support_send_to_sd", is_support_send_to_sdcard);
parse_bool("support_filament_backup", is_support_filament_backup);
parse_bool("support_update_remain", is_support_update_remain);
parse_bool("support_auto_recovery_step_loss", is_support_auto_recovery_step_loss);
parse_bool("support_ams_humidity", is_support_ams_humidity);
parse_bool("support_prompt_sound", is_support_prompt_sound);
parse_bool("support_filament_tangle_detect", is_support_filament_tangle_detect);
parse_bool("support_1080dpi", is_support_1080dpi);
parse_bool("support_cloud_print_only", is_support_cloud_print_only);
parse_bool("support_command_ams_switch", is_support_command_ams_switch);
parse_bool("support_mqtt_alive", is_support_mqtt_alive);
parse_bool("support_motor_noise_cali", is_support_motor_noise_cali);
parse_bool("support_timelapse", is_support_timelapse);
parse_bool("support_user_preset", is_support_user_preset);
parse_bool("support_refresh_nozzle", is_support_refresh_nozzle);
parse_bool("support_flow_calibration", is_support_flow_calibration);
parse_bool("support_build_plate_marker_detect", is_support_build_plate_marker_detect);
parse_bool("support_nozzle_blob_detect", is_support_nozzle_blob_detection);
if (!m_manager->IsMultiMachineEnabled() && !is_support_agora)
parse_bool("support_tunnel_mqtt", is_support_tunnel_mqtt);
const auto bed_leveling_it = flags.find("support_bed_leveling");
if (bed_leveling_it != flags.end() && bed_leveling_it->is_number_integer())
is_support_bed_leveling = bed_leveling_it->get<int>();
auto copy_bool = [&flags](json& target, const char* name) {
const auto it = flags.find(name);
if (it != flags.end() && it->is_boolean())
target[name] = *it;
};
// The capability manifest uses an object for this range, while the legacy
// DeviceCore parser consumes a boolean plus a two-element range array.
json device_config;
copy_bool(device_config, "support_chamber");
copy_bool(device_config, "support_first_layer_inspect");
copy_bool(device_config, "support_ai_monitoring");
copy_bool(device_config, "support_lidar_calibration");
const auto chamber_edit_it = flags.find("support_chamber_temp_edit");
if (chamber_edit_it != flags.end() && chamber_edit_it->is_boolean()) {
device_config["support_chamber_temp_edit"] = *chamber_edit_it;
} else if (chamber_edit_it != flags.end() && chamber_edit_it->is_object()) {
const auto min_it = chamber_edit_it->find("min");
const auto max_it = chamber_edit_it->find("max");
if (min_it != chamber_edit_it->end() && max_it != chamber_edit_it->end() && min_it->is_number() && max_it->is_number()) {
device_config["support_chamber_temp_edit"] = true;
device_config["support_chamber_temp_edit_range"] = {*min_it, *max_it};
}
}
json fan_config;
copy_bool(fan_config, "support_aux_fan");
copy_bool(fan_config, "support_chamber_fan");
m_config->ParseConfig(device_config);
m_fan->ParseV2_0(fan_config);
}
static bool is_hex_digit(char c) { static bool is_hex_digit(char c) {
return std::isxdigit(static_cast<unsigned char>(c)) != 0; return std::isxdigit(static_cast<unsigned char>(c)) != 0;
} }
@@ -5965,6 +6073,15 @@ bool MachineObject::HasAms() const
return m_fila_system->HasAms(); return m_fila_system->HasAms();
} }
int MachineObject::command_with_dialog(int cmd_result)
{
if (!m_agent)
return -1;
if (cmd_result == ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED || cmd_result == ORCA_NETWORK_ERR_CAP_NOT_AVAILABLE)
show_unsupported_dlg(cmd_result);
return cmd_result;
}
void change_the_opacity(wxColour& colour) void change_the_opacity(wxColour& colour)
{ {
if (colour.Alpha() == 255) { if (colour.Alpha() == 255) {
+15 -29
View File
@@ -18,6 +18,7 @@
#include "boost/bimap/bimap.hpp" #include "boost/bimap/bimap.hpp"
#include "libslic3r/calib.hpp" #include "libslic3r/calib.hpp"
#include "libslic3r/Utils.hpp" #include "libslic3r/Utils.hpp"
#include "slic3r/Utils/PrinterNetworkTypes.hpp"
#include "DeviceCore/DevDefs.h" #include "DeviceCore/DevDefs.h"
#include "DeviceCore/DevConfigUtil.h" #include "DeviceCore/DevConfigUtil.h"
@@ -100,7 +101,6 @@ struct DevPrintTaskRatingInfo;
// given nozzle diameter (mm), bucketed per nozzle size to mirror the printer firmware. // given nozzle diameter (mm), bucketed per nozzle size to mirror the printer firmware.
bool is_stringing_prone_filament(const std::string& filament_id, float nozzle_diameter); bool is_stringing_prone_filament(const std::string& filament_id, float nozzle_diameter);
class MachineObject class MachineObject
{ {
private: private:
@@ -307,7 +307,7 @@ public:
bool ams_support_virtual_tray { true }; bool ams_support_virtual_tray { true };
time_t ams_user_setting_start = 0; time_t ams_user_setting_start = 0;
time_t ams_switch_filament_start = 0; time_t ams_switch_filament_start = 0;
AmsStatusMain ams_status_main; AmsStatusMain ams_status_main = AmsStatusMain::AMS_STATUS_MAIN_IDLE;
int ams_status_sub; int ams_status_sub;
int ams_version = 0; int ams_version = 0;
@@ -547,30 +547,12 @@ public:
bool xcam_first_layer_inspector { false }; bool xcam_first_layer_inspector { false };
time_t xcam_first_layer_hold_start = 0; time_t xcam_first_layer_hold_start = 0;
std::string local_rtsp_url; std::string local_rtsp_url;
std::string webcam_stream_url;
std::string tutk_state; std::string tutk_state;
enum LiveviewLocal { LiveviewLocal liveview_local{ LiveviewLocal::LVL_None };
LVL_None, LiveviewRemote liveview_remote{ LiveviewRemote::LVR_None};
LVL_Disable, FileLocal file_local{ FileLocal::FL_None };
LVL_Local, FileRemote file_remote{ FileRemote::FR_None };
LVL_Rtsps,
LVL_Rtsp
} liveview_local{ LVL_None };
enum LiveviewRemote {
LVR_None,
LVR_Tutk,
LVR_Agora,
LVR_TutkAgora
} liveview_remote{ LVR_None };
enum FileLocal {
FL_None,
FL_Local
} file_local{ FL_None };
enum FileRemote {
FR_None,
FR_Tutk,
FR_Agora,
FR_TutkAgora
} file_remote{ FR_None };
enum PlateMakerDectect : int enum PlateMakerDectect : int
{ {
@@ -696,7 +678,7 @@ public:
/* machine mqtt apis */ /* machine mqtt apis */
int connect(bool use_openssl = true); int connect();
int disconnect(); int disconnect();
json_diff print_json; json_diff print_json;
@@ -708,6 +690,8 @@ public:
std::string subtask_id_; std::string subtask_id_;
std::string job_id_; std::string job_id_;
std::string last_subtask_id_; std::string last_subtask_id_;
// note: printer-agent-supplied thumbnail url, empty when the agent supplies none.
std::string m_agent_thumbnail_url;
BBLSliceInfo* slice_info {nullptr}; BBLSliceInfo* slice_info {nullptr};
boost::thread* get_slice_info_thread { nullptr }; boost::thread* get_slice_info_thread { nullptr };
boost::thread* get_model_task_thread { nullptr }; boost::thread* get_model_task_thread { nullptr };
@@ -765,6 +749,7 @@ public:
int command_set_printer_nozzle(std::string nozzle_type, float diameter); int command_set_printer_nozzle(std::string nozzle_type, float diameter);
int command_set_printer_nozzle2(int id, std::string nozzle_type, float diameter); int command_set_printer_nozzle2(int id, std::string nozzle_type, float diameter);
int command_get_access_code(); int command_get_access_code();
int command_start_camera();
int command_ack_proceed(json& proceed); int command_ack_proceed(json& proceed);
int command_purification_disable(); int command_purification_disable();
int command_dont_remind_next_time(json& mqtt_guard_json); int command_dont_remind_next_time(json& mqtt_guard_json);
@@ -811,8 +796,7 @@ public:
int command_ams_calibrate(int ams_id); int command_ams_calibrate(int ams_id);
int command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type, int nozzle_temp_min, int nozzle_temp_max); int command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type, int nozzle_temp_min, int nozzle_temp_max);
int command_ams_select_tray(std::string tray_id); int command_ams_select_tray(std::string tray_id);
int command_ams_refresh_rfid(std::string tray_id); int command_ams_refresh_rfid(int ams_id, int slot_id);
int command_ams_refresh_rfid2(int ams_id, int slot_id);
int command_ams_control(std::string action); int command_ams_control(std::string action);
int command_ams_drying_stop(); int command_ams_drying_stop();
int command_start_extrusion_cali(int tray_index, int nozzle_temp, int bed_temp, float max_volumetric_speed, std::string setting_id = ""); int command_start_extrusion_cali(int tray_index, int nozzle_temp, int bed_temp, float max_volumetric_speed, std::string setting_id = "");
@@ -894,6 +878,7 @@ public:
static bool is_in_printing_status(std::string status); static bool is_in_printing_status(std::string status);
void set_print_state(std::string status); void set_print_state(std::string status);
void update_print_progress(const json& value);
bool is_connected(); bool is_connected();
bool is_connecting(); bool is_connecting();
@@ -962,6 +947,7 @@ public:
/*for parse new info*/ /*for parse new info*/
bool check_enable_np(const json& print) const; bool check_enable_np(const json& print) const;
void parse_new_info(json print); void parse_new_info(json print);
void parse_new_info2(const json& info);
int get_flag_bits(std::string str, int start, int count = 1) const; int get_flag_bits(std::string str, int start, int count = 1) const;
uint32_t get_flag_bits_no_border(std::string str, int start_idx, int count = 1) const; uint32_t get_flag_bits_no_border(std::string str, int start_idx, int count = 1) const;
int get_flag_bits(int num, int start, int count = 1, int base = 10) const; int get_flag_bits(int num, int start, int count = 1, int base = 10) const;
@@ -990,7 +976,7 @@ public:
void command_set_save_remote_print_file_to_storage(bool save); void command_set_save_remote_print_file_to_storage(bool save);
private: private:
int command_with_dialog(int cmd_result);
/* xcam door open check*/ /* xcam door open check*/
bool is_support_door_open_check = false; bool is_support_door_open_check = false;
DoorOpenCheckState xcam_door_open_check = DoorOpenCheckState::DOOR_OPEN_CHECK_DISABLE; DoorOpenCheckState xcam_door_open_check = DoorOpenCheckState::DOOR_OPEN_CHECK_DISABLE;
+5 -2
View File
@@ -6546,8 +6546,11 @@ void GLCanvas3D::render_thumbnail_internal(ThumbnailData& thumbnail_data, const
// glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 1.0f)); // glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 1.0f));
BOOST_LOG_TRIVIAL(info) << boost::format("render_thumbnail: finished"); BOOST_LOG_TRIVIAL(info) << boost::format("render_thumbnail: finished");
// Puts the canvas viewport back in place of the thumbnail one set above. // Puts the canvas viewport back in place of the thumbnail one set above. The CLI renders
wxGetApp().plater()->get_camera().apply_viewport(); // thumbnails with no application and no plater, so there is no canvas viewport to restore.
if (wxTheApp != nullptr)
if (Plater *plater = wxGetApp().plater(); plater != nullptr)
plater->get_camera().apply_viewport();
} }
void GLCanvas3D::render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, void GLCanvas3D::render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params,
+32
View File
@@ -14,6 +14,9 @@
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <boost/any.hpp> #include <boost/any.hpp>
#include <wx/filename.h>
#include <wx/filesys.h>
#if __APPLE__ #if __APPLE__
#import <IOKit/pwr_mgt/IOPMLib.h> #import <IOKit/pwr_mgt/IOPMLib.h>
#elif _WIN32 #elif _WIN32
@@ -27,11 +30,13 @@
#include "AboutDialog.hpp" #include "AboutDialog.hpp"
#include "MsgDialog.hpp" #include "MsgDialog.hpp"
#include "Plater.hpp"
#include "format.hpp" #include "format.hpp"
#include "WebUserLoginDialog.hpp" #include "WebUserLoginDialog.hpp"
#include "libslic3r/Print.hpp" #include "libslic3r/Print.hpp"
#include "libslic3r/Utils.hpp"
namespace Slic3r { namespace Slic3r {
@@ -531,6 +536,11 @@ boost::filesystem::path into_path(const wxString &str)
return boost::filesystem::path(str.wx_str()); return boost::filesystem::path(str.wx_str());
} }
wxString file_url_from_path(const boost::filesystem::path &path)
{
return wxFileSystem::FileNameToURL(wxFileName(from_path(path)));
}
void about() void about()
{ {
AboutDialog dlg; AboutDialog dlg;
@@ -635,4 +645,26 @@ void desktop_open_any_folder( const std::string& path )
} }
bool desktop_open_project_attachment(wxWindow *parent, const boost::filesystem::path &path)
{
// The auxiliary path is UTF-8, which is what boost::filesystem reads a narrow string as.
const boost::filesystem::path aux_root(wxGetApp().plater()->model().get_auxiliary_file_temp_path());
boost::system::error_code ec;
if (!is_absolute_path_within_root(path, aux_root) || !boost::filesystem::is_regular_file(path, ec))
return false;
// Attachments come with the project and carry no download mark, so the desktop would open them without a warning.
if (!is_safe_to_open_file_name(path.filename().string())) {
MessageDialog dlg(parent,
wxString::Format(_L("\"%s\" is not a plain document, image or model file. Opening it may run it as a "
"program or script on this computer.\n\n"
"Only open attachments from projects you trust. Open it anyway?"),
from_path(path.filename())),
_L("Open attachment"), wxICON_WARNING | wxYES_NO);
if (dlg.ShowModal() != wxID_YES)
return false;
}
return wxLaunchDefaultApplication(from_path(path), 0);
}
} } } }
+6
View File
@@ -76,6 +76,9 @@ std::string into_u8(const wxString &str);
wxString from_path(const boost::filesystem::path &path); wxString from_path(const boost::filesystem::path &path);
// boost path from wxString // boost path from wxString
boost::filesystem::path into_path(const wxString &str); boost::filesystem::path into_path(const wxString &str);
// file:// URL of a local path, percent-encoded so characters such as '#', '%' and '?' stay part of the path.
// Append any query or fragment to the result.
wxString file_url_from_path(const boost::filesystem::path &path);
// Display an About dialog // Display an About dialog
extern void about(); extern void about();
@@ -83,6 +86,9 @@ extern void about();
extern void desktop_open_datadir_folder(); extern void desktop_open_datadir_folder();
// Ask the destop to open one folder // Ask the destop to open one folder
extern void desktop_open_any_folder(const std::string& path); extern void desktop_open_any_folder(const std::string& path);
// Ask the desktop to open a file from the project's auxiliary directory, after a confirmation
// unless its type is known to be plain content. Returns false if the file was not opened.
extern bool desktop_open_project_attachment(wxWindow *parent, const boost::filesystem::path &path);
} // namespace GUI } // namespace GUI
} // namespace Slic3r } // namespace Slic3r
+59 -51
View File
@@ -4,6 +4,7 @@
#include "libslic3r/Platform.hpp" #include "libslic3r/Platform.hpp"
#include "GUI_App.hpp" #include "GUI_App.hpp"
#include "Shortcuts.hpp" #include "Shortcuts.hpp"
#include "DeviceCore/DevConfigUtil.h"
#include "BindDialog.hpp" #include "BindDialog.hpp"
#include "DeviceManager.hpp" #include "DeviceManager.hpp"
#include "HMS.hpp" #include "HMS.hpp"
@@ -24,7 +25,6 @@
#include <boost/locale/encoding_utf.hpp> #include <boost/locale/encoding_utf.hpp>
#include <boost/log/detail/native_typeof.hpp> #include <boost/log/detail/native_typeof.hpp>
#include <libslic3r/Config.hpp> #include <libslic3r/Config.hpp>
#include <mutex>
#include <slic3r/plugin/PythonPluginInterface.hpp> #include <slic3r/plugin/PythonPluginInterface.hpp>
#include <wx/event.h> #include <wx/event.h>
@@ -41,9 +41,11 @@
#include <iterator> #include <iterator>
#include <exception> #include <exception>
#include <cstdlib> #include <cstdlib>
#include <mutex>
#include <regex> #include <regex>
#include <thread> #include <thread>
#include <string_view> #include <string_view>
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <boost/format.hpp> #include <boost/format.hpp>
@@ -88,7 +90,6 @@
#include "libslic3r/Thread.hpp" #include "libslic3r/Thread.hpp"
#include "libslic3r/miniz_extension.hpp" #include "libslic3r/miniz_extension.hpp"
#include "libslic3r/Utils.hpp" #include "libslic3r/Utils.hpp"
#include "libslic3r/Color.hpp"
#include "slic3r/plugin/PluginManager.hpp" #include "slic3r/plugin/PluginManager.hpp"
#include "slic3r/plugin/host/PluginHostUi.hpp" #include "slic3r/plugin/host/PluginHostUi.hpp"
#include "slic3r/plugin/PythonInterpreter.hpp" #include "slic3r/plugin/PythonInterpreter.hpp"
@@ -108,19 +109,18 @@
#include "../Utils/PrintHost.hpp" #include "../Utils/PrintHost.hpp"
#include "../Utils/Process.hpp" #include "../Utils/Process.hpp"
#include "../Utils/wxInspectorPlugins/Registration.hpp" #include "../Utils/wxInspectorPlugins/Registration.hpp"
#include "../Utils/MacDarkMode.hpp"
#include "../Utils/Http.hpp" #include "../Utils/Http.hpp"
#include "../Utils/InstanceID.hpp" #include "../Utils/InstanceID.hpp"
#include "../Utils/UndoRedo.hpp" #include "../Utils/UndoRedo.hpp"
#include "slic3r/Config/Snapshot.hpp" #include "slic3r/Config/Snapshot.hpp"
#include "Preferences.hpp" #include "Preferences.hpp"
#include "Tab.hpp" #include "Tab.hpp"
#include "SysInfoDialog.hpp"
#include "UpdateDialogs.hpp" #include "UpdateDialogs.hpp"
#include "Mouse3DController.hpp" #include "Mouse3DController.hpp"
#include "RemovableDriveManager.hpp" #include "RemovableDriveManager.hpp"
#include "InstanceCheck.hpp" #include "InstanceCheck.hpp"
#ifdef __APPLE__ #ifdef __APPLE__
#include "../Utils/MacDarkMode.hpp"
#include "DeepLinkHandlerMac.h" #include "DeepLinkHandlerMac.h"
#endif #endif
#include "NotificationManager.hpp" #include "NotificationManager.hpp"
@@ -129,8 +129,6 @@
#include "PrintHostDialogs.hpp" #include "PrintHostDialogs.hpp"
#include "NetworkPluginDialog.hpp" #include "NetworkPluginDialog.hpp"
#include "DesktopIntegrationDialog.hpp" #include "DesktopIntegrationDialog.hpp"
#include "SendSystemInfoDialog.hpp"
#include "ParamsDialog.hpp"
#include "KBShortcutsDialog.hpp" #include "KBShortcutsDialog.hpp"
#include "DownloadProgressDialog.hpp" #include "DownloadProgressDialog.hpp"
#include "TroubleshootDialog.hpp" #include "TroubleshootDialog.hpp"
@@ -141,7 +139,6 @@
#include "Widgets/ProgressDialog.hpp" #include "Widgets/ProgressDialog.hpp"
//BBS: DailyTip and UserGuide Dialog //BBS: DailyTip and UserGuide Dialog
#include "WebDownPluginDlg.hpp"
#include "WebGuideDialog.hpp" #include "WebGuideDialog.hpp"
#include "ReleaseNote.hpp" #include "ReleaseNote.hpp"
#include "PrivacyUpdateDialog.hpp" #include "PrivacyUpdateDialog.hpp"
@@ -1512,11 +1509,33 @@ int GUI_App::install_plugin(std::string name, std::string package_name, InstallP
size_t n = mz_zip_reader_get_extra(&archive, stat.m_file_index, extra.data(), extra.size()); size_t n = mz_zip_reader_get_extra(&archive, stat.m_file_index, extra.data(), extra.size());
dest_file = decode(extra.substr(0, n), stat.m_filename); dest_file = decode(extra.substr(0, n), stat.m_filename);
} }
if (!is_path_within_root(dest_file, plugin_folder)) {
BOOST_LOG_TRIVIAL(error) << "[install_plugin] entry " << dest_file << " resolves outside " << plugin_folder.string();
close_zip_reader(&archive);
if (pro_fn) { pro_fn(InstallStatusUnzipFailed, 0, cancel); }
return InstallStatusUnzipFailed;
}
auto dest_path = plugin_folder / dest_file; auto dest_path = plugin_folder / dest_file;
boost::filesystem::create_directories(dest_path.parent_path());
std::string dest_zip_file = encode_path(dest_path.string().c_str()); std::string dest_zip_file = encode_path(dest_path.string().c_str());
#ifndef WIN32
// Validate a symlink's target before anything at the destination is replaced.
const bool is_link = S_ISLNK(stat.m_external_attr >> 16);
std::string link;
if (is_link) {
link.assign(stat.m_uncomp_size, 0);
if (!mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, link.data(), stat.m_uncomp_size, 0) ||
!is_symlink_target_within_root(dest_file, link, plugin_folder)) {
BOOST_LOG_TRIVIAL(error) << "[install_plugin] link " << dest_file << " -> " << link << " is unreadable or resolves outside " << plugin_folder.string();
close_zip_reader(&archive);
if (pro_fn) { pro_fn(InstallStatusUnzipFailed, 0, cancel); }
return InstallStatusUnzipFailed;
}
}
#endif
try { try {
if (fs::exists(dest_path)) { boost::filesystem::create_directories(dest_path.parent_path());
// symlink_status so that an existing symlink, dangling or not, is replaced rather than written through.
if (fs::exists(fs::symlink_status(dest_path))) {
boost::system::error_code ec; boost::system::error_code ec;
fs::remove(dest_path, ec); fs::remove(dest_path, ec);
if (ec) { if (ec) {
@@ -1544,9 +1563,8 @@ int GUI_App::install_plugin(std::string name, std::string package_name, InstallP
} }
mz_bool res = 0; mz_bool res = 0;
#ifndef WIN32 #ifndef WIN32
if (S_ISLNK(stat.m_external_attr >> 16)) { if (is_link) {
std::string link(stat.m_uncomp_size + 1, 0); res = 1;
res = mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, link.data(), stat.m_uncomp_size, 0);
try { try {
boost::filesystem::create_symlink(link, dest_path); boost::filesystem::create_symlink(link, dest_path);
} catch (const std::exception &e) { } catch (const std::exception &e) {
@@ -2398,34 +2416,23 @@ GUI_App::~GUI_App()
bool GUI_App::is_blocking_printing(MachineObject *obj_) bool GUI_App::is_blocking_printing(MachineObject *obj_)
{ {
DeviceManager *dev = Slic3r::GUI::wxGetApp().getDeviceManager();
if (!dev) return true;
std::string target_model;
if (obj_ == nullptr) {
obj_ = dev->get_selected_machine();
if (obj_) {
target_model = obj_->printer_type;
}
} else {
target_model = obj_->printer_type;
}
if (!obj_)
{
return false;
}
PresetBundle *preset_bundle = wxGetApp().preset_bundle; PresetBundle *preset_bundle = wxGetApp().preset_bundle;
std::string source_model = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle); const std::string source_model = preset_bundle
? preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle)
: std::string();
return is_blocking_printing(obj_, source_model);
}
if (source_model != target_model) { bool GUI_App::is_blocking_printing(MachineObject *obj_, const std::string& source_model)
std::vector<std::string> compatible_machine = obj_->get_compatible_machine(); {
vector<std::string>::iterator it = find(compatible_machine.begin(), compatible_machine.end(), source_model); DeviceManager *dev = getDeviceManager();
if (it == compatible_machine.end()) { if (!dev) return true;
return true; if (obj_ == nullptr)
} obj_ = dev->get_selected_machine();
} if (!obj_)
return false; return false;
return !DevPrinterConfigUtil::is_printer_model_compatible(source_model, *obj_);
} }
// If formatted for github, plaintext with OpenGL extensions enclosed into <details>. // If formatted for github, plaintext with OpenGL extensions enclosed into <details>.
@@ -3986,20 +3993,16 @@ void GUI_App::set_live_printer_agent(std::shared_ptr<IPrinterAgent> agent)
m_agent->set_user_selected_machine(""); m_agent->set_user_selected_machine("");
// note: belt-and-suspenders (precedent: DeviceManagerRefresher::on_timer) // note: belt-and-suspenders (precedent: DeviceManagerRefresher::on_timer)
dev->OnSelectedMachineLost(); // why: clear stale sidebar sync-status / AMS dev->OnSelectedMachineLost(); // why: clear stale sidebar sync-status / AMS
// why: drop stale LAN discoveries; keep My Devices, but only those belonging to the // why: retain agent-owned LAN discoveries so agents without automatic discovery (for
// agent we're about to swap to, so a device stamped by the outgoing agent doesn't // example the Moonraker-based Qidi/Snapmaker agents) can reuse them after a switch.
// linger hidden - the new agent's start_discovery re-inserts and re-stamps it fresh. dev->clear_other_devices();
// agent is null when clearing the live agent entirely (e.g. plugin unload); there's no
// target to filter against then, so fall back to the original "keep all My Devices"
// behavior rather than guessing.
dev->clear_other_devices(agent ? agent->get_agent_info().id : std::string());
} }
m_agent->set_printer_agent(agent); m_agent->set_printer_agent(agent);
sidebar().update_all_preset_comboboxes(); sidebar().update_all_preset_comboboxes();
} }
std::string GUI_App::resolve_printer_agent_id(const std::string& stored_id) std::string GUI_App::resolve_printer_agent_id(const std::string& stored_id) const
{ {
if (!stored_id.empty()) if (!stored_id.empty())
return stored_id; return stored_id;
@@ -4035,6 +4038,7 @@ void GUI_App::switch_printer_agent()
std::string log_dir = data_dir(); std::string log_dir = data_dir();
std::string cloud_agent_id = agent_info.id == BBL_PRINTER_AGENT_ID ? BBL_CLOUD_PROVIDER : ORCA_CLOUD_PROVIDER; std::string cloud_agent_id = agent_info.id == BBL_PRINTER_AGENT_ID ? BBL_CLOUD_PROVIDER : ORCA_CLOUD_PROVIDER;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << agent_info.id;
std::shared_ptr<ICloudServiceAgent> cloud_agent = m_agent->get_cloud_agent(cloud_agent_id); std::shared_ptr<ICloudServiceAgent> cloud_agent = m_agent->get_cloud_agent(cloud_agent_id);
// Create new printer agent via registry // Create new printer agent via registry
@@ -4048,8 +4052,10 @@ void GUI_App::switch_printer_agent()
return; return;
} }
// The factory caches agents per ID, so an identical pointer means the agent type is unchanged. // Compare the registered IDs, not only the implementation pointer. Different registry IDs
if (m_agent->get_printer_agent() == new_printer_agent) { // may intentionally be backed by the same implementation object (especially for plugins).
const auto current_printer_agent = m_agent->get_printer_agent();
if (current_printer_agent && current_printer_agent->get_agent_info().id == effective_agent_id) {
// Orca: the agent type is unchanged (e.g. switching between two Moonraker/Klipper // Orca: the agent type is unchanged (e.g. switching between two Moonraker/Klipper
// printer presets), so the selected machine and the agent's cached device_info still // printer presets), so the selected machine and the agent's cached device_info still
// point at the previously active printer preset. Re-select the machine when the new // point at the previously active printer preset. Re-select the machine when the new
@@ -5032,14 +5038,16 @@ bool GUI_App::is_user_login(const std::string& provider/* = ORCA_CLOUD_PROVIDER*
return false; return false;
} }
const std::string& GUI_App::get_printer_cloud_provider() const std::string GUI_App::get_printer_cloud_provider() const
{ {
// Orca todo: this need to be revisted. currently it is mainly used for device manager and related clausses and only bambu machines use them. const std::string agent_id = resolve_printer_agent_id(
// preset_bundle ? preset_bundle->printers.get_edited_preset().config.opt_string("printer_agent")
return BBL_CLOUD_PROVIDER; : std::string());
return agent_id == BBL_PRINTER_AGENT_ID ? BBL_CLOUD_PROVIDER : ORCA_CLOUD_PROVIDER;
} }
bool GUI_App::check_login(const std::string& provider/* = ORCA_CLOUD_PROVIDER*/) bool GUI_App::check_login(const std::string& provider/* = ORCA_CLOUD_PROVIDER*/)
{ {
bool result = false; bool result = false;
+3 -5
View File
@@ -24,9 +24,6 @@
#include <wx/snglinst.h> #include <wx/snglinst.h>
#include <wx/msgdlg.h> #include <wx/msgdlg.h>
#include <mutex>
#include <stack>
//#define BBL_HAS_FIRST_PAGE 1 //#define BBL_HAS_FIRST_PAGE 1
#define STUDIO_INACTIVE_TIMEOUT 15*60*1000 #define STUDIO_INACTIVE_TIMEOUT 15*60*1000
#define LOG_FILES_MAX_NUM 30 #define LOG_FILES_MAX_NUM 30
@@ -373,6 +370,7 @@ public:
EAppMode get_app_mode() const { return m_app_mode; } EAppMode get_app_mode() const { return m_app_mode; }
Slic3r::DeviceManager* getDeviceManager() { return m_device_manager; } Slic3r::DeviceManager* getDeviceManager() { return m_device_manager; }
bool is_blocking_printing(MachineObject *obj_ = nullptr); bool is_blocking_printing(MachineObject *obj_ = nullptr);
bool is_blocking_printing(MachineObject *obj_, const std::string& source_model);
Slic3r::TaskManager* getTaskManager() { return m_task_manager; } Slic3r::TaskManager* getTaskManager() { return m_task_manager; }
HMSQuery* get_hms_query() { return hms_query; } HMSQuery* get_hms_query() { return hms_query; }
NetworkAgent* getAgent() { return m_agent; } NetworkAgent* getAgent() { return m_agent; }
@@ -380,7 +378,7 @@ public:
// Reconcile the live printer agent with the stored preset selection. // Reconcile the live printer agent with the stored preset selection.
void switch_printer_agent(); void switch_printer_agent();
std::string resolve_printer_agent_id(const std::string& stored_id); std::string resolve_printer_agent_id(const std::string& stored_id) const;
// ORCA TODO: in the future, bbl presets should specify "bbl" printer agent id // ORCA TODO: in the future, bbl presets should specify "bbl" printer agent id
// then, all resolve and canonical would just be ORCA<->"" // then, all resolve and canonical would just be ORCA<->""
std::string canonical_printer_agent_id(const std::string& picked_id); std::string canonical_printer_agent_id(const std::string& picked_id);
@@ -511,7 +509,7 @@ public:
bool check_login(const std::string& provider = ORCA_CLOUD_PROVIDER); bool check_login(const std::string& provider = ORCA_CLOUD_PROVIDER);
void get_login_info(const std::string& provider = ORCA_CLOUD_PROVIDER); void get_login_info(const std::string& provider = ORCA_CLOUD_PROVIDER);
bool is_user_login(const std::string& provider = ORCA_CLOUD_PROVIDER); bool is_user_login(const std::string& provider = ORCA_CLOUD_PROVIDER);
const std::string& get_printer_cloud_provider() const; std::string get_printer_cloud_provider() const;
void request_user_login(int online_login = 0, const std::string& provider = ORCA_CLOUD_PROVIDER); void request_user_login(int online_login = 0, const std::string& provider = ORCA_CLOUD_PROVIDER);
void request_user_handle(int online_login = 0, const std::string& provider = ORCA_CLOUD_PROVIDER); void request_user_handle(int online_login = 0, const std::string& provider = ORCA_CLOUD_PROVIDER);
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#include <wx/mediactrl.h>
#include <wx/uri.h>
namespace Slic3r { namespace GUI {
class IMediaController
{
public:
virtual ~IMediaController() = default;
virtual void Load(wxURI url) = 0;
virtual void Play() = 0;
virtual void Stop() = 0;
virtual wxMediaState GetState() { return wxMediaState{}; }
virtual int GetLastError() const { return {}; };
virtual wxSize GetVideoSize() const { return {}; };
};
}} // namespace Slic3r::GUI
+2 -1
View File
@@ -15,6 +15,7 @@
#include "slic3r/Utils/FileTransferUtils.hpp" #include "slic3r/Utils/FileTransferUtils.hpp"
#include "slic3r/Utils/BBLNetworkPlugin.hpp" #include "slic3r/Utils/BBLNetworkPlugin.hpp"
#include "NetworkAgent.hpp"
namespace Slic3r { namespace Slic3r {
namespace GUI { namespace GUI {
@@ -216,7 +217,7 @@ void PrintJob::process(Ctl &ctl)
params.dev_ip = m_dev_ip; params.dev_ip = m_dev_ip;
params.use_ssl_for_ftp = m_local_use_ssl_for_ftp; params.use_ssl_for_ftp = m_local_use_ssl_for_ftp;
params.use_ssl_for_mqtt = m_local_use_ssl; params.use_ssl_for_mqtt = m_local_use_ssl;
params.username = "bblp"; params.username = m_agent->default_lan_username();
params.password = m_access_code; params.password = m_access_code;
// check access code and ip address // check access code and ip address
+2 -2
View File
@@ -127,7 +127,7 @@ void SendJob::process(Ctl &ctl)
if (m_is_check_mode) { if (m_is_check_mode) {
PrintParams verify_params; PrintParams verify_params;
verify_params.dev_ip = m_dev_ip; verify_params.dev_ip = m_dev_ip;
verify_params.username = "bblp"; verify_params.username = agent->default_lan_username();
verify_params.password = m_access_code; verify_params.password = m_access_code;
verify_params.use_ssl_for_ftp = m_local_use_ssl_for_ftp; verify_params.use_ssl_for_ftp = m_local_use_ssl_for_ftp;
verify_params.use_ssl_for_mqtt = m_local_use_ssl; verify_params.use_ssl_for_mqtt = m_local_use_ssl;
@@ -219,7 +219,7 @@ void SendJob::process(Ctl &ctl)
// local print access // local print access
params.dev_ip = m_dev_ip; params.dev_ip = m_dev_ip;
params.username = "bblp"; params.username = agent->default_lan_username();
params.password = m_access_code; params.password = m_access_code;
params.use_ssl_for_ftp = m_local_use_ssl_for_ftp; params.use_ssl_for_ftp = m_local_use_ssl_for_ftp;
params.use_ssl_for_mqtt = m_local_use_ssl; params.use_ssl_for_mqtt = m_local_use_ssl;
+147 -213
View File
@@ -1997,6 +1997,126 @@ bool MainFrame::can_reslice() const
return (m_plater != nullptr) && !m_plater->model().objects.empty(); return (m_plater != nullptr) && !m_plater->model().objects.empty();
} }
namespace {
// Orca: stable key persisted by the "remember last print action" preference. Reordering PrintSelectType
// must not silently remap a saved preference, so the key never derives from the enum value.
const char* print_select_type_key(MainFrame::PrintSelectType type)
{
switch (type) {
case MainFrame::ePrintAll: return "print_all";
case MainFrame::ePrintPlate: return "print_plate";
case MainFrame::eExportSlicedFile: return "export_sliced_file";
case MainFrame::eExportAllSlicedFile: return "export_all_sliced_file";
case MainFrame::eExportGcode: return "export_gcode";
case MainFrame::eSendGcode: return "send_gcode";
case MainFrame::eSendToPrinter: return "send_to_printer";
case MainFrame::eSendToPrinterAll: return "send_to_printer_all";
case MainFrame::ePrintMultiMachine: return "print_multi_machine";
case MainFrame::eUploadGcode: break; // Orca: no dropdown entry, never selectable
}
return "";
}
// Orca: single source for the print button and print dropdown labels
wxString print_select_type_label(MainFrame::PrintSelectType type)
{
switch (type) {
case MainFrame::ePrintAll: return _L("Print all");
case MainFrame::ePrintPlate: return _L("Print plate");
case MainFrame::eExportSlicedFile: return _L("Export plate sliced file");
case MainFrame::eExportAllSlicedFile: return _L("Export all sliced file");
case MainFrame::eExportGcode: return _L("Export G-code file");
case MainFrame::eSendGcode: return _L_CONTEXT("Print", "Verb");
case MainFrame::eSendToPrinter: return _L("Send");
case MainFrame::eSendToPrinterAll: return _L("Send all");
case MainFrame::ePrintMultiMachine: return _L("Send to Multi-device");
case MainFrame::eUploadGcode: break; // Orca: no dropdown entry, never selectable
}
return _L("Print plate");
}
} // namespace
std::vector<MainFrame::PrintSelectType> MainFrame::available_print_actions() const
{
std::vector<PrintSelectType> actions;
const auto preset_bundle = wxGetApp().preset_bundle;
const bool use_printer_agents = wxGetApp().app_config->get_bool("use_printer_agents");
if (preset_bundle && !preset_bundle->is_bbl_vendor() && !use_printer_agents) {
// ThirdParty actions
actions.push_back(eSendGcode);
// Orca: when the printer accepts a .gcode.3mf (the "Support 3MF as gcode" option),
// also offer exporting the sliced .gcode.3mf bundle
const auto* use_3mf_opt = preset_bundle->printers.get_edited_preset().config.option<ConfigOptionBool>("use_3mf");
if (use_3mf_opt != nullptr && use_3mf_opt->value)
actions.push_back(eExportSlicedFile);
actions.push_back(eExportGcode);
return actions;
}
// Orca Slicer actions
bool support_send = true;
bool support_print_all = true;
if (preset_bundle && !preset_bundle->use_bbl_network() && !use_printer_agents) {
support_send = false; // All 3rd print hosts do not have the send options
const auto& cfg = preset_bundle->printers.get_edited_preset().config;
const auto* host_type_opt = cfg.option<ConfigOptionEnum<PrintHostType>>("host_type");
// Only simply print support uploading all plates
support_print_all = host_type_opt != nullptr && host_type_opt->value == PrintHostType::htSimplyPrint;
}
actions.push_back(ePrintPlate);
if (support_print_all)
actions.push_back(ePrintAll);
if (support_send) {
actions.push_back(eSendToPrinter);
actions.push_back(eSendToPrinterAll);
}
if (enable_multi_machine)
actions.push_back(ePrintMultiMachine);
actions.push_back(eExportSlicedFile);
actions.push_back(eExportAllSlicedFile);
actions.push_back(eExportGcode);
return actions;
}
void MainFrame::select_print_action(PrintSelectType select_type)
{
m_print_btn->SetLabel(print_select_type_label(select_type));
m_print_select = select_type;
remember_print_select(select_type);
m_print_enable = get_enable_print_status();
m_print_btn->Enable(m_print_enable);
this->Layout();
fit_tab_labels(); // ORCA on label change
}
void MainFrame::remember_print_select(PrintSelectType select_type)
{
if (!wxGetApp().app_config->get_bool("remember_print_action"))
return;
// AppConfig is marked dirty here and flushed by the regular autosave
wxGetApp().app_config->set("last_print_action", print_select_type_key(select_type));
}
bool MainFrame::get_remembered_print_select(PrintSelectType& out) const
{
if (!wxGetApp().app_config->get_bool("remember_print_action"))
return false;
const std::string saved = wxGetApp().app_config->get("last_print_action");
if (saved.empty())
return false;
// Orca: only restore an action the current printer actually offers in the dropdown
for (PrintSelectType type : available_print_actions()) {
if (saved == print_select_type_key(type)) {
out = type;
return true;
}
}
return false;
}
wxBoxSizer* MainFrame::create_side_tools() wxBoxSizer* MainFrame::create_side_tools()
{ {
enable_multi_machine = wxGetApp().is_enable_multi_machine(); enable_multi_machine = wxGetApp().is_enable_multi_machine();
@@ -2016,6 +2136,14 @@ wxBoxSizer* MainFrame::create_side_tools()
m_print_btn = new SideButton(print_panel, _L("Print plate"), ""); m_print_btn = new SideButton(print_panel, _L("Print plate"), "");
m_print_option_btn = new SideButton(print_panel, "", "sidebutton_dropdown", 0, 14); m_print_option_btn = new SideButton(print_panel, "", "sidebutton_dropdown", 0, 14);
// Orca: restore the last used print/export action if the user opted to remember it
PrintSelectType remembered_print_select;
if (get_remembered_print_select(remembered_print_select)) {
m_print_select = remembered_print_select;
m_print_btn->SetLabel(print_select_type_label(remembered_print_select));
fit_tab_labels(); // ORCA on label change
}
auto slice_sizer = new wxBoxSizer(wxHORIZONTAL); auto slice_sizer = new wxBoxSizer(wxHORIZONTAL);
slice_sizer->Add(m_slice_option_btn, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, FromDIP(1)); slice_sizer->Add(m_slice_option_btn, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, FromDIP(1));
slice_sizer->Add(m_slice_btn, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(1)); slice_sizer->Add(m_slice_btn, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(1));
@@ -2178,193 +2306,15 @@ wxBoxSizer* MainFrame::create_side_tools()
m_print_option_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) m_print_option_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event)
{ {
SidePopup* p = new SidePopup(this); SidePopup* p = new SidePopup(this);
for (PrintSelectType type : available_print_actions()) {
if (wxGetApp().preset_bundle SideButton* btn = new SideButton(p, print_select_type_label(type), "");
&& !wxGetApp().preset_bundle->is_bbl_vendor() btn->SetCornerRadius(0);
&& !wxGetApp().app_config->get_bool("use_printer_agents")) { btn->Bind(wxEVT_BUTTON, [this, p, type](wxCommandEvent&) {
// ThirdParty Buttons select_print_action(type);
SideButton* export_gcode_btn = new SideButton(p, _L("Export G-code file"), "");
export_gcode_btn->SetCornerRadius(0);
export_gcode_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
m_print_btn->SetLabel(_L("Export G-code file"));
m_print_select = eExportGcode;
m_print_enable = get_enable_print_status();
m_print_btn->Enable(m_print_enable);
this->Layout();
fit_tab_labels(); // ORCA on label change
p->Dismiss(); p->Dismiss();
}); });
p->append_button(btn);
// upload and print
SideButton* send_gcode_btn = new SideButton(p, _L_CONTEXT("Print", "Verb"), "");
send_gcode_btn->SetCornerRadius(0);
send_gcode_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
m_print_btn->SetLabel(_L_CONTEXT("Print", "Verb"));
m_print_select = eSendGcode;
m_print_enable = get_enable_print_status();
m_print_btn->Enable(m_print_enable);
this->Layout();
fit_tab_labels(); // ORCA on label change
p->Dismiss();
});
p->append_button(send_gcode_btn);
// Orca: when the printer accepts a .gcode.3mf (the "Support 3MF as gcode" option),
// also offer exporting the sliced .gcode.3mf bundle
const auto& printer_config = wxGetApp().preset_bundle->printers.get_edited_preset().config;
const auto* use_3mf_opt = printer_config.option<ConfigOptionBool>("use_3mf");
if (use_3mf_opt != nullptr && use_3mf_opt->value) {
SideButton* export_sliced_file_btn = new SideButton(p, _L("Export plate sliced file"), "");
export_sliced_file_btn->SetCornerRadius(0);
export_sliced_file_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
m_print_btn->SetLabel(_L("Export plate sliced file"));
m_print_select = eExportSlicedFile;
m_print_enable = get_enable_print_status();
m_print_btn->Enable(m_print_enable);
this->Layout();
fit_tab_labels(); // ORCA on label change
p->Dismiss();
});
p->append_button(export_sliced_file_btn);
} }
p->append_button(export_gcode_btn);
}
else {
//Orca Slicer Buttons
SideButton* print_plate_btn = new SideButton(p, _L("Print plate"), "");
print_plate_btn->SetCornerRadius(0);
SideButton* send_to_printer_btn = new SideButton(p, _L("Send"), "");
send_to_printer_btn->SetCornerRadius(0);
SideButton* export_sliced_file_btn = new SideButton(p, _L("Export plate sliced file"), "");
export_sliced_file_btn->SetCornerRadius(0);
SideButton* export_all_sliced_file_btn = new SideButton(p, _L("Export all sliced file"), "");
export_all_sliced_file_btn->SetCornerRadius(0);
print_plate_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
m_print_btn->SetLabel(_L("Print plate"));
m_print_select = ePrintPlate;
m_print_enable = get_enable_print_status();
m_print_btn->Enable(m_print_enable);
this->Layout();
fit_tab_labels(); // ORCA on label change
p->Dismiss();
});
SideButton* print_all_btn = new SideButton(p, _L("Print all"), "");
print_all_btn->SetCornerRadius(0);
print_all_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
m_print_btn->SetLabel(_L("Print all"));
m_print_select = ePrintAll;
m_print_enable = get_enable_print_status();
m_print_btn->Enable(m_print_enable);
this->Layout();
fit_tab_labels(); // ORCA on label change
p->Dismiss();
});
send_to_printer_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
m_print_btn->SetLabel(_L("Send"));
m_print_select = eSendToPrinter;
m_print_enable = get_enable_print_status();
m_print_btn->Enable(m_print_enable);
this->Layout();
fit_tab_labels(); // ORCA on label change
p->Dismiss();
});
SideButton* send_to_printer_all_btn = new SideButton(p, _L("Send all"), "");
send_to_printer_all_btn->SetCornerRadius(0);
send_to_printer_all_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
m_print_btn->SetLabel(_L("Send all"));
m_print_select = eSendToPrinterAll;
m_print_enable = get_enable_print_status();
m_print_btn->Enable(m_print_enable);
this->Layout();
fit_tab_labels(); // ORCA on label change
p->Dismiss();
});
export_sliced_file_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
m_print_btn->SetLabel(_L("Export plate sliced file"));
m_print_select = eExportSlicedFile;
m_print_enable = get_enable_print_status();
m_print_btn->Enable(m_print_enable);
this->Layout();
fit_tab_labels(); // ORCA on label change
p->Dismiss();
});
export_all_sliced_file_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
m_print_btn->SetLabel(_L("Export all sliced file"));
m_print_select = eExportAllSlicedFile;
m_print_enable = get_enable_print_status();
m_print_btn->Enable(m_print_enable);
this->Layout();
fit_tab_labels(); // ORCA on label change
p->Dismiss();
});
bool support_send = true;
bool support_print_all = true;
const auto preset_bundle = wxGetApp().preset_bundle;
if (preset_bundle) {
if (preset_bundle->use_bbl_network() || wxGetApp().app_config->get_bool("use_printer_agents")) {
// BBL network support everything
} else {
support_send = false; // All 3rd print hosts do not have the send options
auto cfg = preset_bundle->printers.get_edited_preset().config;
const auto host_type = cfg.option<ConfigOptionEnum<PrintHostType>>("host_type")->value;
// Only simply print support uploading all plates
support_print_all = host_type == PrintHostType::htSimplyPrint;
}
}
p->append_button(print_plate_btn);
if (support_print_all) {
p->append_button(print_all_btn);
}
if (support_send) {
p->append_button(send_to_printer_btn);
p->append_button(send_to_printer_all_btn);
}
if (enable_multi_machine) {
SideButton* print_multi_machine_btn = new SideButton(p, _L("Send to Multi-device"), "");
print_multi_machine_btn->SetCornerRadius(0);
print_multi_machine_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
m_print_btn->SetLabel(_L("Send to Multi-device"));
m_print_select = ePrintMultiMachine;
m_print_enable = get_enable_print_status();
m_print_btn->Enable(m_print_enable);
this->Layout();
fit_tab_labels(); // ORCA on label change
p->Dismiss();
});
p->append_button(print_multi_machine_btn);
}
p->append_button(export_sliced_file_btn);
p->append_button(export_all_sliced_file_btn);
SideButton* export_gcode_btn = new SideButton(p, _L("Export G-code file"), "");
export_gcode_btn->SetCornerRadius(0);
export_gcode_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
m_print_btn->SetLabel(_L("Export G-code file"));
m_print_select = eExportGcode;
m_print_enable = get_enable_print_status();
m_print_btn->Enable(m_print_enable);
this->Layout();
fit_tab_labels(); // ORCA on label change
p->Dismiss();
});
p->append_button(export_gcode_btn);
}
p->Popup(m_print_btn); p->Popup(m_print_btn);
} }
); );
@@ -4224,38 +4174,22 @@ void MainFrame::on_config_changed(DynamicPrintConfig* config) const
void MainFrame::set_print_button_to_default(PrintSelectType select_type) void MainFrame::set_print_button_to_default(PrintSelectType select_type)
{ {
if (select_type == PrintSelectType::ePrintPlate) { // Orca: keep the user's remembered print/export action instead of resetting it to the computed
m_print_btn->SetLabel(_L("Print plate")); // default. get_remembered_print_select() already rejects anything this printer does not offer.
m_print_select = ePrintPlate; PrintSelectType remembered;
if (get_remembered_print_select(remembered))
select_type = remembered;
if (select_type == eUploadGcode)
return; // unsupported: no dropdown entry exists for this action
m_print_btn->SetLabel(print_select_type_label(select_type));
m_print_select = select_type;
// get_enable_print_status() already applies can_send_gcode() to the actions that need it
if (m_print_enable) if (m_print_enable)
m_print_enable = get_enable_print_status(); m_print_enable = get_enable_print_status();
m_print_btn->Enable(m_print_enable); m_print_btn->Enable(m_print_enable);
this->Layout(); this->Layout();
} else if (select_type == PrintSelectType::eSendGcode) {
m_print_btn->SetLabel(_L_CONTEXT("Print", "Verb"));
m_print_select = eSendGcode;
if (m_print_enable)
m_print_enable = get_enable_print_status() && can_send_gcode();
m_print_btn->Enable(m_print_enable);
this->Layout();
} else if (select_type == PrintSelectType::eExportGcode) {
m_print_btn->SetLabel(_L("Export G-code file"));
m_print_select = eExportGcode;
if (m_print_enable)
m_print_enable = get_enable_print_status() && can_send_gcode();
m_print_btn->Enable(m_print_enable);
this->Layout();
} else if (select_type == PrintSelectType::eExportSlicedFile) {
m_print_btn->SetLabel(_L("Export plate sliced file"));
m_print_select = eExportSlicedFile;
if (m_print_enable)
m_print_enable = get_enable_print_status();
m_print_btn->Enable(m_print_enable);
this->Layout();
} else {
// unsupport
return;
}
} }
void MainFrame::add_to_recent_projects(const wxString& filename) void MainFrame::add_to_recent_projects(const wxString& filename)
@@ -4443,7 +4377,7 @@ void MainFrame::load_printer_url()
if (auto *device_manager = wxGetApp().getDeviceManager()) { if (auto *device_manager = wxGetApp().getDeviceManager()) {
auto *machine = device_manager->get_selected_machine(); auto *machine = device_manager->get_selected_machine();
if (!machine) { if (!machine) {
auto machines = device_manager->get_my_machine_list(); auto machines = device_manager->get_my_machine_list(device_manager->get_current_printer_agent_id());
if (machines.size() == 1) if (machines.size() == 1)
machine = machines.begin()->second; machine = machines.begin()->second;
} }
+9
View File
@@ -13,6 +13,7 @@
#include <string> #include <string>
#include <map> #include <map>
#include <vector>
#include "GUI_Utils.hpp" #include "GUI_Utils.hpp"
#include "Event.hpp" #include "Event.hpp"
@@ -430,6 +431,14 @@ public:
// Propagate changed configuration from the Tab to the Plater and save changes to the AppConfig // Propagate changed configuration from the Tab to the Plater and save changes to the AppConfig
void on_config_changed(DynamicPrintConfig* cfg) const ; void on_config_changed(DynamicPrintConfig* cfg) const ;
void set_print_button_to_default(PrintSelectType select_type); void set_print_button_to_default(PrintSelectType select_type);
// Orca: the print/export actions the current printer offers, in the order the dropdown lists them.
// The dropdown is built from this, and a remembered action is only restored if it appears here.
std::vector<PrintSelectType> available_print_actions() const;
// Orca: apply an action picked from the print dropdown to the print button
void select_print_action(PrintSelectType select_type);
// Orca: remember the user's preferred print/export action across sessions (see "remember_print_action" preference)
void remember_print_select(PrintSelectType select_type);
bool get_remembered_print_select(PrintSelectType& out) const;
bool can_save() const; bool can_save() const;
bool can_save_as() const; bool can_save_as() const;
+1 -4
View File
@@ -98,10 +98,7 @@ void MarkdownTip::LoadStyle()
ph /= "tooltip/styled.html"; ph /= "tooltip/styled.html";
_data_dir = false; _data_dir = false;
} }
auto url = ph.string(); _tipView->LoadURL(file_url_from_path(ph));
std::replace(url.begin(), url.end(), '\\', '/');
url = "file:///" + url;
_tipView->LoadURL(from_u8(url));
_lastTip.clear(); _lastTip.clear();
} }
+291 -43
View File
@@ -1,4 +1,6 @@
#include "MediaPlayCtrl.h" #include "MediaPlayCtrl.h"
#include "WebMediaController.hpp"
#include "IPrinterAgent.hpp"
#include "Widgets/Button.hpp" #include "Widgets/Button.hpp"
#include "Widgets/CheckBox.hpp" #include "Widgets/CheckBox.hpp"
#include "Widgets/Label.hpp" #include "Widgets/Label.hpp"
@@ -7,6 +9,7 @@
#include "DeviceManager.hpp" #include "DeviceManager.hpp"
#include "DeviceCore/DevConfigUtil.h" #include "DeviceCore/DevConfigUtil.h"
#include "slic3r/Utils/NetworkAgent.hpp" #include "slic3r/Utils/NetworkAgent.hpp"
#include "slic3r/Utils/NetworkAgentFactory.hpp"
#include "libslic3r/Thread.hpp" #include "libslic3r/Thread.hpp"
#include "libslic3r/AppConfig.hpp" #include "libslic3r/AppConfig.hpp"
#include "I18N.hpp" #include "I18N.hpp"
@@ -15,11 +18,15 @@
#include "slic3r/Utils/BBLNetworkPlugin.hpp" #include "slic3r/Utils/BBLNetworkPlugin.hpp"
#include <algorithm>
#include <boost/lexical_cast.hpp> #include <boost/lexical_cast.hpp>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <boost/nowide/cstdio.hpp> #include <boost/nowide/cstdio.hpp>
#include <boost/nowide/fstream.hpp> #include <boost/nowide/fstream.hpp>
#include <boost/nowide/utf8_codecvt.hpp> #include <boost/nowide/utf8_codecvt.hpp>
#include <slic3r/GUI/DeviceManager.hpp>
#include <wx/mediactrl.h>
#undef pid_t #undef pid_t
#include <boost/process.hpp> #include <boost/process.hpp>
#ifdef __WIN32__ #ifdef __WIN32__
@@ -48,6 +55,7 @@ namespace GUI {
MediaPlayCtrl::MediaPlayCtrl(wxWindow *parent, wxMediaCtrl3 *media_ctrl, const wxPoint &pos, const wxSize &size) MediaPlayCtrl::MediaPlayCtrl(wxWindow *parent, wxMediaCtrl3 *media_ctrl, const wxPoint &pos, const wxSize &size)
: wxPanel(parent, wxID_ANY, pos, size) : wxPanel(parent, wxID_ANY, pos, size)
, m_media_ctrl(media_ctrl) , m_media_ctrl(media_ctrl)
, m_active_media_controller(media_ctrl)
{ {
SetLabel("MediaPlayCtrl"); SetLabel("MediaPlayCtrl");
SetBackgroundColour(*wxWHITE); SetBackgroundColour(*wxWHITE);
@@ -94,7 +102,10 @@ MediaPlayCtrl::MediaPlayCtrl(wxWindow *parent, wxMediaCtrl3 *media_ctrl, const w
}); });
m_button_play->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](auto &e) { TogglePlay(); }); m_button_play->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](auto &e) { TogglePlay(); });
m_button_play->Bind(wxEVT_RIGHT_UP, [this](auto & e) { m_media_ctrl->Play(); }); m_button_play->Bind(wxEVT_RIGHT_UP, [this](auto & e) {
if (m_active_media_controller)
m_active_media_controller->Play();
});
// Orca: live-view FAQ link binding removed (vendor URL) // Orca: live-view FAQ link binding removed (vendor URL)
Bind(wxEVT_RIGHT_UP, [this](auto & e) { Bind(wxEVT_RIGHT_UP, [this](auto & e) {
@@ -139,9 +150,12 @@ MediaPlayCtrl::MediaPlayCtrl(wxWindow *parent, wxMediaCtrl3 *media_ctrl, const w
MediaPlayCtrl::~MediaPlayCtrl() MediaPlayCtrl::~MediaPlayCtrl()
{ {
if (m_webrtc_ctrl)
m_webrtc_ctrl->Stop();
m_media_ctrl->EndExternalStream();
{ {
boost::unique_lock lock(m_mutex); boost::unique_lock lock(m_mutex);
m_tasks.push_back("<exit>"); m_tasks.push_back({"<exit>", nullptr});
m_cond.notify_all(); m_cond.notify_all();
} }
while (!m_thread.try_join_for(boost::chrono::milliseconds(10))) { while (!m_thread.try_join_for(boost::chrono::milliseconds(10))) {
@@ -151,9 +165,79 @@ MediaPlayCtrl::~MediaPlayCtrl()
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": " << this; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": " << this;
} }
void MediaPlayCtrl::SetWebMediaController(IMediaController *ctrl)
{
m_web_ctrl = ctrl;
set_active_media_controller(current_mode());
}
void MediaPlayCtrl::set_active_media_controller(CameraStreamMode mode)
{
switch (mode) {
case CameraStreamMode::http_snapshot:
if (auto *web_ctrl = dynamic_cast<WebMediaController *>(m_web_ctrl))
web_ctrl->set_mode(mode);
m_active_media_controller = m_web_ctrl;
break;
case CameraStreamMode::webrtc:
if (!m_webrtc_ctrl) {
m_webrtc_ctrl = std::make_unique<WebRtcMediaController>(
[this](const wxImage& image, wxSize size) { m_media_ctrl->SetExternalFrame(image, size); },
[this, token = std::weak_ptr<int>(m_token)](WebRtcMediaController::Status status) {
if (token.expired())
return;
CallAfter([this, status] { on_webrtc_status(status); });
});
}
m_active_media_controller = m_webrtc_ctrl.get();
break;
default:
m_active_media_controller = m_media_ctrl;
break;
}
}
CameraStreamMode MediaPlayCtrl::current_mode() const
{
auto agent = wxGetApp().getAgent();
return agent ? agent->get_camera_stream_mode() : CameraStreamMode::none;
}
void MediaPlayCtrl::SetMachineObject(MachineObject* obj) void MediaPlayCtrl::SetMachineObject(MachineObject* obj)
{ {
std::string machine = obj ? obj->get_dev_id() : ""; const CameraStreamMode mode = current_mode();
if (mode != m_last_mode) {
if (m_last_state != MEDIASTATE_IDLE) {
m_failed_code = 0; // a mode switch is not a stream failure - don't arm back-off
Stop(" ");
}
m_last_mode = mode;
}
set_active_media_controller(mode);
const bool uses_local_camera_url = mode == CameraStreamMode::http || mode == CameraStreamMode::https ||
mode == CameraStreamMode::http_snapshot || mode == CameraStreamMode::rtsp;
const bool uses_webrtc = mode == CameraStreamMode::webrtc;
const std::string machine = obj ? obj->get_dev_id() : "";
bool changed = false;
if (uses_local_camera_url) {
auto agent = wxGetApp().getAgent();
std::string url = agent ? agent->get_local_camera_stream_url() : "";
m_camera_exists = !url.empty();
Enable(obj && m_camera_exists);
changed = machine != m_machine || url != m_agent_camera_url;
m_agent_camera_url = url;
m_url = from_u8(url);
} else if (uses_webrtc) {
m_camera_exists = obj != nullptr;
Enable(obj != nullptr);
changed = machine != m_machine;
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::SetMachineObject webrtc: changed=" << changed
<< " last_state=" << m_last_state << " web_user_stopped=" << m_web_user_stopped;
m_url.clear();
m_agent_camera_url.clear();
} else {
if (obj) { if (obj) {
m_camera_exists = obj->has_ipcam; m_camera_exists = obj->has_ipcam;
m_dev_ver = obj->get_ota_version(); m_dev_ver = obj->get_ota_version();
@@ -167,12 +251,12 @@ void MediaPlayCtrl::SetMachineObject(MachineObject* obj)
if (DevPrinterConfigUtil::get_printer_series_str(obj->printer_type) == "series_o" && BBLNetworkPlugin::instance().use_legacy_network()) { if (DevPrinterConfigUtil::get_printer_series_str(obj->printer_type) == "series_o" && BBLNetworkPlugin::instance().use_legacy_network()) {
// Legacy plugin cannot support remote play for H2D, force using local mode // Legacy plugin cannot support remote play for H2D, force using local mode
m_remote_proto = MachineObject::LVR_None; m_remote_proto = LiveviewRemote::LVR_None;
} }
} else { } else {
m_camera_exists = false; m_camera_exists = false;
m_lan_mode = false; m_lan_mode = false;
m_lan_proto = MachineObject::LVL_None; m_lan_proto = LiveviewLocal::LVL_None;
m_lan_ip.clear(); m_lan_ip.clear();
m_lan_passwd.clear(); m_lan_passwd.clear();
m_dev_ver.clear(); m_dev_ver.clear();
@@ -181,11 +265,9 @@ void MediaPlayCtrl::SetMachineObject(MachineObject* obj)
m_device_busy = false; m_device_busy = false;
} }
Enable(obj && obj->is_info_ready() && obj->m_push_count > 0); Enable(obj && obj->is_info_ready() && obj->m_push_count > 0);
if (machine == m_machine) { if (machine == m_machine)
if (m_last_state == MEDIASTATE_IDLE && IsEnabled())
Play();
return; return;
}
m_machine = machine; m_machine = machine;
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl switch machine: " << m_machine; BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl switch machine: " << m_machine;
m_disable_lan = false; m_disable_lan = false;
@@ -204,6 +286,23 @@ void MediaPlayCtrl::SetMachineObject(MachineObject* obj)
m_next_retry = wxDateTime::Now() + wxTimeSpan::Seconds(2); m_next_retry = wxDateTime::Now() + wxTimeSpan::Seconds(2);
else else
SetStatus("", false); SetStatus("", false);
return;
}
m_machine = machine;
if (!changed)
return;
// A genuine target switch is not a stream failure and should clear the
// manual-stop state before the new target is allowed to play.
m_web_user_stopped = false;
if (uses_local_camera_url) {
m_failed_code = 0;
m_failed_retry = 0;
m_next_retry = wxDateTime();
}
if (m_last_state != MEDIASTATE_IDLE)
Stop(" ");
} }
wxString hide_id_middle_string(wxString const &str, size_t offset = 0, size_t length = -1) wxString hide_id_middle_string(wxString const &str, size_t offset = 0, size_t length = -1)
@@ -254,13 +353,71 @@ void refresh_agora_url(char const* device, char const* dev_ver, char const* chan
void MediaPlayCtrl::Play() void MediaPlayCtrl::Play()
{ {
if (!m_next_retry.IsValid() || wxDateTime::Now() < m_next_retry) if ((m_next_retry.IsValid() && wxDateTime::Now() < m_next_retry) || !IsShownOnScreen() || m_last_state != MEDIASTATE_IDLE)
return; return;
if (!IsShownOnScreen())
const CameraStreamMode mode = current_mode();
set_active_media_controller(mode);
m_last_mode = mode;
auto agent = wxGetApp().getAgent();
auto printer_agent = agent ? agent->get_printer_agent() : nullptr;
const bool is_bbl = (printer_agent ? printer_agent->get_agent_info().id : "") == BBL_PRINTER_AGENT_ID;
if (!is_bbl) {
const bool is_webrtc = mode == CameraStreamMode::webrtc;
const bool is_snapshot = mode == CameraStreamMode::http_snapshot;
const bool is_http_stream = mode == CameraStreamMode::http || mode == CameraStreamMode::https || mode == CameraStreamMode::rtsp;
if (!is_webrtc && !is_snapshot && !is_http_stream)
return; return;
if (m_last_state != MEDIASTATE_IDLE) {
auto *webrtc_ctrl = is_webrtc ? dynamic_cast<WebRtcMediaController *>(m_active_media_controller) : nullptr;
if (is_webrtc && (!webrtc_ctrl || !m_media_ctrl)) {
Stop(_L("Please confirm if the printer is connected."));
return; return;
} }
if (webrtc_ctrl && webrtc_ctrl->is_active())
return;
m_failed_code = 0;
if (!m_active_media_controller || m_machine.empty() || !IsEnabled() || !m_camera_exists ||
(!is_webrtc && m_url.IsEmpty())) {
Stop(_L("Please confirm if the printer is connected."));
return;
}
if (is_webrtc) {
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::Play webrtc: last_state=" << m_last_state << " failed_retry=" << m_failed_retry
<< " shown=" << IsShownOnScreen();
auto channel = agent ? agent->create_camera_signaling_channel(m_machine, wxGetApp().get_printer_cloud_provider()) : nullptr;
if (!channel) {
Stop(_L("Sign in to OrcaCloud to view the camera."));
return;
}
webrtc_ctrl->set_signalling_channel(std::move(channel));
m_media_ctrl->BeginExternalStream();
m_last_state = MEDIASTATE_INITIALIZING;
SetStatus(_L("Initializing..."), false);
} else if (is_snapshot) {
m_last_state = wxMEDIASTATE_PLAYING;
SetStatus(_L("Playing..."), false);
}
m_button_play->SetIcon("media_stop");
if (m_active_media_controller == m_media_ctrl) {
// wxMediaCtrl3 reports when it has a decoded frame; load() waits for
// that event before queuing Play so the stream is not marked stopped.
load();
} else {
m_active_media_controller->Load(wxURI(m_url));
m_active_media_controller->Play();
}
if (webrtc_ctrl) {
m_webrtc_epoch = webrtc_ctrl->epoch();
}
return;
}
m_failed_code = 0; m_failed_code = 0;
if (m_machine.empty()) { if (m_machine.empty()) {
Stop(_L("Please confirm if the printer is connected.")); Stop(_L("Please confirm if the printer is connected."));
@@ -281,16 +438,15 @@ void MediaPlayCtrl::Play()
} }
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::Play: " << m_lan_proto << m_remote_proto << m_disable_lan; BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::Play: " << m_lan_proto << m_remote_proto << m_disable_lan;
NetworkAgent *agent = wxGetApp().getAgent();
std::string agent_version = agent ? agent->get_version() : ""; std::string agent_version = agent ? agent->get_version() : "";
if (m_lan_proto > MachineObject::LVL_Disable && (m_lan_mode || !m_remote_proto) && !m_disable_lan && !m_lan_ip.empty()) { if (m_lan_proto > LiveviewLocal::LVL_Disable && (m_lan_mode || !m_remote_proto) && !m_disable_lan && !m_lan_ip.empty()) {
m_disable_lan = m_remote_proto && !m_lan_mode; // try remote next time m_disable_lan = m_remote_proto && !m_lan_mode; // try remote next time
std::string url; std::string url;
if (m_lan_proto == MachineObject::LVL_Local) if (m_lan_proto == LiveviewLocal::LVL_Local)
url = "bambu:///local/" + m_lan_ip + ".?port=6000&user=" + m_lan_user + "&passwd=" + m_lan_passwd; url = "bambu:///local/" + m_lan_ip + ".?port=6000&user=" + m_lan_user + "&passwd=" + m_lan_passwd;
else if (m_lan_proto == MachineObject::LVL_Rtsps) else if (m_lan_proto == LiveviewLocal::LVL_Rtsps)
url = "bambu:///rtsps___" + m_lan_user + ":" + m_lan_passwd + "@" + m_lan_ip + "/streaming/live/1?proto=rtsps"; url = "bambu:///rtsps___" + m_lan_user + ":" + m_lan_passwd + "@" + m_lan_ip + "/streaming/live/1?proto=rtsps";
else if (m_lan_proto == MachineObject::LVL_Rtsp) else if (m_lan_proto == LiveviewLocal::LVL_Rtsp)
url = "bambu:///rtsp___" + m_lan_user + ":" + m_lan_passwd + "@" + m_lan_ip + "/streaming/live/1?proto=rtsp"; url = "bambu:///rtsp___" + m_lan_user + ":" + m_lan_passwd + "@" + m_lan_ip + "/streaming/live/1?proto=rtsp";
url += "&device=" + m_machine; url += "&device=" + m_machine;
url += "&net_ver=" + agent_version; url += "&net_ver=" + agent_version;
@@ -312,8 +468,8 @@ void MediaPlayCtrl::Play()
// !m_lan_mode && !m_remote_proto && m_lan_proto == LVL_Disable (*) // !m_lan_mode && !m_remote_proto && m_lan_proto == LVL_Disable (*)
// !m_lan_mode && !m_remote_proto && m_lan_proto == LVL_None (x) // !m_lan_mode && !m_remote_proto && m_lan_proto == LVL_None (x)
if (m_lan_proto <= MachineObject::LVL_Disable && (m_lan_mode || !m_remote_proto)) { if (m_lan_proto <= LiveviewLocal::LVL_Disable && (m_lan_mode || !m_remote_proto)) {
Stop(m_lan_proto == MachineObject::LVL_None Stop(m_lan_proto == LiveviewLocal::LVL_None
? _L("A problem occurred. Please update the printer firmware and try again.") ? _L("A problem occurred. Please update the printer firmware and try again.")
: _L("LAN Only Liveview is off. Please turn on the liveview on printer screen.")); : _L("LAN Only Liveview is off. Please turn on the liveview on printer screen."));
return; return;
@@ -381,15 +537,78 @@ void MediaPlayCtrl::Play()
void start_ping_test(); void start_ping_test();
void MediaPlayCtrl::StopWebStream()
{
if (m_last_state == MEDIASTATE_IDLE)
return;
if (m_active_media_controller && m_active_media_controller == m_web_ctrl)
m_active_media_controller->Stop();
m_button_play->SetIcon("media_play");
m_last_state = MEDIASTATE_IDLE;
SetStatus(_L("Video Stopped."), false);
}
void MediaPlayCtrl::Stop(wxString const &msg, wxString const &msg2) void MediaPlayCtrl::Stop(wxString const &msg, wxString const &msg2)
{ {
const bool webrtc_active = m_last_mode == CameraStreamMode::webrtc &&
dynamic_cast<WebRtcMediaController *>(m_active_media_controller) != nullptr;
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::Stop: last_state=" << m_last_state
<< " webrtc_active=" << webrtc_active << " failed_code=" << m_failed_code
<< " msg='" << msg.ToUTF8().data() << "'";
if (webrtc_active) {
m_active_media_controller->Stop();
m_media_ctrl->EndExternalStream();
}
switch (m_last_mode) {
case CameraStreamMode::http:
case CameraStreamMode::https:
case CameraStreamMode::http_snapshot: {
const bool snapshot = m_last_mode == CameraStreamMode::http_snapshot;
if (m_last_state != MEDIASTATE_IDLE) {
if (snapshot) {
if (m_active_media_controller)
m_active_media_controller->Stop();
} else {
// http/https mode plays through the ffmpeg backend (m_media_ctrl), not
// the webview - tear its read thread down too, otherwise it keeps
// pulling and painting frames after the UI says "Video Stopped".
boost::unique_lock lock(m_mutex);
m_tasks.push_back({"<stop>", m_active_media_controller});
m_cond.notify_all();
}
m_button_play->SetIcon("media_play");
m_last_state = MEDIASTATE_IDLE;
if (!msg.IsEmpty())
SetStatus(msg);
else
SetStatus(_L("Video Stopped."), false);
// Keep retries bounded for an explicit or retry-driven playback attempt.
// m_failed_retry is cleared on success (onStateChanged) and on a deliberate
// machine switch (SetMachineObject); manual playback via TogglePlay resets it.
if (m_failed_code != 0) {
const bool auto_retry = wxGetApp().app_config->get("liveview", "auto_retry") != "false";
++m_failed_retry;
m_next_retry = auto_retry
? wxDateTime::Now() + wxTimeSpan::Seconds(std::min(5 * m_failed_retry, 30))
: wxDateTime::Now() + wxTimeSpan::Days(1); // "off": wait for a manual retry
}
} else if (!msg.IsEmpty()) {
SetStatus(msg, false);
}
return;
}
default:
break;
}
int last_state = m_last_state; int last_state = m_last_state;
if (m_last_state != MEDIASTATE_IDLE) { if (m_last_state != MEDIASTATE_IDLE) {
m_media_ctrl->InvalidateBestSize(); m_media_ctrl->InvalidateBestSize();
m_button_play->SetIcon("media_play"); m_button_play->SetIcon("media_play");
boost::unique_lock lock(m_mutex); boost::unique_lock lock(m_mutex);
m_tasks.push_back("<stop>"); if (!webrtc_active)
m_tasks.push_back({"<stop>", m_active_media_controller});
m_cond.notify_all(); m_cond.notify_all();
if (!msg.IsEmpty()) if (!msg.IsEmpty())
SetStatus(msg); SetStatus(msg);
@@ -454,15 +673,38 @@ void MediaPlayCtrl::Stop(wxString const &msg, wxString const &msg2)
m_next_retry = wxDateTime::Now() + wxTimeSpan::Seconds(5 * m_failed_retry); m_next_retry = wxDateTime::Now() + wxTimeSpan::Seconds(5 * m_failed_retry);
} }
void MediaPlayCtrl::on_webrtc_status(WebRtcMediaController::Status status)
{
// Drop CallAfter-queued events from a superseded Play attempt.
if (status.epoch != m_webrtc_epoch)
return;
if (status.kind == WebRtcMediaController::Status::Connecting) {
m_last_state = MEDIASTATE_INITIALIZING;
SetStatus(_L("Initializing..."), false);
} else if (status.kind == WebRtcMediaController::Status::Playing) {
m_last_state = wxMEDIASTATE_PLAYING;
m_failed_code = 0;
m_failed_retry = 0;
SetStatus(_L("Playing..."), false);
} else if (status.kind == WebRtcMediaController::Status::Failed) {
m_failed_code = static_cast<int>(status.code) + 1;
Stop();
}
// Status::Stopped needs no action: a genuine failure arrives as Failed, and
// a stop we initiated is already handled by Stop() itself.
}
void MediaPlayCtrl::TogglePlay() void MediaPlayCtrl::TogglePlay()
{ {
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::TogglePlay"; BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::TogglePlay";
if (m_last_state != MEDIASTATE_IDLE) { if (m_last_state != MEDIASTATE_IDLE) {
m_next_retry = wxDateTime(); m_next_retry = wxDateTime();
m_web_user_stopped = true;
Stop(); Stop();
} else { } else {
m_failed_retry = 0; m_failed_retry = 0;
m_user_triggered = true; m_user_triggered = true;
m_web_user_stopped = false;
if (m_last_user_play + wxTimeSpan::Minutes(5) < wxDateTime::Now()) { if (m_last_user_play + wxTimeSpan::Minutes(5) < wxDateTime::Now()) {
m_last_failed_codes.clear(); m_last_failed_codes.clear();
m_last_user_play = wxDateTime::Now(); m_last_user_play = wxDateTime::Now();
@@ -528,13 +770,13 @@ void MediaPlayCtrl::ToggleStream()
wxGetApp().app_config->set("not_show_vcamera_stop_prev", "1"); wxGetApp().app_config->set("not_show_vcamera_stop_prev", "1");
if (res == wxID_CANCEL) return; if (res == wxID_CANCEL) return;
} }
if (m_lan_proto > MachineObject::LVL_Disable && (m_lan_mode || !m_remote_proto) && !m_disable_lan && !m_lan_ip.empty()) { if (m_lan_proto > LiveviewLocal::LVL_Disable && (m_lan_mode || !m_remote_proto) && !m_disable_lan && !m_lan_ip.empty()) {
std::string url; std::string url;
if (m_lan_proto == MachineObject::LVL_Local) if (m_lan_proto == LiveviewLocal::LVL_Local)
url = "bambu:///local/" + m_lan_ip + ".?port=6000&user=" + m_lan_user + "&passwd=" + m_lan_passwd; url = "bambu:///local/" + m_lan_ip + ".?port=6000&user=" + m_lan_user + "&passwd=" + m_lan_passwd;
else if (m_lan_proto == MachineObject::LVL_Rtsps) else if (m_lan_proto == LiveviewLocal::LVL_Rtsps)
url = "bambu:///rtsps___" + m_lan_user + ":" + m_lan_passwd + "@" + m_lan_ip + "/streaming/live/1?proto=rtsps"; url = "bambu:///rtsps___" + m_lan_user + ":" + m_lan_passwd + "@" + m_lan_ip + "/streaming/live/1?proto=rtsps";
else if (m_lan_proto == MachineObject::LVL_Rtsp) else if (m_lan_proto == LiveviewLocal::LVL_Rtsp)
url = "bambu:///rtsp___" + m_lan_user + ":" + m_lan_passwd + "@" + m_lan_ip + "/streaming/live/1?proto=rtsp"; url = "bambu:///rtsp___" + m_lan_user + ":" + m_lan_passwd + "@" + m_lan_ip + "/streaming/live/1?proto=rtsp";
url += "&device=" + into_u8(m_machine); url += "&device=" + into_u8(m_machine);
url += "&dev_ver=" + m_dev_ver; url += "&dev_ver=" + m_dev_ver;
@@ -594,7 +836,9 @@ void MediaPlayCtrl::jump_to_play()
void MediaPlayCtrl::onStateChanged(wxMediaEvent &event) void MediaPlayCtrl::onStateChanged(wxMediaEvent &event)
{ {
auto last_state = m_last_state; auto last_state = m_last_state;
auto state = m_media_ctrl->GetState(); if (m_active_media_controller != m_media_ctrl)
return;
auto state = m_active_media_controller->GetState();
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::onStateChanged: " << state << ", last_state: " << last_state; BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::onStateChanged: " << state << ", last_state: " << last_state;
if ((int) state < 0) return; if ((int) state < 0) return;
{ {
@@ -606,14 +850,14 @@ void MediaPlayCtrl::onStateChanged(wxMediaEvent &event)
} }
if ((last_state == MEDIASTATE_IDLE || last_state == MEDIASTATE_INITIALIZING) && state == wxMEDIASTATE_STOPPED) { return; } if ((last_state == MEDIASTATE_IDLE || last_state == MEDIASTATE_INITIALIZING) && state == wxMEDIASTATE_STOPPED) { return; }
if ((last_state == wxMEDIASTATE_PAUSED || last_state == wxMEDIASTATE_PLAYING) && state == wxMEDIASTATE_STOPPED) { if ((last_state == wxMEDIASTATE_PAUSED || last_state == wxMEDIASTATE_PLAYING) && state == wxMEDIASTATE_STOPPED) {
m_failed_code = m_media_ctrl->GetLastError(); m_failed_code = m_active_media_controller->GetLastError();
Stop(); Stop();
return; return;
} }
if (last_state == MEDIASTATE_LOADING && (state == wxMEDIASTATE_STOPPED || state == wxMEDIASTATE_PAUSED)) { if (last_state == MEDIASTATE_LOADING && (state == wxMEDIASTATE_STOPPED || state == wxMEDIASTATE_PAUSED)) {
wxSize size = m_media_ctrl->GetVideoSize(); wxSize size = m_active_media_controller->GetVideoSize();
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::onStateChanged: size: " << size.x << "x" << size.y; BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::onStateChanged: size: " << size.x << "x" << size.y;
m_failed_code = m_media_ctrl->GetLastError(); m_failed_code = m_active_media_controller->GetLastError();
if (size.GetWidth() >= 320) { if (size.GetWidth() >= 320) {
m_last_state = state; m_last_state = state;
m_failed_code = 0; m_failed_code = 0;
@@ -623,7 +867,7 @@ void MediaPlayCtrl::onStateChanged(wxMediaEvent &event)
m_failed_retry = 0; m_failed_retry = 0;
m_disable_lan = false; m_disable_lan = false;
boost::unique_lock lock(m_mutex); boost::unique_lock lock(m_mutex);
m_tasks.push_back("<play>"); m_tasks.push_back({"<play>", m_active_media_controller});
m_cond.notify_all(); m_cond.notify_all();
} else if (event.GetId()) { } else if (event.GetId()) {
if (m_failed_code == 0) if (m_failed_code == 0)
@@ -666,7 +910,8 @@ void MediaPlayCtrl::load()
{ {
m_last_state = MEDIASTATE_LOADING; m_last_state = MEDIASTATE_LOADING;
SetStatus(_L("Loading...")); SetStatus(_L("Loading..."));
if (wxGetApp().app_config->get("internal_developer_mode") == "true") { const auto mode = current_mode();
if (mode == CameraStreamMode::none && wxGetApp().app_config->get("internal_developer_mode") == "true") {
std::string file_h264 = data_dir() + "/video.h264"; std::string file_h264 = data_dir() + "/video.h264";
std::string file_info = data_dir() + "/video.info"; std::string file_info = data_dir() + "/video.info";
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl dump video to " << file_h264; BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl dump video to " << file_h264;
@@ -677,7 +922,7 @@ void MediaPlayCtrl::load()
m_url = m_url + "&dump_info=" + boost::lexical_cast<std::string>(dump_info_file); m_url = m_url + "&dump_info=" + boost::lexical_cast<std::string>(dump_info_file);
} }
boost::unique_lock lock(m_mutex); boost::unique_lock lock(m_mutex);
m_tasks.push_back(m_url); m_tasks.push_back({m_url, m_active_media_controller});
m_cond.notify_all(); m_cond.notify_all();
} }
@@ -686,9 +931,8 @@ void MediaPlayCtrl::on_show_hide(wxShowEvent &evt)
evt.Skip(); evt.Skip();
if (m_isBeingDeleted) return; if (m_isBeingDeleted) return;
m_failed_retry = 0; m_failed_retry = 0;
if (m_next_retry.IsValid()) // Try open 2 seconds later, to avoid quick play/stop if (!IsShownOnScreen())
m_next_retry = wxDateTime::Now() + wxTimeSpan::Seconds(2); Stop();
IsShownOnScreen() ? Play() : Stop();
} }
void MediaPlayCtrl::media_proc() void MediaPlayCtrl::media_proc()
@@ -698,30 +942,34 @@ void MediaPlayCtrl::media_proc()
while (m_tasks.empty()) { while (m_tasks.empty()) {
m_cond.wait(lock); m_cond.wait(lock);
} }
wxString url = m_tasks.front(); MediaTask task = m_tasks.front();
if (m_tasks.size() >= 2 && !url.IsEmpty() && url[0] != '<' && m_tasks[1] == "<stop>") { if (m_tasks.size() >= 2 && !task.command.IsEmpty() && task.command[0] != '<' &&
BOOST_LOG_TRIVIAL(trace) << "MediaPlayCtrl: busy skip url: " << url; m_tasks[1].command == "<stop>" && task.controller == m_tasks[1].controller) {
BOOST_LOG_TRIVIAL(trace) << "MediaPlayCtrl: busy skip url: " << task.command;
m_tasks.pop_front(); m_tasks.pop_front();
m_tasks.pop_front(); m_tasks.pop_front();
continue; continue;
} }
lock.unlock(); lock.unlock();
if (url == "<stop>") { if (task.command == "<stop>") {
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: start stop"; BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: start stop";
m_media_ctrl->Stop(); if (task.controller)
task.controller->Stop();
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: end stop"; BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: end stop";
} }
else if (url == "<exit>") { else if (task.command == "<exit>") {
break; break;
} }
else if (url == "<play>") { else if (task.command == "<play>") {
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: start play"; BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: start play";
m_media_ctrl->Play(); if (task.controller)
task.controller->Play();
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: end play"; BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: end play";
} }
else { else {
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: start load"; BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: start load";
m_media_ctrl->Load(wxURI(url)); if (task.controller)
task.controller->Load(wxURI(task.command));
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: end load"; BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: end load";
} }
lock.lock(); lock.lock();
+23 -1
View File
@@ -9,6 +9,9 @@
#define MediaPlayCtrl_h #define MediaPlayCtrl_h
#include "wxMediaCtrl3.h" #include "wxMediaCtrl3.h"
#include "IMediaController.hpp"
#include "WebRtcMediaController.hpp"
#include "slic3r/Utils/IPrinterAgent.hpp"
#include <wx/panel.h> #include <wx/panel.h>
@@ -36,6 +39,10 @@ public:
void SetMachineObject(MachineObject * obj); void SetMachineObject(MachineObject * obj);
void SetWebMediaController(IMediaController *ctrl);
void StopWebStream();
bool IsStreaming() const; bool IsStreaming() const;
void ToggleStream(); void ToggleStream();
@@ -54,6 +61,7 @@ protected:
void TogglePlay(); void TogglePlay();
void SetStatus(wxString const &msg, bool hyperlink = true); void SetStatus(wxString const &msg, bool hyperlink = true);
void on_webrtc_status(WebRtcMediaController::Status status);
private: private:
void load(); void load();
@@ -66,6 +74,9 @@ private:
static bool get_stream_url(std::string *url = nullptr); static bool get_stream_url(std::string *url = nullptr);
CameraStreamMode current_mode() const;
void set_active_media_controller(CameraStreamMode mode);
private: private:
static inline const wxMediaState MEDIASTATE_IDLE = static_cast<wxMediaState>(3); static inline const wxMediaState MEDIASTATE_IDLE = static_cast<wxMediaState>(3);
static inline const wxMediaState MEDIASTATE_INITIALIZING = static_cast<wxMediaState>(4); static inline const wxMediaState MEDIASTATE_INITIALIZING = static_cast<wxMediaState>(4);
@@ -76,6 +87,13 @@ private:
std::shared_ptr<int> m_token = std::make_shared<int>(0); std::shared_ptr<int> m_token = std::make_shared<int>(0);
wxMediaCtrl3 * m_media_ctrl; wxMediaCtrl3 * m_media_ctrl;
IMediaController * m_active_media_controller = nullptr;
IMediaController * m_web_ctrl = nullptr;
std::unique_ptr<WebRtcMediaController> m_webrtc_ctrl;
CameraStreamMode m_last_mode = CameraStreamMode::none;
std::uint64_t m_webrtc_epoch = 0;
std::string m_agent_camera_url;
bool m_web_user_stopped = false;
wxMediaState m_last_state = MEDIASTATE_IDLE; wxMediaState m_last_state = MEDIASTATE_IDLE;
std::string m_machine; std::string m_machine;
int m_lan_proto = 0; int m_lan_proto = 0;
@@ -91,7 +109,11 @@ private:
bool m_disable_lan = false; bool m_disable_lan = false;
wxString m_url; wxString m_url;
std::deque<wxString> m_tasks; struct MediaTask {
wxString command;
IMediaController *controller = nullptr;
};
std::deque<MediaTask> m_tasks;
boost::mutex m_mutex; boost::mutex m_mutex;
boost::condition_variable m_cond; boost::condition_variable m_cond;
boost::thread m_thread; boost::thread m_thread;
+11 -1
View File
@@ -34,6 +34,8 @@
#include "DeviceCore/DevManager.h" #include "DeviceCore/DevManager.h"
#include <boost/log/trivial.hpp>
namespace Slic3r { namespace Slic3r {
namespace GUI { namespace GUI {
@@ -265,6 +267,7 @@ void MonitorPanel::msw_rescale()
void MonitorPanel::select_machine(std::string machine_sn) void MonitorPanel::select_machine(std::string machine_sn)
{ {
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: MonitorPanel::select_machine queueing machine_sn=" << machine_sn;
wxCommandEvent *event = new wxCommandEvent(wxEVT_COMMAND_CHOICE_SELECTED); wxCommandEvent *event = new wxCommandEvent(wxEVT_COMMAND_CHOICE_SELECTED);
event->SetString(machine_sn); event->SetString(machine_sn);
wxQueueEvent(this, event); wxQueueEvent(this, event);
@@ -282,13 +285,20 @@ void MonitorPanel::on_timer(wxTimerEvent& event)
void MonitorPanel::on_select_printer(wxCommandEvent& event) void MonitorPanel::on_select_printer(wxCommandEvent& event)
{ {
Slic3r::DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager(); Slic3r::DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
const std::string requested_dev_id = event.GetString().ToStdString();
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: MonitorPanel::on_select_printer requested_dev_id="
<< requested_dev_id << " device_manager=" << (dev ? "set" : "null");
if (!dev) return; if (!dev) return;
if ( dev->get_selected_machine() && (dev->get_selected_machine()->get_dev_id() != event.GetString().ToStdString()) && m_hms_panel) { if ( dev->get_selected_machine() && (dev->get_selected_machine()->get_dev_id() != event.GetString().ToStdString()) && m_hms_panel) {
m_hms_panel->clear_hms_tag(); m_hms_panel->clear_hms_tag();
} }
if (!dev->set_selected_machine(event.GetString().ToStdString())) const bool selected = dev->set_selected_machine(requested_dev_id);
BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: MonitorPanel::on_select_printer set_selected_machine result="
<< selected << " selected_dev_id="
<< (dev->get_selected_machine() ? dev->get_selected_machine()->get_dev_id() : "<null>");
if (!selected)
return; return;
set_default(); set_default();
+1 -22
View File
@@ -51,7 +51,7 @@ void DeviceItem::sync_state()
state_printable = 6; state_printable = 6;
} }
if (is_blocking_printing(obj_)) { if (wxGetApp().is_blocking_printing(obj_)) {
state_printable = 5; state_printable = 5;
} }
@@ -104,27 +104,6 @@ void DeviceItem::unselected()
} }
} }
bool DeviceItem::is_blocking_printing(MachineObject* obj_)
{
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
if (!dev) return true;
auto target_model = obj_->printer_type;
std::string source_model = "";
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
source_model = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle);
if (source_model != target_model) {
std::vector<std::string> compatible_machine = obj_->get_compatible_machine();
vector<std::string>::iterator it = find(compatible_machine.begin(), compatible_machine.end(), source_model);
if (it == compatible_machine.end()) {
return true;
}
}
return false;
}
void DeviceItem::update_item(const DeviceItem* item) void DeviceItem::update_item(const DeviceItem* item)
{ {
// Except for the selected status, everything else is updated // Except for the selected status, everything else is updated

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