Merge branch 'main' into feat/speed-dial-fixes

This commit is contained in:
Lam Wei Lun
2026-09-24 10:42:19 +08:00
105 changed files with 4862 additions and 1026 deletions
+220
View File
@@ -0,0 +1,220 @@
# Deferred Page Construction: High Level Design
## Why it exists
The main window is a notebook of tabs. When the first frame appears, one tab is on
screen and the others are not; some of them are opened later in the session, some
never, and some only exist for certain printers. Building a tab before the first frame
adds its cost to every startup, whether or not the tab is used.
This subsystem builds a tab's panel the first time the tab is shown. It also builds the
remaining tabs while the user is idle after startup, in units of tens of milliseconds,
so a click that lands in the middle of one waits for that unit and no longer. Startup
pays only for what the first frame shows, and the other tabs are usually built before
anyone opens them.
## The parts
`src/slic3r/GUI/Lazy.hpp`, `LazyPage.hpp`, `StagedBuild.hpp` and `IdleScheduler.hpp/.cpp`
(with its wx-free `PrebuildQueue.hpp`) are independent. A holder can hold anything a
factory makes, a page is a holder with a placeholder widget, a staged object can live
outside a holder, and the scheduler knows none of them; it runs tasks, which `MainFrame`
makes from the holders.
### Lazy<T>: the holder
Holds an object that a factory makes on first use, under a name for the log and a place
in the idle queue, both given by the owner. The header has no wx dependency; the busy
cursor for an on-demand build lives in `Lazy.cpp` and is skipped when there is no app,
so the holder is unit-tested.
- `get()` is null until the object is completely built; `built()` says the same.
- `ensure()` builds whatever is left now, under a busy cursor, and returns the object, or
null in the two cases below. A click on an unbuilt tab or a first open of a dialog goes
through this, and it logs the units and time it took.
- `build_step()` runs one unit of construction and returns true while more remain. The
first unit is the factory call, and each later unit is one `StagedBuild` step if the
type has them.
- `when_built(fn)` runs `fn(object)` now if it exists, otherwise once it is built.
- `pending()` says whether the idle prebuild has work here: not built, and the factory
has not returned null. A null factory result is logged and the holder stays unbuilt.
- A unit that pumps the event loop cannot re-enter the holder; a nested `build_step()`
does nothing and a nested `ensure()` returns null.
- After a unit throws, the holder and the scheduler still run the next one.
The holder does not own the object; its wx parent does, as for any window. A type with
one instance in the app derives from `LazyInstance<T>`, which points at that instance's
holder (the holder registers itself, and a recreated `MainFrame`'s holder replaces the
old frame's) and gives the type the static entry points the rest of the app uses,
`T::if_built()`, `T::ensure()` and `T::when_built(fn)`. They return null, or do
nothing, while no holder exists, so a caller needs no `mainframe` check.
`built()` is an atomic flag, since a job worker reads it through the statics
(`MainFrame::get_calibration_curr_tab()`); it is set after the object is complete.
`LazyBase` is the holder's type-free interface (`name()`, `built()`, `pending()`,
`build_step()`, `prebuild_order()`) and is what the scheduler side sees.
### LazyPage<Panel>: the placeholder
A `wxPanel` placed in the parent in place of the real panel, and a `Lazy<Panel>` whose
factory makes the panel inside it (by default `new Panel(parent)`). The notebook needs a
page object for the tab to exist and for `show_device()` to insert and remove tabs by
pointer, and the placeholder is that object. `MainFrame` creates every page once, named
after its `TAB_ID_*`, and keeps it for the frame's life; `show_device()` only moves
pages in and out of the book.
- `Show()` is forwarded to the panel, so a panel's own `Show()` override stays its
activation hook (refresh timers, machine sync) and `SelectPageByName()` works
unchanged. A show builds the panel unless the frame itself is still hidden, because
the book selects its first page as it is inserted; `MainFrame::Show()` shows the
current page again when the frame becomes visible, which builds it.
- `in_book()` says whether the parent notebook currently lists the page, and
`pending()` is that and not built, so a tab `show_device()` has taken out of the book
is not prebuilt.
- A panel built while its page is hidden stays hidden, and a completed panel gets the
dark-mode pass the frame ran before it existed.
The Compare presets dialog is a holder without a page. `MainFrame` keeps a
`Lazy<DiffPresetDialog>` whose factory constructs the dialog and binds its events, the
dialog derives from `LazyInstance`, and its callers use
`DiffPresetDialog::ensure()->show()` and `DiffPresetDialog::if_built()` like a tab's
callers do. Saving a preset refreshes the dialog only while it is shown, since `show()`
reloads the presets.
### StagedBuild: construction in units
A mixin for a panel whose constructor is too big to be one unit. The constructor builds
the skeleton (sizers, and the parts other code may touch) and queues the rest with
`add_build_step()`. `build_step()` runs one step, and `add_build_steps_of(child)`
forwards a child's steps so a nested panel is spread the same way; the parent is built
only once the child is, including steps the child queues later. Steps run in order, on
the main thread. A `Lazy<T>` recognises a staged type at compile time and runs its steps
one per unit.
Nothing may touch what a step builds before the last step has run. In practice:
- nothing paints the panel before it is complete, since a panel built at idle is hidden
with its page and one built on demand finishes inside `ensure()` before the event loop
runs again;
- members created in steps are initialised to null in the header, so a partially built
panel can be destroyed;
- timers and event handlers that use step content check `built()` first
(`MonitorPanel::update_all()`, `CalibrationPanel::update_all()`), and a child's steps
are queued before anything in the constructor can fire such a handler;
- a destructor that disconnects from step content checks `built()` first;
- a constructor or step does not take focus while the panel is off screen
(`IsShownOnScreen()` before `SetFocus()`), since it may run while the user is typing
elsewhere;
- where a step's widgets must keep their place in a sizer that later steps also fill, the
constructor adds an empty slot sizer in that position and the step fills the slot
(`StatusBasePanel`).
### IdleScheduler: when to build
A task is any `LazyBase`: a name for the log, `pending()`, `build_step()` and
`prebuild_order()`. `PrebuildQueue` holds the tasks by order (equal order in the order
added) and runs a slice, the units of the first pending task until it completes, the
budget is spent on the clock it is given, or the interrupt predicate says input arrived.
It has no wx dependency and is unit-tested with a fake clock. A task whose work is gone
is passed over and stays in the queue, so a tab that `show_device()` removes and later
re-inserts is pending again.
`IdleScheduler` drives the queue with the real clock, the app's input timestamp and the
Windows queue check, and logs each unit and slice. Each slice is a timer message: a
period of 250 ms while waiting for the user to go quiet, and a one-shot of zero after a
slice that left work, so the event loop dispatches whatever it has queued (paint,
timers, input) before the next slice runs. Chaining slices with `CallAfter` would not do
this: wx drains pending events fully before the next native message, on every platform.
On GTK the one-shot is 5 ms, because a due GLib timeout runs ahead of the redraw and
idle sources that paint and deliver posted events.
A slice runs only once the user has been idle for the quiet time, and the timer stops
itself once no task is pending. The tick period, quiet time and slice length are
constants in `IdleScheduler.cpp`. A unit cannot be interrupted once started, so the
largest unit bounds click latency on Windows; on the other platforms a click also waits
for the rest of the slice. A unit that pumps the event loop lets the timer fire inside
its own slice, and that tick does nothing.
The tasks are made by their owners. `MainFrame::prebuild_pages_when_idle()` registers
every `LazyPage` the frame created, in or out of the book (`pending()` is false for a
page out of the book), the Compare presets holder, and the Prepare sidebar's settings
page from `ParamsPanel::settings_page_prebuild()`, whose first unit selects the default
tab if none is selected yet and whose later units build one option group each.
`show_device()` only restarts the timer. `MainFrame` owns the scheduler because it owns
everything the tasks build, and clearing the queue with the frame is what keeps a task
from outliving its object.
Idle time comes from `GUI_App::FilterEvent`, which timestamps mouse and keyboard events
(`wxEVT_CATEGORY_USER_INPUT` minus command events, which all claim that category), and
`GUI_App::input_idle_ms()` reports it. On Windows a slice additionally refuses to start
when the message queue holds keyboard, button, touch or pen input. Mouse moves are
excluded because Windows synthesises one whenever a window appears under the cursor,
which every unit does. Other platforms use the timestamp alone.
Once every registered page is built the timer is stopped and the subsystem costs
nothing.
## Rules
**What builds before the first frame.** The Prepare tab's plater, because `post_init()`
needs its GL canvas on screen to initialise OpenGL in every startup state, and the start
page the user configured. Home is built by the first `MainFrame::Show()`, Prepare's
settings page by selecting the tab. `post_init()` passes through the Prepare tab for GL
init under `Freeze()` with `MainFrame::select_prepare_for_gl_init()`, which changes the
selection without the page-changed event, so nothing else is built for that pass.
**Reaching a lazy object.** A caller uses the type's own statics. `T::if_built()` may
return null and is for telling the object something it can live without (a rescale, a
colour change, a status update). `T::ensure()` builds the object and is for navigating
to it or for a caller that is about to show it. A caller that tells the object something
it would not fetch for itself on construction uses `T::when_built()`, which keeps the
message until the object exists. A panel that pulls its state when constructed (the Home
page requests the recent list on load, the Device tab reads the device manager on show)
is reached with `if_built()`; one that cannot pull gets `when_built()`.
**Unit size.** A unit cannot be interrupted, so it should stay within the slice length on
a fast machine. A constructor above that is staged. A single widget above it is the
floor unless the widget itself is split.
**Order.** Cheapest and most likely to be opened first, given where `MainFrame` creates
each holder. The settings page is 0 (the Prepare tab's own content), Home 10, Device 20
(a Bambu user's usual second stop), Calibration 30, Multi-device 40, the web Device
view 50, Project 60 (a second WebView2 instance) and the Compare presets dialog 100;
steps of ten so a new tab takes a value between its neighbours without renumbering
them. `MainFrame::prebuild_pages_when_idle()` registers the tasks once, from
`post_init()`.
**Never prebuilt.** A holder with a negative order, for a tab that few sessions open
and that costs more to build unasked than it saves (the Design tab), and plugin-provided
tabs, which are Python-side and not lazy pages.
## Adopting it
A lazy tab needs:
1. The panel derived from `LazyInstance<Panel>`, since a tab's panel has one instance.
2. A `LazyPage<Panel>*` member in `MainFrame`, created once in `init_tabpanel()` with
its `TAB_ID_*` as the name, its place by the order rule (and a factory if
`new Panel(parent)` is not enough), added to `m_lazy_pages`, and used wherever the
tab is added to or looked up in the notebook.
3. Every use of the panel outside `MainFrame` going through one of the panel's statics,
chosen by the rule above. When converting an existing member, `grep` for it; the
compiler finds the rest.
4. A constructor that copes with the frame already existing and the user being busy
elsewhere, since `wxGetApp().mainframe` is set, the frame may be shown, and the user
may be typing when a lazy panel is built: no `SetFocus()` while off screen, and
whatever the constructor did through a `MainFrame` accessor before (a mode update, a
deferred URL) done in the constructor itself.
To stage a heavy constructor, inherit `StagedBuild`, keep the skeleton in the constructor,
move the rest into `add_build_step()` lambdas in the original order, and follow the
staged-panel rules above. Measure the units. A step that is still one big widget has to
be split inside that widget or accepted as the floor.
Verification is by log. `MainFrame::prebuild_pages_when_idle` lists the queue it
registered, `IdleScheduler::tick` reports each completed task by name at info level and
each slice and unit at debug level, and `Lazy::ensure` reports an object a user built
on demand with the units and time it took. A run from the configured start
page shows every registered page complete, in order, with no unit longer than intended.
A click on a tab mid-prebuild shows the finished panel with an `ensure` line for what was
left, and the slices resume for the remaining tasks once the user is idle again.
+121 -17
View File
@@ -1,7 +1,7 @@
{
"name": "Sovol",
"url": "",
"version": "02.04.00.04",
"version": "02.04.00.07",
"force_update": "0",
"description": "Sovol configurations",
"machine_model_list": [
@@ -167,10 +167,6 @@
"name": "0.20mm Standard @Sovol SV08 MAX 0.4 nozzle",
"sub_path": "process/0.20mm Standard @Sovol SV08 MAX 0.4 nozzle.json"
},
{
"name": "0.20mm Standard @Sovol Zero 0.4 nozzle",
"sub_path": "process/0.20mm Standard @Sovol Zero 0.4 nozzle.json"
},
{
"name": "0.28mm Fast @Sovol SV06 ACE",
"sub_path": "process/0.28mm Fast @Sovol SV06 ACE 0.4 nozzle.json"
@@ -198,6 +194,74 @@
{
"name": "0.40mm Standard @Sovol SV08 MAX 0.8 nozzle",
"sub_path": "process/0.40mm Standard @Sovol SV08 MAX 0.8 nozzle.json"
},
{
"name": "fdm_process_zero",
"sub_path": "process/fdm_process_zero.json"
},
{
"name": "0.06mm Quality @Sovol Zero 0.2 nozzle",
"sub_path": "process/0.06mm Quality @Sovol Zero 0.2 nozzle.json"
},
{
"name": "0.10mm Standard @Sovol Zero 0.2 nozzle",
"sub_path": "process/0.10mm Standard @Sovol Zero 0.2 nozzle.json"
},
{
"name": "0.12mm Quality @Sovol Zero 0.4 nozzle",
"sub_path": "process/0.12mm Quality @Sovol Zero 0.4 nozzle.json"
},
{
"name": "0.14mm Fast @Sovol Zero 0.2 nozzle",
"sub_path": "process/0.14mm Fast @Sovol Zero 0.2 nozzle.json"
},
{
"name": "0.15mm Quality @Sovol Zero 0.6 nozzle",
"sub_path": "process/0.15mm Quality @Sovol Zero 0.6 nozzle.json"
},
{
"name": "0.20mm Quality @Sovol Zero 0.8 nozzle",
"sub_path": "process/0.20mm Quality @Sovol Zero 0.8 nozzle.json"
},
{
"name": "0.20mm Standard @Sovol Zero 0.4 nozzle",
"sub_path": "process/0.20mm Standard @Sovol Zero 0.4 nozzle.json"
},
{
"name": "0.25mm Quality @Sovol Zero 1.0 nozzle",
"sub_path": "process/0.25mm Quality @Sovol Zero 1.0 nozzle.json"
},
{
"name": "0.25mm SPEEDBENCHY @Sovol Zero 0.4 nozzle",
"sub_path": "process/0.25mm SPEEDBENCHY @Sovol Zero 0.4 nozzle.json"
},
{
"name": "0.28mm Fast @Sovol Zero 0.4 nozzle",
"sub_path": "process/0.28mm Fast @Sovol Zero 0.4 nozzle.json"
},
{
"name": "0.30mm Standard @Sovol Zero 0.6 nozzle",
"sub_path": "process/0.30mm Standard @Sovol Zero 0.6 nozzle.json"
},
{
"name": "0.40mm Standard @Sovol Zero 0.8 nozzle",
"sub_path": "process/0.40mm Standard @Sovol Zero 0.8 nozzle.json"
},
{
"name": "0.42mm Fast @Sovol Zero 0.6 nozzle",
"sub_path": "process/0.42mm Fast @Sovol Zero 0.6 nozzle.json"
},
{
"name": "0.50mm Standard @Sovol Zero 1.0 nozzle",
"sub_path": "process/0.50mm Standard @Sovol Zero 1.0 nozzle.json"
},
{
"name": "0.56mm Fast @Sovol Zero 0.8 nozzle",
"sub_path": "process/0.56mm Fast @Sovol Zero 0.8 nozzle.json"
},
{
"name": "0.70mm Fast @Sovol Zero 1.0 nozzle",
"sub_path": "process/0.70mm Fast @Sovol Zero 1.0 nozzle.json"
}
],
"filament_list": [
@@ -249,6 +313,10 @@
"name": "Generic PETG @Sovol Zero",
"sub_path": "filament/Generic PETG @Sovol Zero.json"
},
{
"name": "Generic PETG @Sovol Zero Hardened Steel nozzle",
"sub_path": "filament/Generic PETG @Sovol Zero Hardened Steel nozzle.json"
},
{
"name": "Generic PLA @Sovol SV06 ACE",
"sub_path": "filament/Generic PLA @Sovol SV06 ACE.json"
@@ -277,6 +345,14 @@
"name": "Generic PLA @Sovol Zero",
"sub_path": "filament/Generic PLA @Sovol Zero.json"
},
{
"name": "Generic PLA @Sovol Zero Hardened Steel nozzle",
"sub_path": "filament/Generic PLA @Sovol Zero Hardened Steel nozzle.json"
},
{
"name": "Generic PLA SPEEDBENCHY @Sovol Zero",
"sub_path": "filament/Generic PLA SPEEDBENCHY @Sovol Zero.json"
},
{
"name": "Generic PLA Silk @Sovol SV08 MAX",
"sub_path": "filament/Generic PLA Silk @Sovol SV08 MAX.json"
@@ -285,6 +361,10 @@
"name": "Generic PLA Silk @Sovol Zero",
"sub_path": "filament/Generic PLA Silk @Sovol Zero.json"
},
{
"name": "Generic PLA Silk @Sovol Zero Hardened Steel nozzle",
"sub_path": "filament/Generic PLA Silk @Sovol Zero Hardened Steel nozzle.json"
},
{
"name": "Generic TPU @Sovol SV06 ACE",
"sub_path": "filament/Generic TPU @Sovol SV06 ACE.json"
@@ -312,18 +392,6 @@
{
"name": "SUNLU PETG @Sovol SV08 MAX",
"sub_path": "filament/SUNLU PETG @Sovol SV08 MAX.json"
},
{
"name": "Sovol Zero PETG HS Nozzle",
"sub_path": "filament/Sovol Zero PETG HS Nozzle.json"
},
{
"name": "Sovol Zero PLA Basic HS Nozzle",
"sub_path": "filament/Sovol Zero PLA Basic HS Nozzle.json"
},
{
"name": "Sovol Zero PLA Silk HS Nozzle",
"sub_path": "filament/Sovol Zero PLA Silk HS Nozzle.json"
}
],
"machine_list": [
@@ -415,9 +483,45 @@
"name": "Sovol SV08 MAX 0.8 nozzle",
"sub_path": "machine/Sovol SV08 MAX 0.8 nozzle.json"
},
{
"name": "Sovol Zero 0.2 nozzle",
"sub_path": "machine/Sovol Zero 0.2 nozzle.json"
},
{
"name": "Sovol Zero 0.4 nozzle",
"sub_path": "machine/Sovol Zero 0.4 nozzle.json"
},
{
"name": "Sovol Zero 0.6 nozzle",
"sub_path": "machine/Sovol Zero 0.6 nozzle.json"
},
{
"name": "Sovol Zero 0.8 nozzle",
"sub_path": "machine/Sovol Zero 0.8 nozzle.json"
},
{
"name": "Sovol Zero 1.0 nozzle",
"sub_path": "machine/Sovol Zero 1.0 nozzle.json"
},
{
"name": "Sovol Zero 0.2 Hardened Steel nozzle",
"sub_path": "machine/Sovol Zero 0.2 Hardened Steel nozzle.json"
},
{
"name": "Sovol Zero 0.4 Hardened Steel nozzle",
"sub_path": "machine/Sovol Zero 0.4 Hardened Steel nozzle.json"
},
{
"name": "Sovol Zero 0.6 Hardened Steel nozzle",
"sub_path": "machine/Sovol Zero 0.6 Hardened Steel nozzle.json"
},
{
"name": "Sovol Zero 0.8 Hardened Steel nozzle",
"sub_path": "machine/Sovol Zero 0.8 Hardened Steel nozzle.json"
},
{
"name": "Sovol Zero 1.0 Hardened Steel nozzle",
"sub_path": "machine/Sovol Zero 1.0 Hardened Steel nozzle.json"
}
]
}
@@ -1,10 +1,11 @@
{
"type": "filament",
"name": "Generic ABS @Sovol Zero",
"inherits": "Generic ABS @System",
"renamed_from": "Sovol Zero ABS",
"inherits": "fdm_filament_abs",
"from": "system",
"setting_id": "g6sSyyIFYJUzlXsp",
"filament_id": "OFY9muEs",
"instantiation": "true",
"filament_flow_ratio": [
"0.98"
@@ -73,6 +74,39 @@
"0"
],
"compatible_printers": [
"Sovol Zero 0.4 nozzle"
"Sovol Zero 0.2 nozzle",
"Sovol Zero 0.4 nozzle",
"Sovol Zero 0.6 nozzle",
"Sovol Zero 0.8 nozzle",
"Sovol Zero 1.0 nozzle",
"Sovol Zero 0.2 Hardened Steel nozzle",
"Sovol Zero 0.4 Hardened Steel nozzle",
"Sovol Zero 0.6 Hardened Steel nozzle",
"Sovol Zero 0.8 Hardened Steel nozzle",
"Sovol Zero 1.0 Hardened Steel nozzle"
],
"cool_plate_temp": [
"80"
],
"eng_plate_temp": [
"80"
],
"textured_plate_temp": [
"80"
],
"cool_plate_temp_initial_layer": [
"80"
],
"eng_plate_temp_initial_layer": [
"80"
],
"textured_plate_temp_initial_layer": [
"80"
],
"filament_end_gcode": [
"; filament end gcode \n"
],
"filament_density": [
"1.10"
]
}
@@ -1,11 +1,12 @@
{
"type": "filament",
"filament_id": "OFLakOUI",
"setting_id": "yRKCiPMN6fxibcyg",
"name": "Generic PC @Sovol Zero",
"inherits": "Generic PC @System",
"renamed_from": "Sovol Zero PC",
"from": "system",
"setting_id": "yRKCiPMN6fxibcyg",
"instantiation": "true",
"inherits": "fdm_filament_pc",
"filament_flow_ratio": [
"0.98"
],
@@ -13,7 +14,16 @@
"21"
],
"compatible_printers": [
"Sovol Zero 0.4 nozzle"
"Sovol Zero 0.2 nozzle",
"Sovol Zero 0.4 nozzle",
"Sovol Zero 0.6 nozzle",
"Sovol Zero 0.8 nozzle",
"Sovol Zero 1.0 nozzle",
"Sovol Zero 0.2 Hardened Steel nozzle",
"Sovol Zero 0.4 Hardened Steel nozzle",
"Sovol Zero 0.6 Hardened Steel nozzle",
"Sovol Zero 0.8 Hardened Steel nozzle",
"Sovol Zero 1.0 Hardened Steel nozzle"
],
"nozzle_temperature_initial_layer": [
"290"
@@ -51,7 +61,9 @@
"slow_down_min_speed": [
"10"
],
"dont_slow_down_outer_wall": "0",
"dont_slow_down_outer_wall": [
"0"
],
"overhang_fan_speed": [
"20"
],
@@ -61,8 +73,19 @@
"temperature_vitrification": [
"60"
],
"additional_cooling_fan_speed": "0",
"activate_air_filtration": "1",
"during_print_exhaust_fan_speed": "50",
"complete_print_exhaust_fan_speed": "50"
"additional_cooling_fan_speed": [
"0"
],
"activate_air_filtration": [
"1"
],
"during_print_exhaust_fan_speed": [
"50"
],
"complete_print_exhaust_fan_speed": [
"50"
],
"filament_end_gcode": [
"; filament end gcode \n"
]
}
@@ -0,0 +1,116 @@
{
"type": "filament",
"filament_id": "OFYPdQJh",
"setting_id": "d9QbCESNz3ZLU6eu",
"name": "Generic PETG @Sovol Zero Hardened Steel nozzle",
"renamed_from": "Sovol Zero PETG HS Nozzle",
"from": "system",
"instantiation": "true",
"inherits": "fdm_filament_pet",
"filament_flow_ratio": [
"0.98"
],
"enable_pressure_advance": [
"1"
],
"pressure_advance": [
"0.048"
],
"filament_max_volumetric_speed": [
"15"
],
"nozzle_temperature_initial_layer": [
"265"
],
"nozzle_temperature": [
"250"
],
"nozzle_temperature_range_low": [
"230"
],
"nozzle_temperature_range_high": [
"280"
],
"hot_plate_temp": [
"85"
],
"hot_plate_temp_initial_layer": [
"85"
],
"fan_min_speed": [
"10"
],
"fan_max_speed": [
"40"
],
"fan_cooling_layer_time": [
"50"
],
"full_fan_speed_layer": [
"3"
],
"slow_down_layer_time": [
"10"
],
"slow_down_min_speed": [
"10"
],
"overhang_fan_speed": [
"70"
],
"overhang_fan_threshold": [
"10%"
],
"temperature_vitrification": [
"60"
],
"close_fan_the_first_x_layers": [
"1"
],
"filament_retraction_length": [
"0.5"
],
"filament_z_hop": [
"0.4"
],
"activate_air_filtration": [
"1"
],
"during_print_exhaust_fan_speed": [
"50"
],
"complete_print_exhaust_fan_speed": [
"50"
],
"compatible_printers": [
"Sovol Zero 0.2 Hardened Steel nozzle",
"Sovol Zero 0.4 Hardened Steel nozzle",
"Sovol Zero 0.6 Hardened Steel nozzle",
"Sovol Zero 0.8 Hardened Steel nozzle",
"Sovol Zero 1.0 Hardened Steel nozzle"
],
"cool_plate_temp": [
"85"
],
"eng_plate_temp": [
"85"
],
"textured_plate_temp": [
"85"
],
"cool_plate_temp_initial_layer": [
"85"
],
"eng_plate_temp_initial_layer": [
"85"
],
"textured_plate_temp_initial_layer": [
"85"
],
"filament_end_gcode": [
"; filament end gcode \n"
],
"filament_start_gcode": [
"; filament start gcode\n"
]
}
@@ -1,16 +1,21 @@
{
"type": "filament",
"filament_id": "OFYPdQJh",
"setting_id": "i4OW2eAtXOKdtVNj",
"name": "Generic PETG @Sovol Zero",
"inherits": "Generic PETG @System",
"renamed_from": "Sovol Zero PETG",
"from": "system",
"setting_id": "i4OW2eAtXOKdtVNj",
"instantiation": "true",
"inherits": "fdm_filament_pet",
"filament_flow_ratio": [
"1.0348"
],
"enable_pressure_advance": "1",
"pressure_advance": "0.046",
"enable_pressure_advance": [
"1"
],
"pressure_advance": [
"0.046"
],
"filament_max_volumetric_speed": [
"15"
],
@@ -68,10 +73,44 @@
"filament_z_hop": [
"0.4"
],
"activate_air_filtration": "1",
"during_print_exhaust_fan_speed": "50",
"complete_print_exhaust_fan_speed": "50",
"activate_air_filtration": [
"1"
],
"during_print_exhaust_fan_speed": [
"50"
],
"complete_print_exhaust_fan_speed": [
"50"
],
"compatible_printers": [
"Sovol Zero 0.4 nozzle"
"Sovol Zero 0.2 nozzle",
"Sovol Zero 0.4 nozzle",
"Sovol Zero 0.6 nozzle",
"Sovol Zero 0.8 nozzle",
"Sovol Zero 1.0 nozzle"
],
"cool_plate_temp": [
"85"
],
"eng_plate_temp": [
"85"
],
"textured_plate_temp": [
"85"
],
"cool_plate_temp_initial_layer": [
"85"
],
"eng_plate_temp_initial_layer": [
"85"
],
"textured_plate_temp_initial_layer": [
"85"
],
"filament_end_gcode": [
"; filament end gcode \n"
],
"filament_start_gcode": [
"; filament start gcode\n"
]
}
@@ -0,0 +1,110 @@
{
"type": "filament",
"filament_id": "OFDSrzZ8",
"setting_id": "nZgETe4CTpicEjQX",
"name": "Generic PLA @Sovol Zero Hardened Steel nozzle",
"renamed_from": "Sovol Zero PLA Basic HS Nozzle",
"from": "system",
"instantiation": "true",
"inherits": "fdm_filament_pla",
"filament_flow_ratio": [
"1.0348"
],
"filament_max_volumetric_speed": [
"21"
],
"enable_pressure_advance": [
"1"
],
"pressure_advance": [
"0.03"
],
"compatible_printers": [
"Sovol Zero 0.2 Hardened Steel nozzle",
"Sovol Zero 0.4 Hardened Steel nozzle",
"Sovol Zero 0.6 Hardened Steel nozzle",
"Sovol Zero 0.8 Hardened Steel nozzle",
"Sovol Zero 1.0 Hardened Steel nozzle"
],
"nozzle_temperature_initial_layer": [
"245"
],
"nozzle_temperature": [
"230"
],
"nozzle_temperature_range_low": [
"190"
],
"nozzle_temperature_range_high": [
"250"
],
"hot_plate_temp": [
"65"
],
"hot_plate_temp_initial_layer": [
"65"
],
"fan_min_speed": [
"70"
],
"fan_max_speed": [
"100"
],
"fan_cooling_layer_time": [
"80"
],
"full_fan_speed_layer": [
"3"
],
"slow_down_layer_time": [
"5"
],
"slow_down_min_speed": [
"10"
],
"dont_slow_down_outer_wall": [
"0"
],
"overhang_fan_speed": [
"100"
],
"overhang_fan_threshold": [
"50%"
],
"temperature_vitrification": [
"60"
],
"additional_cooling_fan_speed": [
"75%"
],
"activate_air_filtration": [
"1"
],
"during_print_exhaust_fan_speed": [
"80%"
],
"complete_print_exhaust_fan_speed": [
"80%"
],
"cool_plate_temp": [
"60"
],
"eng_plate_temp": [
"60"
],
"textured_plate_temp": [
"60"
],
"cool_plate_temp_initial_layer": [
"55"
],
"eng_plate_temp_initial_layer": [
"55"
],
"filament_end_gcode": [
"; filament end gcode \n"
],
"filament_start_gcode": [
"; filament start gcode\n"
]
}
@@ -1,21 +1,24 @@
{
"type": "filament",
"filament_id": "OFDSrzZ8",
"setting_id": "NNDerIjh7qtuh1I3",
"name": "Generic PLA @Sovol Zero",
"inherits": "Generic PLA @System",
"renamed_from": "Sovol Zero PLA Basic",
"from": "system",
"setting_id": "NNDerIjh7qtuh1I3",
"instantiation": "true",
"inherits": "fdm_filament_pla",
"filament_flow_ratio": [
"0.98"
],
"filament_max_volumetric_speed": [
"21"
"25"
],
"enable_pressure_advance": "1",
"pressure_advance": "0.032",
"compatible_printers": [
"Sovol Zero 0.4 nozzle"
"Sovol Zero 0.2 nozzle",
"Sovol Zero 0.4 nozzle",
"Sovol Zero 0.6 nozzle",
"Sovol Zero 0.8 nozzle",
"Sovol Zero 1.0 nozzle"
],
"nozzle_temperature_initial_layer": [
"230"
@@ -24,28 +27,28 @@
"210"
],
"nozzle_temperature_range_low": [
"190"
"180"
],
"nozzle_temperature_range_high": [
"250"
"240"
],
"hot_plate_temp": [
"65"
"60"
],
"hot_plate_temp_initial_layer": [
"65"
"60"
],
"fan_min_speed": [
"70"
"100"
],
"fan_max_speed": [
"100"
],
"fan_cooling_layer_time": [
"80"
"60"
],
"full_fan_speed_layer": [
"3"
"2"
],
"slow_down_layer_time": [
"5"
@@ -53,18 +56,43 @@
"slow_down_min_speed": [
"10"
],
"dont_slow_down_outer_wall": "0",
"overhang_fan_speed": [
"100"
],
"overhang_fan_threshold": [
"50%"
"10%"
],
"temperature_vitrification": [
"50"
],
"filament_z_hop": [
"0.4"
],
"filament_retraction_length": [
"0.4"
],
"filament_diameter": [
"1.75"
],
"cool_plate_temp": [
"60"
],
"additional_cooling_fan_speed": "75%",
"activate_air_filtration": "1",
"during_print_exhaust_fan_speed": "80%",
"complete_print_exhaust_fan_speed": "80%"
"eng_plate_temp": [
"60"
],
"textured_plate_temp": [
"60"
],
"cool_plate_temp_initial_layer": [
"55"
],
"eng_plate_temp_initial_layer": [
"55"
],
"filament_end_gcode": [
"; filament end gcode \n"
],
"filament_start_gcode": [
"; filament start gcode\n"
]
}
@@ -0,0 +1,118 @@
{
"type": "filament",
"filament_id": "OFIUkWYN",
"setting_id": "MC2InZ7iKDjIGike",
"name": "Generic PLA SPEEDBENCHY @Sovol Zero",
"from": "system",
"instantiation": "true",
"inherits": "fdm_filament_pla",
"filament_flow_ratio": [
"1"
],
"filament_max_volumetric_speed": [
"50"
],
"compatible_printers": [
"Sovol Zero 0.4 nozzle",
"Sovol Zero 0.4 Hardened Steel nozzle"
],
"nozzle_temperature_initial_layer": [
"245"
],
"nozzle_temperature": [
"225"
],
"nozzle_temperature_range_low": [
"190"
],
"nozzle_temperature_range_high": [
"250"
],
"hot_plate_temp": [
"65"
],
"hot_plate_temp_initial_layer": [
"65"
],
"enable_pressure_advance": [
"1"
],
"pressure_advance": [
"0.032"
],
"fan_min_speed": [
"100"
],
"fan_max_speed": [
"100"
],
"fan_cooling_layer_time": [
"30"
],
"full_fan_speed_layer": [
"3"
],
"slow_down_layer_time": [
"1.7"
],
"slow_down_min_speed": [
"40"
],
"overhang_fan_speed": [
"100"
],
"overhang_fan_threshold": [
"75%"
],
"additional_cooling_fan_speed": [
"100"
],
"filament_z_hop": [
"0"
],
"filament_retraction_length": [
"0.35"
],
"filament_retraction_speed": [
"90"
],
"filament_deretraction_speed": [
"90"
],
"filament_retract_before_wipe": [
"0%"
],
"filament_wipe_distance": [
"2"
],
"filament_retract_when_changing_layer": [
"0"
],
"filament_diameter": [
"1.75"
],
"cool_plate_temp": [
"60"
],
"eng_plate_temp": [
"60"
],
"textured_plate_temp": [
"60"
],
"cool_plate_temp_initial_layer": [
"55"
],
"eng_plate_temp_initial_layer": [
"55"
],
"filament_end_gcode": [
"; filament end gcode \n"
],
"filament_start_gcode": [
"; filament start gcode\n"
],
"temperature_vitrification": [
"100"
]
}
@@ -0,0 +1,110 @@
{
"type": "filament",
"filament_id": "OFesA6rF",
"setting_id": "LA8X1RsMdV2xNT1o",
"name": "Generic PLA Silk @Sovol Zero Hardened Steel nozzle",
"renamed_from": "Sovol Zero PLA Silk HS Nozzle",
"from": "system",
"instantiation": "true",
"inherits": "fdm_filament_pla",
"filament_flow_ratio": [
"0.98"
],
"filament_max_volumetric_speed": [
"15"
],
"enable_pressure_advance": [
"1"
],
"pressure_advance": [
"0.027"
],
"compatible_printers": [
"Sovol Zero 0.2 Hardened Steel nozzle",
"Sovol Zero 0.4 Hardened Steel nozzle",
"Sovol Zero 0.6 Hardened Steel nozzle",
"Sovol Zero 0.8 Hardened Steel nozzle",
"Sovol Zero 1.0 Hardened Steel nozzle"
],
"nozzle_temperature_initial_layer": [
"245"
],
"nozzle_temperature": [
"230"
],
"nozzle_temperature_range_low": [
"190"
],
"nozzle_temperature_range_high": [
"250"
],
"hot_plate_temp": [
"65"
],
"hot_plate_temp_initial_layer": [
"65"
],
"fan_min_speed": [
"70"
],
"fan_max_speed": [
"100"
],
"fan_cooling_layer_time": [
"80"
],
"full_fan_speed_layer": [
"3"
],
"slow_down_layer_time": [
"5"
],
"slow_down_min_speed": [
"10"
],
"dont_slow_down_outer_wall": [
"0"
],
"overhang_fan_speed": [
"100"
],
"overhang_fan_threshold": [
"50%"
],
"temperature_vitrification": [
"60"
],
"additional_cooling_fan_speed": [
"75%"
],
"activate_air_filtration": [
"1"
],
"during_print_exhaust_fan_speed": [
"80%"
],
"complete_print_exhaust_fan_speed": [
"80%"
],
"cool_plate_temp": [
"60"
],
"eng_plate_temp": [
"60"
],
"textured_plate_temp": [
"60"
],
"cool_plate_temp_initial_layer": [
"55"
],
"eng_plate_temp_initial_layer": [
"55"
],
"filament_end_gcode": [
"; filament end gcode \n"
],
"filament_start_gcode": [
"; filament start gcode\n"
]
}
@@ -1,27 +1,30 @@
{
"type": "filament",
"filament_id": "OFesA6rF",
"setting_id": "Se2yKerHRStYtWf5",
"name": "Generic PLA Silk @Sovol Zero",
"inherits": "Generic PLA Silk @System",
"renamed_from": "Sovol Zero PLA Silk",
"from": "system",
"setting_id": "Se2yKerHRStYtWf5",
"instantiation": "true",
"filament_multitool_ramming_flow": [
"20"
],
"filament_retraction_length": [
"nil"
],
"inherits": "fdm_filament_pla",
"filament_flow_ratio": [
"0.98"
],
"filament_max_volumetric_speed": [
"15"
],
"enable_pressure_advance": "1",
"pressure_advance": "0.029",
"enable_pressure_advance": [
"1"
],
"pressure_advance": [
"0.029"
],
"compatible_printers": [
"Sovol Zero 0.4 nozzle"
"Sovol Zero 0.2 nozzle",
"Sovol Zero 0.4 nozzle",
"Sovol Zero 0.6 nozzle",
"Sovol Zero 0.8 nozzle",
"Sovol Zero 1.0 nozzle"
],
"nozzle_temperature_initial_layer": [
"245"
@@ -59,7 +62,9 @@
"slow_down_min_speed": [
"10"
],
"dont_slow_down_outer_wall": "0",
"dont_slow_down_outer_wall": [
"0"
],
"overhang_fan_speed": [
"100"
],
@@ -69,8 +74,43 @@
"temperature_vitrification": [
"60"
],
"additional_cooling_fan_speed": "75%",
"activate_air_filtration": "1",
"during_print_exhaust_fan_speed": "80%",
"complete_print_exhaust_fan_speed": "80%"
"additional_cooling_fan_speed": [
"75%"
],
"activate_air_filtration": [
"1"
],
"during_print_exhaust_fan_speed": [
"80%"
],
"complete_print_exhaust_fan_speed": [
"80%"
],
"filament_multitool_ramming_flow": [
"20"
],
"filament_retraction_length": [
"nil"
],
"cool_plate_temp": [
"60"
],
"eng_plate_temp": [
"60"
],
"textured_plate_temp": [
"60"
],
"cool_plate_temp_initial_layer": [
"55"
],
"eng_plate_temp_initial_layer": [
"55"
],
"filament_end_gcode": [
"; filament end gcode \n"
],
"filament_start_gcode": [
"; filament start gcode\n"
]
}
@@ -1,11 +1,12 @@
{
"type": "filament",
"filament_id": "OFgbpcy9",
"setting_id": "MlY6WQ41FE8zDPSV",
"name": "Generic TPU @Sovol Zero",
"inherits": "Generic TPU @System",
"renamed_from": "Sovol Zero TPU",
"from": "system",
"setting_id": "MlY6WQ41FE8zDPSV",
"instantiation": "true",
"inherits": "fdm_filament_tpu",
"filament_flow_ratio": [
"0.98"
],
@@ -60,11 +61,55 @@
"filament_z_hop": [
"0.4"
],
"additional_cooling_fan_speed": "50",
"activate_air_filtration": "1",
"during_print_exhaust_fan_speed": "100",
"complete_print_exhaust_fan_speed": "50",
"additional_cooling_fan_speed": [
"50"
],
"activate_air_filtration": [
"1"
],
"during_print_exhaust_fan_speed": [
"100"
],
"complete_print_exhaust_fan_speed": [
"50"
],
"compatible_printers": [
"Sovol Zero 0.4 nozzle"
"Sovol Zero 0.2 nozzle",
"Sovol Zero 0.4 nozzle",
"Sovol Zero 0.6 nozzle",
"Sovol Zero 0.8 nozzle",
"Sovol Zero 1.0 nozzle",
"Sovol Zero 0.2 Hardened Steel nozzle",
"Sovol Zero 0.4 Hardened Steel nozzle",
"Sovol Zero 0.6 Hardened Steel nozzle",
"Sovol Zero 0.8 Hardened Steel nozzle",
"Sovol Zero 1.0 Hardened Steel nozzle"
],
"cool_plate_temp": [
"60"
],
"eng_plate_temp": [
"60"
],
"textured_plate_temp": [
"60"
],
"cool_plate_temp_initial_layer": [
"55"
],
"eng_plate_temp_initial_layer": [
"55"
],
"textured_plate_temp_initial_layer": [
"55"
],
"filament_end_gcode": [
"; filament end gcode \n"
],
"filament_retraction_length": [
"nil"
],
"filament_start_gcode": [
"; filament start gcode\n"
]
}
@@ -1,77 +0,0 @@
{
"type": "filament",
"name": "Sovol Zero PETG HS Nozzle",
"inherits": "Generic PETG @System",
"from": "system",
"setting_id": "AfxHmpdOkMDJa2fk",
"filament_id": "OFymnal9",
"instantiation": "true",
"filament_flow_ratio": [
"0.98"
],
"enable_pressure_advance": "1",
"pressure_advance": "0.048",
"filament_max_volumetric_speed": [
"15"
],
"nozzle_temperature_initial_layer": [
"265"
],
"nozzle_temperature": [
"250"
],
"nozzle_temperature_range_low": [
"230"
],
"nozzle_temperature_range_high": [
"280"
],
"hot_plate_temp": [
"85"
],
"hot_plate_temp_initial_layer": [
"85"
],
"fan_min_speed": [
"10"
],
"fan_max_speed": [
"40"
],
"fan_cooling_layer_time": [
"50"
],
"full_fan_speed_layer": [
"3"
],
"slow_down_layer_time": [
"10"
],
"slow_down_min_speed": [
"10"
],
"overhang_fan_speed": [
"70"
],
"overhang_fan_threshold": [
"10%"
],
"temperature_vitrification": [
"60"
],
"close_fan_the_first_x_layers": [
"1"
],
"filament_retraction_length": [
"0.5"
],
"filament_z_hop": [
"0.4"
],
"activate_air_filtration": "1",
"during_print_exhaust_fan_speed": "50",
"complete_print_exhaust_fan_speed": "50",
"compatible_printers": [
"Sovol Zero 0.4 nozzle"
]
}
@@ -1,70 +0,0 @@
{
"type": "filament",
"name": "Sovol Zero PLA Basic HS Nozzle",
"inherits": "Generic PLA @System",
"from": "system",
"setting_id": "i9wDnSEIrOBPbOZl",
"filament_id": "OFzB4uOs",
"instantiation": "true",
"filament_flow_ratio": [
"1.0348"
],
"filament_max_volumetric_speed": [
"21"
],
"enable_pressure_advance": "1",
"pressure_advance": "0.03",
"compatible_printers": [
"Sovol Zero 0.4 nozzle"
],
"nozzle_temperature_initial_layer": [
"245"
],
"nozzle_temperature": [
"230"
],
"nozzle_temperature_range_low": [
"190"
],
"nozzle_temperature_range_high": [
"250"
],
"hot_plate_temp": [
"65"
],
"hot_plate_temp_initial_layer": [
"65"
],
"fan_min_speed": [
"70"
],
"fan_max_speed": [
"100"
],
"fan_cooling_layer_time": [
"80"
],
"full_fan_speed_layer": [
"3"
],
"slow_down_layer_time": [
"5"
],
"slow_down_min_speed": [
"10"
],
"dont_slow_down_outer_wall": "0",
"overhang_fan_speed": [
"100"
],
"overhang_fan_threshold": [
"50%"
],
"temperature_vitrification": [
"60"
],
"additional_cooling_fan_speed": "75%",
"activate_air_filtration": "1",
"during_print_exhaust_fan_speed": "80%",
"complete_print_exhaust_fan_speed": "80%"
}
@@ -1,70 +0,0 @@
{
"type": "filament",
"name": "Sovol Zero PLA Silk HS Nozzle",
"inherits": "Generic PLA @System",
"from": "system",
"setting_id": "XxEJBNFLRltR2Xwn",
"filament_id": "OFAO9A7J",
"instantiation": "true",
"filament_flow_ratio": [
"0.98"
],
"filament_max_volumetric_speed": [
"15"
],
"enable_pressure_advance": "1",
"pressure_advance": "0.027",
"compatible_printers": [
"Sovol Zero 0.4 nozzle"
],
"nozzle_temperature_initial_layer": [
"245"
],
"nozzle_temperature": [
"230"
],
"nozzle_temperature_range_low": [
"190"
],
"nozzle_temperature_range_high": [
"250"
],
"hot_plate_temp": [
"65"
],
"hot_plate_temp_initial_layer": [
"65"
],
"fan_min_speed": [
"70"
],
"fan_max_speed": [
"100"
],
"fan_cooling_layer_time": [
"80"
],
"full_fan_speed_layer": [
"3"
],
"slow_down_layer_time": [
"5"
],
"slow_down_min_speed": [
"10"
],
"dont_slow_down_outer_wall": "0",
"overhang_fan_speed": [
"100"
],
"overhang_fan_threshold": [
"50%"
],
"temperature_vitrification": [
"60"
],
"additional_cooling_fan_speed": "75%",
"activate_air_filtration": "1",
"during_print_exhaust_fan_speed": "80%",
"complete_print_exhaust_fan_speed": "80%"
}
@@ -0,0 +1,19 @@
{
"type": "machine",
"name": "Sovol Zero 0.2 Hardened Steel nozzle",
"inherits": "Sovol Zero 0.2 nozzle",
"from": "system",
"setting_id": "OkvEInEyzUUJzw9K",
"instantiation": "true",
"printer_model": "Sovol Zero",
"printer_variant": "0.2HS",
"nozzle_diameter": [
"0.2"
],
"default_filament_profile": [
"Generic PLA @Sovol Zero Hardened Steel nozzle"
],
"nozzle_type": [
"hardened_steel"
]
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,19 @@
{
"type": "machine",
"name": "Sovol Zero 0.4 Hardened Steel nozzle",
"inherits": "Sovol Zero 0.4 nozzle",
"from": "system",
"setting_id": "RqwHKS8MYRdoeJPi",
"instantiation": "true",
"printer_model": "Sovol Zero",
"printer_variant": "0.4HS",
"nozzle_diameter": [
"0.4"
],
"default_filament_profile": [
"Generic PLA @Sovol Zero Hardened Steel nozzle"
],
"nozzle_type": [
"hardened_steel"
]
}
@@ -1,18 +1,24 @@
{
"type": "machine",
"name": "Sovol Zero 0.4 nozzle",
"inherits": "fdm_machine_common",
"from": "system",
"setting_id": "QclEa2nmpBiii6Y0",
"name": "Sovol Zero 0.4 nozzle",
"from": "system",
"instantiation": "true",
"inherits": "fdm_machine_common",
"printer_model": "Sovol Zero",
"default_print_profile": "0.20mm Standard @Sovol Zero 0.4 nozzle",
"printer_variant": "0.4",
"nozzle_diameter": [
"0.4"
],
"min_layer_height": [
"0.08"
],
"max_layer_height": [
"0.32"
],
"retract_before_wipe": [
"100%"
"0%"
],
"printable_area": [
"0x0",
@@ -23,7 +29,7 @@
"printable_height": "152.4",
"gcode_flavor": "klipper",
"retraction_length": [
"0.8"
"1.8"
],
"machine_max_speed_e": [
"50"
@@ -71,7 +77,7 @@
"0.5"
],
"z_hop": [
"0.4"
"0.6"
],
"retraction_speed": [
"40"
@@ -89,7 +95,7 @@
"1"
],
"wipe_distance": [
"2"
"3"
],
"z_hop_types": [
"Auto Lift"
@@ -0,0 +1,19 @@
{
"type": "machine",
"name": "Sovol Zero 0.6 Hardened Steel nozzle",
"inherits": "Sovol Zero 0.6 nozzle",
"from": "system",
"setting_id": "s3N3FJkWrzOylFm8",
"instantiation": "true",
"printer_model": "Sovol Zero",
"printer_variant": "0.6HS",
"nozzle_diameter": [
"0.6"
],
"default_filament_profile": [
"Generic PLA @Sovol Zero Hardened Steel nozzle"
],
"nozzle_type": [
"hardened_steel"
]
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,19 @@
{
"type": "machine",
"name": "Sovol Zero 0.8 Hardened Steel nozzle",
"inherits": "Sovol Zero 0.8 nozzle",
"from": "system",
"setting_id": "houacf8sQ3bLozqD",
"instantiation": "true",
"printer_model": "Sovol Zero",
"printer_variant": "0.8HS",
"nozzle_diameter": [
"0.8"
],
"default_filament_profile": [
"Generic PLA @Sovol Zero Hardened Steel nozzle"
],
"nozzle_type": [
"hardened_steel"
]
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,19 @@
{
"type": "machine",
"name": "Sovol Zero 1.0 Hardened Steel nozzle",
"inherits": "Sovol Zero 1.0 nozzle",
"from": "system",
"setting_id": "sqlEiL0in8LSjmmn",
"instantiation": "true",
"printer_model": "Sovol Zero",
"printer_variant": "1.0HS",
"nozzle_diameter": [
"1.0"
],
"default_filament_profile": [
"Generic PLA @Sovol Zero Hardened Steel nozzle"
],
"nozzle_type": [
"hardened_steel"
]
}
File diff suppressed because one or more lines are too long
@@ -2,11 +2,11 @@
"type": "machine_model",
"name": "Sovol Zero",
"model_id": "Sovol-Zero",
"nozzle_diameter": "0.4",
"nozzle_diameter": "0.2;0.4;0.6;0.8;1.0;0.2HS;0.4HS;0.6HS;0.8HS;1.0HS",
"machine_tech": "FFF",
"family": "Sovol",
"bed_model": "sovol_zero_buildplate_model.stl",
"bed_texture": "sovol_zero_buildplate_texture.svg",
"hotend_model": "",
"default_materials": "Generic PLA @Sovol Zero;Sovol Zero PLA Basic HS Nozzle;Generic PLA Silk @Sovol Zero;Sovol Zero PLA Silk HS Nozzle;Generic ABS @Sovol Zero;Generic PETG @Sovol Zero;Sovol Zero PETG HS Nozzle;Generic TPU @Sovol Zero;Generic PC @Sovol Zero"
"default_materials": "Generic PLA @Sovol Zero;Generic PLA SPEEDBENCHY @Sovol Zero;Generic PLA @Sovol Zero Hardened Steel nozzle;Generic PLA Silk @Sovol Zero;Generic PLA Silk @Sovol Zero Hardened Steel nozzle;Generic ABS @Sovol Zero;Generic PETG @Sovol Zero;Generic PETG @Sovol Zero Hardened Steel nozzle;Generic TPU @Sovol Zero;Generic PC @Sovol Zero"
}
@@ -0,0 +1,36 @@
{
"type": "process",
"name": "0.06mm Quality @Sovol Zero 0.2 nozzle",
"inherits": "fdm_process_zero",
"from": "system",
"setting_id": "fWP7jaL7sJmmZ6QN",
"instantiation": "true",
"layer_height": "0.06",
"bottom_shell_layers": "8",
"bottom_shell_thickness": "0.8",
"bridge_speed": "30",
"outer_wall_acceleration": "10000",
"top_surface_acceleration": "5000",
"outer_wall_line_width": "0.21",
"line_width": "0.21",
"sparse_infill_density": "15%",
"inner_wall_acceleration": "12000",
"initial_layer_print_height": "0.06",
"initial_layer_line_width": "0.3",
"infill_wall_overlap": "18",
"inner_wall_line_width": "0.22",
"wall_loops": "3",
"internal_solid_infill_line_width": "0.21",
"sparse_infill_line_width": "0.21",
"support_top_z_distance": "0.10",
"support_line_width": "0.21",
"tree_support_tip_diameter": "0.21",
"support_speed": "100",
"top_surface_line_width": "0.21",
"top_shell_layers": "8",
"top_shell_thickness": "1",
"compatible_printers": [
"Sovol Zero 0.2 nozzle",
"Sovol Zero 0.2 Hardened Steel nozzle"
]
}
@@ -0,0 +1,36 @@
{
"type": "process",
"name": "0.10mm Standard @Sovol Zero 0.2 nozzle",
"inherits": "fdm_process_zero",
"from": "system",
"setting_id": "Gql3487jsaTXyuYZ",
"instantiation": "true",
"layer_height": "0.10",
"bottom_shell_layers": "4",
"bottom_shell_thickness": "0.8",
"bridge_speed": "50",
"outer_wall_acceleration": "10000",
"top_surface_acceleration": "5000",
"outer_wall_line_width": "0.22",
"line_width": "0.22",
"sparse_infill_density": "15%",
"inner_wall_acceleration": "12000",
"initial_layer_print_height": "0.12",
"initial_layer_line_width": "0.3",
"infill_wall_overlap": "20",
"inner_wall_line_width": "0.22",
"wall_loops": "2",
"internal_solid_infill_line_width": "0.22",
"sparse_infill_line_width": "0.22",
"support_top_z_distance": "0.20",
"support_line_width": "0.22",
"tree_support_tip_diameter": "0.22",
"support_speed": "100",
"top_surface_line_width": "0.22",
"top_shell_layers": "4",
"top_shell_thickness": "1",
"compatible_printers": [
"Sovol Zero 0.2 nozzle",
"Sovol Zero 0.2 Hardened Steel nozzle"
]
}
@@ -0,0 +1,35 @@
{
"type": "process",
"setting_id": "CaEopiv2Wg6pQJjH",
"name": "0.12mm Quality @Sovol Zero 0.4 nozzle",
"from": "system",
"inherits": "fdm_process_zero",
"instantiation": "true",
"layer_height": "0.12",
"bottom_shell_layers": "8",
"bottom_shell_thickness": "0.8",
"bridge_speed": "30",
"outer_wall_acceleration": "10000",
"top_surface_acceleration": "5000",
"outer_wall_line_width": "0.42",
"line_width": "0.42",
"sparse_infill_density": "15%",
"inner_wall_acceleration": "12000",
"initial_layer_print_height": "0.12",
"initial_layer_line_width": "0.6",
"sparse_infill_line_width": "0.42",
"infill_wall_overlap": "18",
"inner_wall_line_width": "0.44",
"wall_loops": "3",
"support_top_z_distance": "0.10",
"support_line_width": "0.42",
"tree_support_tip_diameter": "0.42",
"support_speed": "100",
"top_surface_line_width": "0.42",
"internal_solid_infill_line_width": "0.42",
"top_shell_layers": "8",
"compatible_printers": [
"Sovol Zero 0.4 nozzle",
"Sovol Zero 0.4 Hardened Steel nozzle"
]
}
@@ -0,0 +1,36 @@
{
"type": "process",
"name": "0.14mm Fast @Sovol Zero 0.2 nozzle",
"inherits": "fdm_process_zero",
"from": "system",
"setting_id": "I9yoyCcGGTRrDoKu",
"instantiation": "true",
"layer_height": "0.14",
"bottom_shell_layers": "3",
"bottom_shell_thickness": "0",
"bridge_speed": "60",
"outer_wall_acceleration": "22000",
"top_surface_acceleration": "22000",
"outer_wall_line_width": "0.24",
"line_width": "0.24",
"sparse_infill_density": "10%",
"inner_wall_acceleration": "28000",
"initial_layer_print_height": "0.14",
"initial_layer_line_width": "0.3",
"infill_wall_overlap": "20",
"inner_wall_line_width": "0.24",
"wall_loops": "2",
"internal_solid_infill_line_width": "0.24",
"sparse_infill_line_width": "0.24",
"support_top_z_distance": "0.30",
"support_line_width": "0.24",
"tree_support_tip_diameter": "0.24",
"support_speed": "120",
"top_surface_line_width": "0.24",
"top_shell_layers": "4",
"top_shell_thickness": "1",
"compatible_printers": [
"Sovol Zero 0.2 nozzle",
"Sovol Zero 0.2 Hardened Steel nozzle"
]
}
@@ -0,0 +1,34 @@
{
"type": "process",
"setting_id": "IfDpa3vM99D4ytny",
"name": "0.15mm Quality @Sovol Zero 0.6 nozzle",
"from": "system",
"inherits": "fdm_process_zero",
"instantiation": "true",
"layer_height": "0.15",
"bottom_shell_layers": "5",
"bottom_shell_thickness": "0.8",
"bridge_speed": "30",
"outer_wall_acceleration": "10000",
"top_surface_acceleration": "5000",
"outer_wall_line_width": "0.63",
"line_width": "0.63",
"sparse_infill_density": "15%",
"inner_wall_acceleration": "12000",
"initial_layer_line_width": "0.9",
"sparse_infill_line_width": "0.63",
"infill_wall_overlap": "18",
"inner_wall_line_width": "0.66",
"wall_loops": "3",
"support_top_z_distance": "0.15",
"support_line_width": "0.63",
"tree_support_tip_diameter": "0.63",
"support_speed": "100",
"top_surface_line_width": "0.63",
"top_shell_layers": "5",
"internal_solid_infill_line_width": "0.63",
"compatible_printers": [
"Sovol Zero 0.6 nozzle",
"Sovol Zero 0.6 Hardened Steel nozzle"
]
}
@@ -0,0 +1,42 @@
{
"type": "process",
"name": "0.20mm Quality @Sovol Zero 0.8 nozzle",
"inherits": "fdm_process_zero",
"from": "system",
"setting_id": "MSxikpQZNYdoBynZ",
"instantiation": "true",
"layer_height": "0.20",
"bottom_shell_layers": "4",
"bottom_shell_thickness": "0.8",
"bridge_speed": "30",
"outer_wall_acceleration": "10000",
"top_surface_acceleration": "5000",
"outer_wall_line_width": "0.84",
"line_width": "0.84",
"sparse_infill_density": "15%",
"inner_wall_acceleration": "12000",
"initial_layer_print_height": "0.36",
"initial_layer_line_width": "1.2",
"sparse_infill_line_width": "0.84",
"infill_wall_overlap": "18",
"inner_wall_line_width": "0.88",
"wall_loops": "3",
"support_top_z_distance": "0.20",
"support_line_width": "0.84",
"tree_support_tip_diameter": "0.84",
"support_speed": "100",
"top_surface_line_width": "0.84",
"top_shell_layers": "4",
"outer_wall_speed": "350",
"inner_wall_speed": "400",
"small_perimeter_speed": "50%",
"internal_solid_infill_speed": "200",
"internal_solid_infill_line_width": "0.84",
"top_surface_speed": "200",
"gap_infill_speed": "200",
"sparse_infill_speed": "500",
"compatible_printers": [
"Sovol Zero 0.8 nozzle",
"Sovol Zero 0.8 Hardened Steel nozzle"
]
}
@@ -1,141 +1,36 @@
{
"type": "process",
"name": "0.20mm Standard @Sovol Zero 0.4 nozzle",
"inherits": "fdm_process_common",
"from": "system",
"setting_id": "ShJTVnJ492No1j2j",
"name": "0.20mm Standard @Sovol Zero 0.4 nozzle",
"from": "system",
"inherits": "fdm_process_zero",
"instantiation": "true",
"reduce_crossing_wall": "1",
"max_travel_detour_distance": "100%",
"layer_height": "0.20",
"bottom_surface_pattern": "monotonic",
"bottom_shell_layers": "3",
"bottom_shell_thickness": "0",
"bridge_flow": "1.2",
"internal_bridge_flow": "1.2",
"bottom_shell_layers": "4",
"bottom_shell_thickness": "0.8",
"bridge_speed": "50",
"internal_bridge_speed": "200",
"brim_type": "auto_brim",
"brim_width": "5",
"brim_object_gap": "0",
"compatible_printers_condition": "",
"print_sequence": "by layer",
"default_acceleration": "40000",
"outer_wall_acceleration": "10000",
"top_surface_acceleration": "5000",
"bridge_no_support": "0",
"draft_shield": "disabled",
"elefant_foot_compensation": "0.1",
"enable_arc_fitting": "0",
"exclude_object": "1",
"outer_wall_line_width": "0.4",
"wall_infill_order": "inner wall/outer wall/infill",
"line_width": "0.4",
"infill_direction": "45",
"sparse_infill_density": "10%",
"sparse_infill_pattern": "grid",
"internal_solid_infill_acceleration": "50%",
"initial_layer_acceleration": "1000",
"initial_solid_infill_acceleration": "3000",
"travel_acceleration": "40000",
"outer_wall_line_width": "0.44",
"line_width": "0.44",
"sparse_infill_density": "15%",
"inner_wall_acceleration": "12000",
"outer_wall_jerk": "5",
"inner_wall_jerk": "5",
"infill_jerk": "5",
"top_surface_jerk": "5",
"initial_layer_jerk": "5",
"travel_jerk": "5",
"initial_layer_line_width": "0.42",
"initial_layer_print_height": "0.25",
"infill_combination": "0",
"sparse_infill_line_width": "0.45",
"infill_wall_overlap": "15%",
"interface_shells": "0",
"ironing_flow": "15%",
"ironing_spacing": "0.25",
"ironing_speed": "15",
"ironing_type": "no ironing",
"reduce_infill_retraction": "1",
"filename_format": "{printer_model}_{input_filename_base}_{filament_type[0]}_{layer_height}_{print_time}.gcode",
"detect_overhang_wall": "1",
"overhang_1_4_speed": "0",
"overhang_2_4_speed": "50",
"overhang_3_4_speed": "30",
"overhang_4_4_speed": "20",
"inner_wall_line_width": "0.4",
"initial_layer_print_height": "0.24",
"initial_layer_line_width": "0.6",
"sparse_infill_line_width": "0.44",
"infill_wall_overlap": "20",
"ironing_speed": "100",
"inner_wall_line_width": "0.44",
"wall_loops": "2",
"print_settings_id": "",
"raft_layers": "0",
"seam_position": "aligned",
"skirt_distance": "0.8",
"skirt_height": "1",
"skirt_loops": "1",
"minimum_sparse_infill_area": "15",
"internal_solid_infill_line_width": "0.45",
"spiral_mode": "0",
"standby_temperature_delta": "-5",
"enable_support": "0",
"resolution": "0.012",
"support_type": "tree(auto)",
"support_style": "default",
"support_on_build_plate_only": "0",
"support_top_z_distance": "0.235",
"support_bottom_z_distance": "0.235",
"support_filament": "0",
"support_line_width": "0.45",
"support_interface_loop_pattern": "0",
"support_interface_filament": "0",
"support_interface_top_layers": "5",
"support_interface_bottom_layers": "-1",
"support_interface_spacing": "0.2",
"support_bottom_interface_spacing": "0.2",
"support_interface_speed": "100%",
"support_interface_pattern": "grid",
"support_base_pattern": "rectilinear",
"support_base_pattern_spacing": "3.5",
"support_speed": "100",
"support_threshold_angle": "20",
"support_object_xy_distance": "0.35",
"tree_support_branch_angle": "40",
"tree_support_wall_count": "0",
"detect_thin_wall": "1",
"top_surface_pattern": "monotonic",
"top_surface_line_width": "0.38",
"support_top_z_distance": "0.20",
"support_line_width": "0.44",
"tree_support_tip_diameter": "0.44",
"support_speed": "60",
"top_surface_line_width": "0.44",
"top_shell_layers": "4",
"top_shell_thickness": "1",
"initial_layer_speed": "55",
"initial_layer_infill_speed": "105",
"initial_layer_travel_speed": "60%",
"outer_wall_speed": "350",
"inner_wall_speed": "400",
"small_perimeter_speed": "50%",
"internal_solid_infill_speed": "200",
"top_surface_speed": "200",
"gap_infill_speed": "200",
"sparse_infill_speed": "500",
"accel_to_decel_enable": "1",
"accel_to_decel_factor": "25%",
"travel_speed": "1000",
"enable_prime_tower": "1",
"wipe_tower_no_sparse_layers": "0",
"prime_tower_width": "60",
"xy_hole_compensation": "0",
"xy_contour_compensation": "0",
"bridge_acceleration": "50%",
"seam_gap": "10%",
"precise_outer_wall": "1",
"wall_generator": "classic",
"gcode_label_objects": "1",
"slow_down_layers": "3",
"top_solid_infill_flow_ratio": "0.9",
"only_one_wall_top": "1",
"wall_loop_direction": "clockwise",
"top_bottom_infill_wall_overlap": "25%",
"filter_out_gap_fill": "0",
"detect_narrow_internal_solid_infill": "1",
"thick_bridges": "1",
"bridge_angle": "0",
"internal_solid_infill_line_width": "0.44",
"compatible_printers": [
"Sovol Zero 0.4 nozzle"
"Sovol Zero 0.4 nozzle",
"Sovol Zero 0.4 Hardened Steel nozzle"
]
}
@@ -0,0 +1,79 @@
{
"type": "process",
"name": "0.25mm Quality @Sovol Zero 1.0 nozzle",
"inherits": "fdm_process_zero",
"from": "system",
"setting_id": "JUmnFOoEMJrbWCN9",
"instantiation": "true",
"layer_height": "0.25",
"bottom_shell_layers": "4",
"bottom_shell_thickness": "0.8",
"bridge_speed": "30",
"internal_bridge_speed": "200",
"default_acceleration": "40000",
"outer_wall_acceleration": "10000",
"top_surface_acceleration": "5000",
"outer_wall_line_width": "1.05",
"line_width": "1.05",
"sparse_infill_density": "15%",
"inner_wall_acceleration": "12000",
"initial_layer_print_height": "0.45",
"initial_layer_line_width": "1.5",
"infill_combination": "0",
"sparse_infill_line_width": "1.05",
"infill_wall_overlap": "18",
"interface_shells": "0",
"ironing_flow": "15%",
"ironing_spacing": "0.25",
"ironing_speed": "15",
"ironing_type": "no ironing",
"filename_format": "{input_filename_base}_{nozzle_diameter[0]}n_{filament_type[0]}_{layer_height}_{print_time}.gcode",
"detect_overhang_wall": "1",
"overhang_1_4_speed": "0",
"overhang_2_4_speed": "50",
"overhang_3_4_speed": "30",
"overhang_4_4_speed": "20",
"inner_wall_line_width": "1.1",
"wall_loops": "3",
"print_settings_id": "",
"raft_layers": "0",
"seam_position": "aligned",
"skirt_distance": "0.8",
"skirt_height": "1",
"skirt_loops": "1",
"minimum_sparse_infill_area": "15",
"internal_solid_infill_line_width": "1.05",
"spiral_mode": "0",
"standby_temperature_delta": "-5",
"enable_support": "0",
"resolution": "0.012",
"support_type": "tree(auto)",
"support_style": "snug",
"support_on_build_plate_only": "0",
"support_top_z_distance": "0.25",
"support_bottom_z_distance": "0.235",
"support_filament": "0",
"support_line_width": "1.05",
"tree_support_tip_diameter": "1.05",
"support_interface_loop_pattern": "0",
"support_interface_filament": "0",
"support_interface_top_layers": "5",
"support_interface_bottom_layers": "-1",
"support_interface_spacing": "0.2",
"support_bottom_interface_spacing": "0.2",
"support_interface_speed": "100%",
"support_interface_pattern": "grid",
"support_base_pattern": "rectilinear",
"support_base_pattern_spacing": "3.5",
"support_speed": "100",
"support_threshold_angle": "50",
"support_object_xy_distance": "0.35",
"tree_support_branch_angle": "40",
"top_surface_line_width": "1.05",
"top_shell_layers": "4",
"top_shell_thickness": "1",
"compatible_printers": [
"Sovol Zero 1.0 nozzle",
"Sovol Zero 1.0 Hardened Steel nozzle"
]
}
@@ -0,0 +1,85 @@
{
"type": "process",
"setting_id": "oFrWcSxmad1b6sD4",
"name": "0.25mm SPEEDBENCHY @Sovol Zero 0.4 nozzle",
"from": "system",
"inherits": "fdm_process_zero",
"instantiation": "true",
"layer_height": "0.25",
"initial_layer_print_height": "0.25",
"line_width": "0.5",
"initial_layer_line_width": "0.5",
"outer_wall_line_width": "0.5",
"inner_wall_line_width": "0.5",
"top_surface_line_width": "0.55",
"internal_solid_infill_line_width": "0.55",
"sparse_infill_line_width": "0.55",
"support_line_width": "0.5",
"tree_support_tip_diameter": "0.5",
"wall_loops": "2",
"top_shell_layers": "3",
"bottom_shell_layers": "3",
"bottom_shell_thickness": "0",
"sparse_infill_density": "10%",
"sparse_infill_pattern": "grid",
"bottom_solid_infill_flow_ratio": "1.2",
"top_solid_infill_flow_ratio": "1.4",
"bridge_flow": "1",
"internal_bridge_flow": "1",
"bridge_speed": "250",
"internal_bridge_speed": "250",
"bridge_acceleration": "100%",
"thick_bridges": "0",
"thick_internal_bridges": "0",
"brim_type": "outer_only",
"brim_width": "1",
"brim_object_gap": "0",
"filter_out_gap_fill": "13",
"gap_fill_target": "nowhere",
"min_width_top_surface": "200%",
"minimum_sparse_infill_area": "0",
"infill_direction": "0",
"solid_infill_direction": "0",
"infill_wall_overlap": "15%",
"reduce_crossing_wall": "0",
"skirt_loops": "0",
"only_one_wall_top": "1",
"slow_down_layers": "1",
"wall_direction": "cw",
"initial_layer_speed": "300",
"initial_layer_infill_speed": "300",
"initial_layer_travel_speed": "100%",
"outer_wall_speed": "350",
"inner_wall_speed": "400",
"small_perimeter_speed": "100%",
"internal_solid_infill_speed": "400",
"top_surface_speed": "300",
"gap_infill_speed": "350",
"sparse_infill_speed": "500",
"support_speed": "100",
"travel_speed": "1200",
"default_acceleration": "40000",
"initial_layer_acceleration": "20000",
"outer_wall_acceleration": "40000",
"inner_wall_acceleration": "40000",
"top_surface_acceleration": "20000",
"internal_solid_infill_acceleration": "100%",
"travel_acceleration": "40000",
"accel_to_decel_factor": "50%",
"outer_wall_jerk": "20",
"inner_wall_jerk": "20",
"infill_jerk": "20",
"top_surface_jerk": "20",
"initial_layer_jerk": "20",
"travel_jerk": "20",
"overhang_1_4_speed": "160",
"overhang_2_4_speed": "100",
"overhang_3_4_speed": "60",
"overhang_4_4_speed": "40",
"support_top_z_distance": "0.235",
"support_bottom_z_distance": "0.235",
"compatible_printers": [
"Sovol Zero 0.4 nozzle",
"Sovol Zero 0.4 Hardened Steel nozzle"
]
}
@@ -0,0 +1,39 @@
{
"type": "process",
"name": "0.28mm Fast @Sovol Zero 0.4 nozzle",
"inherits": "fdm_process_zero",
"from": "system",
"setting_id": "1VoML2LGDk4TJgLI",
"instantiation": "true",
"layer_height": "0.28",
"bottom_shell_layers": "3",
"bottom_shell_thickness": "0",
"bridge_speed": "60",
"outer_wall_acceleration": "22000",
"top_surface_acceleration": "22000",
"outer_wall_line_width": "0.48",
"line_width": "0.48",
"sparse_infill_density": "10%",
"inner_wall_acceleration": "28000",
"initial_layer_print_height": "0.28",
"initial_layer_line_width": "0.6",
"sparse_infill_line_width": "0.48",
"inner_wall_line_width": "0.48",
"internal_solid_infill_line_width": "0.48",
"support_top_z_distance": "0.30",
"support_line_width": "0.48",
"tree_support_tip_diameter": "0.48",
"support_speed": "120",
"top_surface_line_width": "0.48",
"outer_wall_speed": "350",
"inner_wall_speed": "400",
"small_perimeter_speed": "50%",
"internal_solid_infill_speed": "200",
"top_surface_speed": "200",
"gap_infill_speed": "200",
"sparse_infill_speed": "500",
"compatible_printers": [
"Sovol Zero 0.4 nozzle",
"Sovol Zero 0.4 Hardened Steel nozzle"
]
}
@@ -0,0 +1,35 @@
{
"type": "process",
"setting_id": "lTAs2USEP8D749K7",
"name": "0.30mm Standard @Sovol Zero 0.6 nozzle",
"from": "system",
"inherits": "fdm_process_zero",
"instantiation": "true",
"layer_height": "0.30",
"bottom_shell_layers": "4",
"bottom_shell_thickness": "0.8",
"bridge_speed": "50",
"outer_wall_acceleration": "10000",
"top_surface_acceleration": "5000",
"outer_wall_line_width": "0.66",
"line_width": "0.66",
"sparse_infill_density": "15%",
"inner_wall_acceleration": "12000",
"initial_layer_print_height": "0.36",
"initial_layer_line_width": "0.9",
"sparse_infill_line_width": "0.66",
"infill_wall_overlap": "20",
"inner_wall_line_width": "0.66",
"wall_loops": "2",
"support_top_z_distance": "0.30",
"support_line_width": "0.66",
"tree_support_tip_diameter": "0.66",
"support_speed": "100",
"top_surface_line_width": "0.66",
"top_shell_layers": "4",
"internal_solid_infill_line_width": "0.66",
"compatible_printers": [
"Sovol Zero 0.6 nozzle",
"Sovol Zero 0.6 Hardened Steel nozzle"
]
}
@@ -0,0 +1,42 @@
{
"type": "process",
"name": "0.40mm Standard @Sovol Zero 0.8 nozzle",
"inherits": "fdm_process_zero",
"from": "system",
"setting_id": "8x4CsuqroC9HOdAN",
"instantiation": "true",
"layer_height": "0.40",
"bottom_shell_layers": "4",
"bottom_shell_thickness": "0.8",
"bridge_speed": "50",
"outer_wall_acceleration": "10000",
"top_surface_acceleration": "5000",
"outer_wall_line_width": "0.88",
"line_width": "0.88",
"sparse_infill_density": "15%",
"inner_wall_acceleration": "12000",
"initial_layer_print_height": "0.48",
"initial_layer_line_width": "1.2",
"sparse_infill_line_width": "0.88",
"infill_wall_overlap": "20",
"inner_wall_line_width": "0.88",
"wall_loops": "2",
"support_top_z_distance": "0.40",
"support_line_width": "0.88",
"tree_support_tip_diameter": "0.88",
"support_speed": "100",
"top_surface_line_width": "0.88",
"top_shell_layers": "4",
"outer_wall_speed": "350",
"inner_wall_speed": "400",
"small_perimeter_speed": "50%",
"internal_solid_infill_speed": "200",
"top_surface_speed": "200",
"gap_infill_speed": "200",
"sparse_infill_speed": "500",
"internal_solid_infill_line_width": "0.88",
"compatible_printers": [
"Sovol Zero 0.8 nozzle",
"Sovol Zero 0.8 Hardened Steel nozzle"
]
}
@@ -0,0 +1,39 @@
{
"type": "process",
"name": "0.42mm Fast @Sovol Zero 0.6 nozzle",
"inherits": "fdm_process_zero",
"from": "system",
"setting_id": "illqJAnGiA2pY0hw",
"instantiation": "true",
"layer_height": "0.42",
"bottom_shell_layers": "3",
"bottom_shell_thickness": "0",
"bridge_speed": "60",
"outer_wall_acceleration": "22000",
"top_surface_acceleration": "22000",
"outer_wall_line_width": "0.72",
"line_width": "0.72",
"sparse_infill_density": "10%",
"inner_wall_acceleration": "28000",
"initial_layer_print_height": "0.42",
"initial_layer_line_width": "0.9",
"sparse_infill_line_width": "0.72",
"inner_wall_line_width": "0.72",
"internal_solid_infill_line_width": "0.72",
"support_top_z_distance": "0.45",
"support_line_width": "0.72",
"tree_support_tip_diameter": "0.72",
"support_speed": "120",
"top_surface_line_width": "0.72",
"outer_wall_speed": "350",
"inner_wall_speed": "400",
"small_perimeter_speed": "50%",
"internal_solid_infill_speed": "200",
"top_surface_speed": "200",
"gap_infill_speed": "200",
"sparse_infill_speed": "500",
"compatible_printers": [
"Sovol Zero 0.6 nozzle",
"Sovol Zero 0.6 Hardened Steel nozzle"
]
}
@@ -0,0 +1,87 @@
{
"type": "process",
"name": "0.50mm Standard @Sovol Zero 1.0 nozzle",
"inherits": "fdm_process_zero",
"from": "system",
"setting_id": "E5K55O4RkMyYCAlt",
"instantiation": "true",
"layer_height": "0.50",
"bottom_shell_layers": "4",
"bottom_shell_thickness": "0.8",
"bridge_speed": "45",
"internal_bridge_speed": "200",
"default_acceleration": "22000",
"outer_wall_acceleration": "16000",
"top_surface_acceleration": "16000",
"outer_wall_line_width": "1.1",
"line_width": "1.1",
"sparse_infill_density": "15%",
"inner_wall_acceleration": "22000",
"initial_layer_print_height": "0.60",
"initial_layer_line_width": "1.5",
"infill_combination": "0",
"sparse_infill_line_width": "1.1",
"infill_wall_overlap": "20",
"interface_shells": "0",
"ironing_flow": "15%",
"ironing_spacing": "0.25",
"ironing_speed": "15",
"ironing_type": "no ironing",
"filename_format": "{input_filename_base}_{nozzle_diameter[0]}n_{filament_type[0]}_{layer_height}_{print_time}.gcode",
"detect_overhang_wall": "1",
"overhang_1_4_speed": "0",
"overhang_2_4_speed": "50",
"overhang_3_4_speed": "30",
"overhang_4_4_speed": "20",
"inner_wall_line_width": "1.1",
"wall_loops": "2",
"print_settings_id": "",
"raft_layers": "0",
"seam_position": "aligned",
"skirt_distance": "0.8",
"skirt_height": "1",
"skirt_loops": "1",
"minimum_sparse_infill_area": "15",
"internal_solid_infill_line_width": "1.1",
"spiral_mode": "0",
"standby_temperature_delta": "-5",
"enable_support": "0",
"resolution": "0.012",
"support_type": "tree(auto)",
"support_style": "snug",
"support_on_build_plate_only": "0",
"support_top_z_distance": "0.50",
"support_bottom_z_distance": "0.235",
"support_filament": "0",
"support_line_width": "1.1",
"tree_support_tip_diameter": "1.1",
"support_interface_loop_pattern": "0",
"support_interface_filament": "0",
"support_interface_top_layers": "5",
"support_interface_bottom_layers": "-1",
"support_interface_spacing": "0.2",
"support_bottom_interface_spacing": "0.2",
"support_interface_speed": "100%",
"support_interface_pattern": "grid",
"support_base_pattern": "rectilinear",
"support_base_pattern_spacing": "3.5",
"support_speed": "60",
"support_threshold_angle": "50",
"support_object_xy_distance": "0.35",
"tree_support_branch_angle": "40",
"top_surface_line_width": "1.1",
"top_shell_layers": "4",
"top_shell_thickness": "1",
"outer_wall_speed": "30",
"inner_wall_speed": "45",
"small_perimeter_speed": "45",
"internal_solid_infill_speed": "45",
"top_surface_speed": "35",
"gap_infill_speed": "35",
"sparse_infill_speed": "70",
"travel_acceleration": "38000",
"compatible_printers": [
"Sovol Zero 1.0 nozzle",
"Sovol Zero 1.0 Hardened Steel nozzle"
]
}
@@ -0,0 +1,40 @@
{
"type": "process",
"name": "0.56mm Fast @Sovol Zero 0.8 nozzle",
"inherits": "fdm_process_zero",
"from": "system",
"setting_id": "g9PPRM4FLMwjMUfy",
"instantiation": "true",
"layer_height": "0.56",
"bottom_shell_layers": "3",
"bottom_shell_thickness": "0",
"bridge_speed": "60",
"default_acceleration": "28000",
"outer_wall_acceleration": "22000",
"top_surface_acceleration": "22000",
"outer_wall_line_width": "0.96",
"line_width": "0.96",
"sparse_infill_density": "10%",
"inner_wall_acceleration": "28000",
"initial_layer_print_height": "0.56",
"initial_layer_line_width": "1.2",
"sparse_infill_line_width": "0.96",
"inner_wall_line_width": "0.96",
"internal_solid_infill_line_width": "0.96",
"support_top_z_distance": "0.60",
"support_line_width": "0.96",
"tree_support_tip_diameter": "0.96",
"top_surface_line_width": "0.96",
"top_shell_layers": "4",
"outer_wall_speed": "35",
"inner_wall_speed": "55",
"small_perimeter_speed": "60",
"internal_solid_infill_speed": "55",
"top_surface_speed": "40",
"gap_infill_speed": "40",
"sparse_infill_speed": "80",
"compatible_printers": [
"Sovol Zero 0.8 nozzle",
"Sovol Zero 0.8 Hardened Steel nozzle"
]
}
@@ -0,0 +1,33 @@
{
"type": "process",
"name": "0.70mm Fast @Sovol Zero 1.0 nozzle",
"inherits": "fdm_process_zero",
"from": "system",
"setting_id": "fW5KJBVndgQE868q",
"instantiation": "true",
"layer_height": "0.70",
"bottom_shell_layers": "3",
"bottom_shell_thickness": "0",
"bridge_speed": "60",
"outer_wall_acceleration": "22000",
"top_surface_acceleration": "22000",
"outer_wall_line_width": "1.2",
"line_width": "1.2",
"sparse_infill_density": "10%",
"inner_wall_acceleration": "28000",
"initial_layer_print_height": "0.70",
"initial_layer_line_width": "1.5",
"sparse_infill_line_width": "1.2",
"inner_wall_line_width": "1.2",
"internal_solid_infill_line_width": "1.2",
"support_top_z_distance": "0.75",
"support_line_width": "1.2",
"tree_support_tip_diameter": "1.2",
"support_speed": "120",
"top_surface_line_width": "1.2",
"top_shell_layers": "3",
"compatible_printers": [
"Sovol Zero 1.0 nozzle",
"Sovol Zero 1.0 Hardened Steel nozzle"
]
}
@@ -0,0 +1,73 @@
{
"type": "process",
"name": "fdm_process_zero",
"inherits": "fdm_process_common",
"from": "system",
"instantiation": "false",
"reduce_crossing_wall": "1",
"max_travel_detour_distance": "10",
"bottom_surface_pattern": "monotonic",
"bridge_flow": "1.2",
"internal_bridge_flow": "1.2",
"brim_type": "auto_brim",
"brim_width": "5",
"brim_object_gap": "0",
"print_sequence": "by layer",
"bridge_no_support": "0",
"draft_shield": "disabled",
"elefant_foot_compensation": "0.1",
"enable_arc_fitting": "0",
"exclude_object": "1",
"wall_infill_order": "inner wall/outer wall/infill",
"infill_direction": "45",
"sparse_infill_pattern": "grid",
"internal_solid_infill_acceleration": "50%",
"initial_layer_acceleration": "1000",
"travel_acceleration": "40000",
"outer_wall_jerk": "5",
"inner_wall_jerk": "5",
"infill_jerk": "5",
"top_surface_jerk": "5",
"initial_layer_jerk": "5",
"travel_jerk": "5",
"tree_support_wall_count": "0",
"reduce_infill_retraction": "1",
"detect_thin_wall": "1",
"top_surface_pattern": "monotonic",
"initial_layer_speed": "55",
"initial_layer_infill_speed": "105",
"initial_layer_travel_speed": "60%",
"outer_wall_speed": "350",
"inner_wall_speed": "400",
"small_perimeter_speed": "50%",
"internal_solid_infill_speed": "200",
"top_surface_speed": "200",
"gap_infill_speed": "200",
"sparse_infill_speed": "500",
"accel_to_decel_enable": "1",
"accel_to_decel_factor": "25%",
"travel_speed": "1000",
"enable_prime_tower": "0",
"wipe_tower_no_sparse_layers": "0",
"prime_tower_width": "60",
"xy_hole_compensation": "0",
"xy_contour_compensation": "0",
"bridge_acceleration": "50%",
"seam_gap": "10%",
"precise_outer_wall": "1",
"wall_generator": "classic",
"gcode_label_objects": "1",
"slow_down_layers": "3",
"top_solid_infill_flow_ratio": "0.9",
"only_one_wall_top": "1",
"top_bottom_infill_wall_overlap": "25%",
"filter_out_gap_fill": "0",
"detect_narrow_internal_solid_infill": "1",
"thick_bridges": "1",
"bridge_angle": "0",
"initial_layer_line_width": "150%",
"default_acceleration": "40000",
"compatible_printers": [],
"internal_bridge_speed": "150",
"filename_format": "{input_filename_base}_{nozzle_diameter[0]}n_{filament_type[0]}_{layer_height}_{print_time}.gcode"
}
+45
View File
@@ -405,6 +405,20 @@ class TestTripleResolution(unittest.TestCase):
self.assertEqual(afi.resolve_triple("MyPLA @P1", fmap, {}),
("MyVendor", "PLA", "MyPLA"))
def test_split_vendor_and_type_bases_resolve(self):
# A partial base is normal, not an error: vendor and type may live on
# different ancestors, with an intermediate supplying neither (the
# Snapmaker shape). The pair is complete at the instantiated preset.
recs = [
self.rec("APLA @P1", inherits="mid"),
self.rec("mid", inherits="typebase"),
self.rec("typebase", filament_type=["PLA"], inherits="vendorbase"),
self.rec("vendorbase", filament_vendor=["AV"]),
]
fmap = {r["name"]: r for r in recs}
self.assertEqual(afi.resolve_triple("APLA @P1", fmap, {}),
("AV", "PLA", "APLA"))
# ---------------------------------------------------------------------------
# checks on synthetic trees
@@ -417,6 +431,24 @@ class TestChecks(OfCleanTreeCase):
self.assertNotIn("[ERROR]", out)
self.assertNotIn("[WARNING]", out)
def test_instantiated_preset_over_partial_bases_is_silent(self):
# Vendor and type split across two non-instantiated bases, an
# intermediate base with neither: base profiles are allowed to be
# partial. Only the instantiated preset must resolve both.
self.t.write_preset("VendorA", preset("XPLA vendorbase", instantiation=False,
filament_vendor="XV"))
self.t.write_preset("VendorA", preset("XPLA typebase", instantiation=False,
filament_type="PLA",
inherits="XPLA vendorbase"))
self.t.write_preset("VendorA", preset("XPLA mid", instantiation=False,
inherits="XPLA typebase"))
self.t.write_preset("VendorA", preset(
"XPLA @P1", inherits="XPLA mid",
filament_id=afi.generate_filament_id("XV", "PLA", "XPLA"),
compatible_printers=["P1"]))
errors, out = self.t.check()
self.assertEqual(errors, 0, out)
def test_check1_unknown_non_of_id(self):
self.t.write_preset("VendorA", preset("BPLA @base", filament_id="BOGUS_9",
instantiation=False,
@@ -1523,6 +1555,19 @@ class TestRealTree(unittest.TestCase):
self.assertEqual(analysis["missing_effective"], [])
self.assertEqual(analysis["read_errors"], [])
def test_every_instantiated_filament_resolves_vendor_and_type(self):
# The property the web guide resolves at load: a partial base is fine as
# long as the instantiated preset ends up with both fields. Guards the
# split-base bundles (Snapmaker, Anker, SeeMeCNC).
analysis = afi.analyze_tree(REAL_PROFILES)
unresolved = [
(vendor, rec["name"], rec["triple"][0], rec["triple"][1])
for vendor, filaments in analysis["vendors"].items()
for rec in filaments.values()
if rec["instantiation"] and not (rec["triple"][0] and rec["triple"][1])
]
self.assertEqual(unresolved, [])
# ---------------------------------------------------------------------------
# review-fix regressions
# ---------------------------------------------------------------------------
+8
View File
@@ -286,6 +286,8 @@ set(SLIC3R_GUI_SOURCES
GUI/FilamentMapDialog.hpp
GUI/IconManager.cpp
GUI/IconManager.hpp
GUI/IdleScheduler.cpp
GUI/IdleScheduler.hpp
GUI/ImageGrid.cpp
GUI/ImageGrid.h
GUI/ImGuiWrapper.cpp
@@ -347,6 +349,10 @@ set(SLIC3R_GUI_SOURCES
GUI/KBShortcutsDialog.hpp
GUI/KeyChord.cpp
GUI/KeyChord.hpp
GUI/Lazy.cpp
GUI/Lazy.hpp
GUI/LazyPage.cpp
GUI/LazyPage.hpp
GUI/LibVGCode/LibVGCodeWrapper.hpp
GUI/LibVGCode/LibVGCodeWrapper.cpp
GUI/LinuxDisplayBackend.cpp
@@ -433,6 +439,7 @@ set(SLIC3R_GUI_SOURCES
GUI/Plater.hpp
GUI/PlateSettingsDialog.cpp
GUI/PlateSettingsDialog.hpp
GUI/PrebuildQueue.hpp
GUI/Preferences.cpp
GUI/Preferences.hpp
GUI/PresetBundleDialog.cpp
@@ -512,6 +519,7 @@ set(SLIC3R_GUI_SOURCES
GUI/SliceInfoPanel.hpp
GUI/SlicingProgressNotification.cpp
GUI/SlicingProgressNotification.hpp
GUI/StagedBuild.hpp
GUI/StatusPanel.cpp
GUI/StatusPanel.hpp
GUI/StepMeshDialog.cpp
+15 -5
View File
@@ -863,17 +863,27 @@ void AuxiliaryPanel::init_tabpanel()
m_tabpanel->SetBackgroundColour(wxColour("#FEFFFF"));
m_tabpanel->Bind(wxEVT_BOOKCTRL_PAGE_CHANGED, [](wxBookCtrlEvent &e) { /* Event handling */ });
add_build_step([this] {
m_designer_panel = new DesignerPanel(m_tabpanel, AuxiliaryFolderType::DESIGNER);
m_pictures_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::MODEL_PICTURE);
m_bill_of_materials_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::BILL_OF_MATERIALS);
m_assembly_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::ASSEMBLY_GUIDE);
m_others_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::OTHERS);
m_tabpanel->AddPage(m_designer_panel, _L("Basic Info"), true);
});
add_build_step([this] {
m_pictures_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::MODEL_PICTURE);
m_tabpanel->AddPage(m_pictures_panel, _L("Pictures"), false);
});
add_build_step([this] {
m_bill_of_materials_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::BILL_OF_MATERIALS);
m_tabpanel->AddPage(m_bill_of_materials_panel, _L("Bill of Materials"), false);
});
add_build_step([this] {
m_assembly_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::ASSEMBLY_GUIDE);
m_tabpanel->AddPage(m_assembly_panel, _L("Assembly Guide"), false);
});
add_build_step([this] {
m_others_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::OTHERS);
m_tabpanel->AddPage(m_others_panel, _L("Others"), false);
Layout();
});
}
wxWindow *AuxiliaryPanel::create_side_tools()
+2 -1
View File
@@ -46,6 +46,7 @@
#include "slic3r/GUI/UpgradePanel.hpp"
#include "slic3r/GUI/AmsWidgets.hpp"
#include "Widgets/SideTools.hpp"
#include "StagedBuild.hpp"
#define AUFILE_GREY700 wxColour(107, 107, 107)
#define AUFILE_GREY500 wxColour(158, 158, 158)
@@ -194,7 +195,7 @@ public:
};
class AuxiliaryPanel : public wxPanel
class AuxiliaryPanel : public wxPanel, public StagedBuild
{
private:
Tabbook *m_tabpanel = {nullptr};
+2 -1
View File
@@ -12,6 +12,7 @@
#include "libslic3r/CAD/CadDocument.hpp"
#include "slic3r/GUI/CAD/DesignInteraction.hpp" // CadLevel: what one Esc press means
#include "slic3r/GUI/Lazy.hpp"
class ComboBox; // Orca dropdown (Widgets/ComboBox.hpp) — replaces wxChoice everywhere here
class StaticBox; // Orca rounded card frame (Widgets/StaticBox.hpp)
@@ -46,7 +47,7 @@ class DesignCanvas;
// Design (CAD) tab: a sketch-first, Onshape-style form-driven CAD panel.
// Sketch and Extrude are independent tools: the user creates a Sketch first,
// then selects it and Extrudes to produce a solid.
class DesignPanel : public wxPanel
class DesignPanel : public wxPanel, public LazyInstance<DesignPanel>
{
public:
explicit DesignPanel(wxWindow* parent);
+1 -2
View File
@@ -1989,11 +1989,10 @@ json action_set_feature_expr(DesignPanel* panel, const json& params)
// Dispatch one parsed request ON THE MAIN THREAD. Returns a JSON-RPC reply string.
std::string handle_on_main(const std::string& method, const json& params, const json& id)
{
MainFrame* mf = wxGetApp().mainframe;
// The panel is built on first use, and in a headless session nobody clicks the tab that
// would build it -- so build it here rather than refusing. Safe: this runs on the main
// thread (see the CallAfter that dispatches us).
DesignPanel* panel = mf ? mf->ensure_design_panel() : nullptr;
DesignPanel* panel = DesignPanel::ensure();
if (!panel)
return rpc_error(id, -32001, "Design panel not ready");
+6 -3
View File
@@ -491,14 +491,15 @@ void CalibrationPanel::init_tabpanel() {
selected);
}
for (int i = 0; i < (int)CALI_MODE_COUNT; i++)
add_build_steps_of(*m_cali_panels[i]);
// ORCA use standard paddings and keep arrow icon for consistent look between sidebars
//for (int i = 0; i < (int)CALI_MODE_COUNT; i++)
// m_tabpanel->SetPageImage(i, "");
//auto padding_size = m_tabpanel->GetBtnsListCtrl()->GetPaddingSize(0);
//m_tabpanel->GetBtnsListCtrl()->SetPaddingSize({ FromDIP(15), padding_size.y });
m_initialized = true;
}
void CalibrationPanel::init_timer()
@@ -534,6 +535,8 @@ void CalibrationPanel::update_print_error_info(int code, std::string msg, std::s
}
void CalibrationPanel::update_all() {
// Every wizard's pages exist once the last build step has run.
if (!built()) return;
NetworkAgent* m_agent = wxGetApp().getAgent();
Slic3r::DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
@@ -597,7 +600,7 @@ void CalibrationPanel::update_all() {
void CalibrationPanel::show_status(int status)
{
if (!m_initialized) return;
if (!built()) return;
if (last_status == status)return;
last_status = status;
+2 -2
View File
@@ -3,6 +3,7 @@
#include "CalibrationWizard.hpp"
#include "Tabbook.hpp"
#include "Lazy.hpp"
//#include "Widgets/SideTools.hpp"
namespace Slic3r { namespace GUI {
@@ -88,7 +89,7 @@ private:
};
class CalibrationPanel : public wxPanel
class CalibrationPanel : public wxPanel, public StagedBuild, public LazyInstance<CalibrationPanel>
{
public:
CalibrationPanel(wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxTAB_TRAVERSAL);
@@ -109,7 +110,6 @@ protected:
int last_status;
bool m_initialized { false };
std::string last_conn_type = "undedefined";
MachineObject* obj{ nullptr };
MachineObject* last_obj { nullptr };
+25 -34
View File
@@ -130,6 +130,15 @@ CalibrationWizard::~CalibrationWizard()
;
}
void CalibrationWizard::add_page_step(CalibrationWizardPageStep*& step, std::function<CalibrationWizardPage*()> make)
{
add_build_step([this, &step, make = std::move(make)] {
step = new CalibrationWizardPageStep(make());
m_all_pages_sizer->Add(step->page, 1, wxEXPAND | wxALL, FromDIP(25));
step->page->Hide();
});
}
void CalibrationWizard::on_cali_job_finished(wxCommandEvent& event)
{
this->on_cali_job_finished(event.GetString());
@@ -520,17 +529,12 @@ void PressureAdvanceWizard::on_cali_job_finished(wxString evt_data)
void PressureAdvanceWizard::create_pages()
{
start_step = new CalibrationWizardPageStep(new CalibrationPAStartPage(m_scrolledWindow));
preset_step = new CalibrationWizardPageStep(new CalibrationPresetPage(m_scrolledWindow, m_mode, false));
cali_step = new CalibrationWizardPageStep(new CalibrationCaliPage(m_scrolledWindow, m_mode));
save_step = new CalibrationWizardPageStep(new CalibrationPASavePage(m_scrolledWindow));
m_all_pages_sizer->Add(start_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
m_all_pages_sizer->Add(preset_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
m_all_pages_sizer->Add(cali_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
m_all_pages_sizer->Add(save_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
add_page_step(start_step, [this] { return new CalibrationPAStartPage(m_scrolledWindow); });
add_page_step(preset_step, [this] { return new CalibrationPresetPage(m_scrolledWindow, m_mode, false); });
add_page_step(cali_step, [this] { return new CalibrationCaliPage(m_scrolledWindow, m_mode); });
add_page_step(save_step, [this] { return new CalibrationPASavePage(m_scrolledWindow); });
add_build_step([this] {
m_page_steps.push_back(start_step);
m_page_steps.push_back(preset_step);
m_page_steps.push_back(cali_step);
@@ -541,12 +545,12 @@ void PressureAdvanceWizard::create_pages()
}
for (int i = 0; i < m_page_steps.size(); i++) {
m_page_steps[i]->page->Hide();
m_page_steps[i]->page->Bind(EVT_CALI_ACTION, &PressureAdvanceWizard::on_cali_action, this);
}
if (!m_page_steps.empty())
show_step(m_page_steps.front());
});
}
void PressureAdvanceWizard::on_cali_action(wxCommandEvent& evt)
@@ -1053,29 +1057,20 @@ FlowRateWizard::FlowRateWizard(wxWindow* parent, wxWindowID id, const wxPoint& p
void FlowRateWizard::create_pages()
{
start_step = new CalibrationWizardPageStep(new CalibrationFlowRateStartPage(m_scrolledWindow));
preset_step = new CalibrationWizardPageStep(new CalibrationPresetPage(m_scrolledWindow, m_mode, false));
add_page_step(start_step, [this] { return new CalibrationFlowRateStartPage(m_scrolledWindow); });
add_page_step(preset_step, [this] { return new CalibrationPresetPage(m_scrolledWindow, m_mode, false); });
// manual
cali_coarse_step = new CalibrationWizardPageStep(new CalibrationCaliPage(m_scrolledWindow, m_mode, CaliPageType::CALI_PAGE_CALI));
coarse_save_step = new CalibrationWizardPageStep(new CalibrationFlowCoarseSavePage(m_scrolledWindow));
cali_fine_step = new CalibrationWizardPageStep(new CalibrationCaliPage(m_scrolledWindow, m_mode, CaliPageType::CALI_PAGE_FINE_CALI));
fine_save_step = new CalibrationWizardPageStep(new CalibrationFlowFineSavePage(m_scrolledWindow));
add_page_step(cali_coarse_step, [this] { return new CalibrationCaliPage(m_scrolledWindow, m_mode, CaliPageType::CALI_PAGE_CALI); });
add_page_step(coarse_save_step, [this] { return new CalibrationFlowCoarseSavePage(m_scrolledWindow); });
add_page_step(cali_fine_step, [this] { return new CalibrationCaliPage(m_scrolledWindow, m_mode, CaliPageType::CALI_PAGE_FINE_CALI); });
add_page_step(fine_save_step, [this] { return new CalibrationFlowFineSavePage(m_scrolledWindow); });
// auto
cali_step = new CalibrationWizardPageStep(new CalibrationCaliPage(m_scrolledWindow, m_mode));
save_step = new CalibrationWizardPageStep(new CalibrationFlowX1SavePage(m_scrolledWindow));
m_all_pages_sizer->Add(start_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
m_all_pages_sizer->Add(preset_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
m_all_pages_sizer->Add(cali_coarse_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
m_all_pages_sizer->Add(coarse_save_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
m_all_pages_sizer->Add(cali_fine_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
m_all_pages_sizer->Add(fine_save_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
m_all_pages_sizer->Add(cali_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
m_all_pages_sizer->Add(save_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
add_page_step(cali_step, [this] { return new CalibrationCaliPage(m_scrolledWindow, m_mode); });
add_page_step(save_step, [this] { return new CalibrationFlowX1SavePage(m_scrolledWindow); });
add_build_step([this] {
m_page_steps.push_back(start_step);
m_page_steps.push_back(preset_step);
m_page_steps.push_back(cali_coarse_step);
@@ -1090,15 +1085,10 @@ void FlowRateWizard::create_pages()
m_page_steps[i]->chain(m_page_steps[i + 1]);
}
// hide all pages
cali_step->page->Hide();
save_step->page->Hide();
for (int i = 0; i < m_page_steps.size(); i++) {
m_page_steps[i]->page->Hide();
m_page_steps[i]->page->Bind(EVT_CALI_ACTION, &FlowRateWizard::on_cali_action, this);
}
cali_step->page->Bind(EVT_CALI_ACTION, &FlowRateWizard::on_cali_action, this);
save_step->page->Bind(EVT_CALI_ACTION, &FlowRateWizard::on_cali_action, this);
@@ -1106,6 +1096,7 @@ void FlowRateWizard::create_pages()
show_step(m_page_steps.front());
set_cali_method(CalibrationMethod::CALI_METHOD_MANUAL);
});
}
void FlowRateWizard::on_cali_action(wxCommandEvent& evt)
+5 -1
View File
@@ -9,6 +9,7 @@
#include "CalibrationWizardPresetPage.hpp"
#include "CalibrationWizardCaliPage.hpp"
#include "CalibrationWizardSavePage.hpp"
#include "StagedBuild.hpp"
namespace Slic3r { namespace GUI {
@@ -36,7 +37,7 @@ struct ConfigIndexValue
int index{0};
};
class CalibrationWizard : public wxPanel {
class CalibrationWizard : public wxPanel, public StagedBuild {
public:
CalibrationWizard(wxWindow* parent, CalibMode mode,
wxWindowID id = wxID_ANY,
@@ -79,6 +80,9 @@ public:
protected:
void on_cali_go_home();
// Queues a page as a build step, created hidden and added to the pages sizer.
void add_page_step(CalibrationWizardPageStep*& step, std::function<CalibrationWizardPage*()> make);
protected:
/* wx widgets*/
wxScrolledWindow* m_scrolledWindow;
+3 -1
View File
@@ -192,8 +192,10 @@ void CalibrationCaliPage::update(MachineObject* obj)
set_cali_img();
}
// A calibration can run before the Device tab is ever opened, and only its status
// panel shows a print error.
if (obj->print_error > 0) {
StatusPanel* status_panel = Slic3r::GUI::wxGetApp().mainframe->m_monitor->get_status_panel();
StatusPanel* status_panel = MonitorPanel::ensure()->get_status_panel();
status_panel->obj = obj;
status_panel->update_error_message();
}
+6 -3
View File
@@ -538,7 +538,8 @@ void DeviceErrorDialog::on_button_click(ActionButton btn_id)
break;
}
case DeviceErrorDialog::CHECK_ASSISTANT: {
wxGetApp().mainframe->m_monitor->jump_to_HMS(); // go to assistant page
if (MonitorPanel* monitor = MonitorPanel::if_built())
monitor->jump_to_HMS(); // go to assistant page
break;
}
case DeviceErrorDialog::FILAMENT_EXTRUDED: {
@@ -568,7 +569,8 @@ void DeviceErrorDialog::on_button_click(ActionButton btn_id)
}
case DeviceErrorDialog::JUMP_TO_LIVEVIEW: {
Slic3r::GUI::wxGetApp().mainframe->jump_to_monitor();
Slic3r::GUI::wxGetApp().mainframe->m_monitor->jump_to_LiveView();
if (MonitorPanel* monitor = MonitorPanel::if_built())
monitor->jump_to_LiveView();
break;
}
case DeviceErrorDialog::NO_REMINDER_NEXT_TIME: {
@@ -618,7 +620,8 @@ void DeviceErrorDialog::on_button_click(ActionButton btn_id)
}
case DeviceErrorDialog::OK_JUMP_RACK: {
Slic3r::GUI::wxGetApp().mainframe->jump_to_monitor();
Slic3r::GUI::wxGetApp().mainframe->m_monitor->jump_to_Rack();
if (MonitorPanel* monitor = MonitorPanel::if_built())
monitor->jump_to_Rack();
break;
}
case DeviceErrorDialog::ABORT: {
@@ -934,7 +934,8 @@ void wgtDeviceNozzleRackNozzleItem::OnBtnNozzleStatus(wxMouseEvent& evt)
dlg.AddButton(wxID_OK,_L("Jump to the upgrade page"), true);
if (dlg.ShowModal() == wxID_OK) {
wxGetApp().mainframe->m_monitor->jump_to_Upgrade();
if (MonitorPanel* monitor = MonitorPanel::if_built())
monitor->jump_to_Upgrade();
};
}
}
@@ -344,7 +344,8 @@ void wgtDeviceNozzleRackHotendUpdate::OnStatusIconClick(wxMouseEvent& event)
if (dlg.ShowModal() == wxID_OK)
{
wxGetApp().mainframe->m_monitor->jump_to_Upgrade();
if (MonitorPanel* monitor = MonitorPanel::if_built())
monitor->jump_to_Upgrade();
wxCommandEvent evt(wxEVT_NOZZLE_JUMP_UPGRADE, GetId());
evt.SetEventObject(this);
+210 -51
View File
@@ -20,6 +20,7 @@
#include "libslic3r/AppConfig.hpp"
#include "3DScene.hpp"
#include "BackgroundSlicingProcess.hpp"
#include "CameraUtils.hpp"
#include "GLShader.hpp"
#include "GUI.hpp"
#include "Tab.hpp"
@@ -117,6 +118,36 @@ void GLCanvas3D::load_render_colors()
namespace Slic3r {
namespace GUI {
static void pan_camera(Camera& camera, const Vec2d& screen_delta, const Vec3d& anchor)
{
// Orca: Derive world-units-per-pixel from the projection which produced the visible frame.
// Perspective additionally scales with the eye-space depth of the point being dragged.
const auto& viewport = camera.get_viewport();
const auto& projection = camera.get_projection_matrix().matrix();
const double depth_scale = camera.get_type() == Camera::EType::Perspective ?
(anchor - camera.get_position()).dot(camera.get_dir_forward()) : 1.0;
const double projection_x = projection(0, 0) * viewport[2];
const double projection_y = projection(1, 1) * viewport[3];
// Orca: X/Y projection coefficients already include zoom. Using them directly avoids a
// project/unproject round-trip through window depth, whose precision depends on the scene frustum.
if (viewport[2] > 0 && viewport[3] > 0 && anchor.allFinite() && depth_scale > EPSILON &&
std::abs(projection_x) > EPSILON && std::abs(projection_y) > EPSILON) {
const Vec3d displacement = 2.0 * depth_scale *
(screen_delta.y() / projection_y * camera.get_dir_up() -
screen_delta.x() / projection_x * camera.get_dir_right());
if (displacement.allFinite()) {
camera.translate(displacement);
return;
}
}
// Orca: Preserve the former target-plane behavior if the projection or anchor is invalid.
// Screen Y grows downward, and the camera moves opposite to the drag.
camera.translate(camera.get_inv_zoom() *
(screen_delta.y() * camera.get_dir_up() - screen_delta.x() * camera.get_dir_right()));
}
#ifdef __WXGTK3__
// wxGTK3 seems to simulate OSX behavior in regard to HiDPI scaling support.
RetinaHelper::RetinaHelper(wxWindow* window) : m_window(window), m_self(nullptr) {}
@@ -2206,15 +2237,14 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size)
_render_background();
//BBS add partplater rendering logic
bool only_current = false, only_body = false, no_partplate = false;
bool only_current = false, only_body = false;
const bool show_bed = is_bed_visible();
bool show_grid = true;
GLGizmosManager::EType gizmo_type = m_gizmos.get_current_type();
if (!m_main_toolbar.is_enabled()) {
//only_body = true;
only_current = true;
}
else if ((gizmo_type == GLGizmosManager::FdmSupports) || (gizmo_type == GLGizmosManager::Seam) || (gizmo_type == GLGizmosManager::MmSegmentation) || (gizmo_type == GLGizmosManager::FuzzySkin))
no_partplate = true;
else if (gizmo_type == GLGizmosManager::BrimEars && !camera.is_looking_downward())
show_grid = false;
if (m_axes_at_bed_center)
@@ -2228,11 +2258,11 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size)
if (m_canvas_type == ECanvasType::CanvasView3D) {
// m_show_bed gates the plate list too: hiding the bed but leaving its grid and outline
// floating would read as a rendering fault rather than a deliberate view option.
if (!no_partplate && m_show_bed)
if (show_bed)
_render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), m_show_world_axes);
if (!no_partplate && m_show_bed) //BBS: add outline logic
if (show_bed) //BBS: add outline logic
_render_platelist(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), only_current, only_body, hover_id, true, show_grid);
if (m_axes_at_bed_center && m_show_bed && !no_partplate)
if (m_axes_at_bed_center && show_bed)
// Design tab: replace the plate's corner-origin grid with the origin-centred CAD grid.
_render_cad_grid(camera.get_view_matrix(), camera.get_projection_matrix());
@@ -3281,6 +3311,9 @@ void GLCanvas3D::unbind_event_handlers()
m_canvas->Unbind(wxEVT_GESTURE_PAN, &GLCanvas3D::on_gesture, this);
m_canvas->Unbind(wxEVT_GESTURE_ZOOM, &GLCanvas3D::on_gesture, this);
m_canvas->Unbind(wxEVT_GESTURE_ROTATE, &GLCanvas3D::on_gesture, this);
#if __WXOSX__
initGestures(m_canvas->GetHandle(), nullptr);
#endif
}
}
@@ -4029,27 +4062,38 @@ void GLCanvas3D::on_gesture(wxGestureEvent &evt)
auto & camera = wxGetApp().plater()->get_camera();
if (evt.GetEventType() == wxEVT_GESTURE_PAN) {
auto p = evt.GetPosition();
// Orca: Gesture coordinates must use framebuffer pixels, and one stable world-space
// anchor must be retained for the complete gesture to prevent perspective drift.
const auto p = evt.GetPosition();
auto d = static_cast<wxPanGestureEvent&>(evt).GetDelta();
float z = 0;
const Vec3d &p2 = _mouse_to_3d({p.x, p.y}, &z);
const Vec3d &p1 = _mouse_to_3d({p.x - d.x, p.y - d.y}, &z);
camera.set_target(camera.get_target() + p1 - p2);
Vec2d screen_position(p.x, p.y);
Vec2d screen_delta(d.x, d.y);
apply_retina_scale(screen_position);
apply_retina_scale(screen_delta);
if (evt.IsGestureStart() || !m_gesture_pan_anchor.has_value())
m_gesture_pan_anchor = get_camera_pan_anchor(camera, ECameraNavigationType::Gesture,
screen_position - screen_delta);
pan_camera(camera, screen_delta, *m_gesture_pan_anchor);
if (evt.IsGestureEnd())
m_gesture_pan_anchor.reset();
} else if (evt.GetEventType() == wxEVT_GESTURE_ZOOM) {
static float zoom_start = 1;
if (evt.IsGestureStart())
zoom_start = camera.get_zoom();
camera.set_zoom(zoom_start * static_cast<wxZoomGestureEvent&>(evt).GetZoomFactor());
} else if (evt.GetEventType() == wxEVT_GESTURE_ROTATE) {
PartPlate* plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
// Orca: Rotation starts a different navigation operation, so a previous pan anchor
// must not be reused; rotation and pan fallbacks share the same navigation pivot.
m_gesture_pan_anchor.reset();
bool rotate_limit = current_printer_technology() != ptSLA;
static double last_rotate = 0;
if (evt.IsGestureStart())
last_rotate = 0;
auto rotate = static_cast<wxRotateGestureEvent&>(evt).GetRotationAngle() - last_rotate;
last_rotate += rotate;
if (plate)
camera.rotate_on_sphere_with_target(-rotate, 0, rotate_limit, plate->get_bounding_box().center());
const std::optional<Vec3d> rotate_target = get_camera_orbit_target(ECameraNavigationType::Gesture);
if (rotate_target.has_value())
camera.rotate_on_sphere_with_target(-rotate, 0, rotate_limit, *rotate_target);
else
camera.rotate_on_sphere(-rotate, 0, rotate_limit);
camera.auto_type(Camera::EType::Perspective);
@@ -4339,6 +4383,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
post_event(SimpleEvent(EVT_GLCANVAS_SWITCH_TO_GLOBAL));
}
else if (evt.LeftDown() || evt.RightDown() || evt.MiddleDown()) {
// Orca: Retain the click position even if the first motion event crosses a surface edge.
m_mouse.set_start_position_2D_as_invalid();
m_mouse.drag.start_position_2D = pos;
//BBS: add orient deactivate logic
if (!m_gizmos.on_mouse(evt)) {
if (_deactivate_arrange_menu() || _deactivate_orient_menu())
@@ -4532,6 +4580,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
}
// do not process the dragging if the left mouse was set down in another canvas
else if (is_camera_rotate(evt, button_mappings)) {
// Orca: Rotation and panning use different drag coordinates and cached anchors.
// Clear the pan state before processing rotation or switching buttons mid-drag.
m_mouse.set_start_position_2D_as_invalid();
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
m_canvas->CaptureMouse();
@@ -4549,12 +4600,12 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
if (this->m_canvas_type == ECanvasType::CanvasAssembleView || m_gizmos.get_current_type() == GLGizmosManager::FdmSupports ||
m_gizmos.get_current_type() == GLGizmosManager::Seam || m_gizmos.get_current_type() == GLGizmosManager::MmSegmentation ||
m_gizmos.get_current_type() == GLGizmosManager::FuzzySkin) {
Vec3d rotate_target = Vec3d::Zero();
if (!m_selection.is_empty())
rotate_target = m_selection.get_bounding_box().center();
// Orca: Reuse the centralized pivot policy for scene-oriented tools.
const std::optional<Vec3d> rotate_target = get_camera_orbit_target(ECameraNavigationType::Mouse);
if (rotate_target.has_value())
camera.rotate_on_sphere_with_target(rot.x(), rot.y(), false, *rotate_target);
else
rotate_target = volumes_bounding_box().center();
camera.rotate_on_sphere_with_target(rot.x(), rot.y(), false, rotate_target);
camera.rotate_on_sphere(rot.x(), rot.y(), false);
}
else {
if (wxGetApp().app_config->get_bool("use_free_camera"))
@@ -4578,28 +4629,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
}
camera.rotate_on_sphere_with_target(rot.x(), rot.y(), rotate_limit, m_rotation_center);
} else {
Vec3d rotate_target = Vec3d::Zero();
if (m_canvas_type == ECanvasType::CanvasPreview) {
PartPlate *plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
if (plate)
rotate_target = plate->get_bounding_box().center();
}
else {
if (!m_selection.is_empty())
rotate_target = m_selection.get_bounding_box().center();
else {
// Rotate around the center of objects on current plate
auto bbox = volumes_bounding_box(true);
if (!bbox.defined) {
// Rotate around current plate center if current plate is empty
bbox = wxGetApp().plater()->get_partplate_list().get_curr_plate()->get_bounding_box();
}
rotate_target = bbox.center();
}
}
if (!rotate_target.isZero())
camera.rotate_on_sphere_with_target(rot.x(), rot.y(), rotate_limit, rotate_target);
// Orca: Keep regular mouse orbit and perspective-pan fallback centered
// on the same selection, active-plate, or scene reference.
const std::optional<Vec3d> rotate_target = get_camera_orbit_target(ECameraNavigationType::Mouse);
if (rotate_target.has_value())
camera.rotate_on_sphere_with_target(rot.x(), rot.y(), rotate_limit, *rotate_target);
else
camera.rotate_on_sphere(rot.x(), rot.y(), rotate_limit);
}
@@ -4615,16 +4649,14 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
m_mouse.drag.start_position_3D = Vec3d((double)pos(0), (double)pos(1), 0.0);
}
else if (is_camera_pan(evt, button_mappings)) {
// Orca: Pan uses screen coordinates and must not inherit the rotation start point.
m_mouse.set_start_position_3D_as_invalid();
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
m_canvas->CaptureMouse();
// if dragging with right button or if button functions swapped and dragging with left button over blank area then pan
if (m_mouse.is_start_position_2D_defined()) {
// get point in model space at Z = 0
float z = 0.0f;
const Vec3d& cur_pos = _mouse_to_3d(pos, &z);
Vec3d orig = _mouse_to_3d(m_mouse.drag.start_position_2D, &z);
Camera& camera = wxGetApp().plater()->get_camera();
if (this->m_canvas_type != ECanvasType::CanvasAssembleView) {
// Orca: Use a constrained camera when navigating the 3D scene with a regular mouse, if the free camera is not selected
@@ -4636,7 +4668,14 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
camera.recover_from_free_camera();
}
camera.set_target(camera.get_target() + orig - cur_pos);
// Orca: Cache the surface under the initial click and apply every incremental
// cursor delta at that depth, so perspective zoom and camera angle stay exact.
const Vec2d screen_delta =
pos.cast<double>() - m_mouse.drag.start_position_2D.cast<double>();
if (!m_mouse.drag.camera_pan_anchor.has_value())
m_mouse.drag.camera_pan_anchor = get_camera_pan_anchor(camera, ECameraNavigationType::Mouse,
m_mouse.drag.start_position_2D.cast<double>());
pan_camera(camera, screen_delta, *m_mouse.drag.camera_pan_anchor);
m_dirty = true;
m_mouse.ignore_right_up = true; // will be reset on button up event even if not right button is pressed
}
@@ -7329,11 +7368,8 @@ void GLCanvas3D::_picking_pass()
m_hover_volume_idxs.clear();
m_hover_plate_idxs.clear();
// Orca: ignore clipping plane if not applying
GLGizmoBase *current_gizmo = m_gizmos.get_current();
const ClippingPlane clipping_plane = ((!current_gizmo || current_gizmo->apply_clipping_plane()) ? m_gizmos.get_clipping_plane() :
ClippingPlane::ClipsNothing())
.inverted_normal();
// Orca: Picking and camera navigation must interpret the active gizmo clipping plane identically.
const ClippingPlane clipping_plane = get_raycaster_clipping_plane();
const SceneRaycaster::HitResult hit = m_scene_raycaster.hit(m_mouse.position, wxGetApp().plater()->get_camera(), &clipping_plane);
if (hit.is_valid()) {
switch (hit.type)
@@ -10627,6 +10663,129 @@ Vec3d GLCanvas3D::_mouse_to_bed_3d(const Point& mouse_pos)
return mouse_ray(mouse_pos).intersect_plane(0.0);
}
ClippingPlane GLCanvas3D::get_raycaster_clipping_plane() const
{
// Orca: Ignore the gizmo clipping plane when the active tool does not apply it, and
// invert the result into the convention expected by SceneRaycaster.
GLGizmoBase* current_gizmo = m_gizmos.get_current();
return ((!current_gizmo || current_gizmo->apply_clipping_plane()) ? m_gizmos.get_clipping_plane() :
ClippingPlane::ClipsNothing())
.inverted_normal();
}
std::optional<Vec3d> GLCanvas3D::get_camera_orbit_target(ECameraNavigationType navigation_type) const
{
// Orca: Centralize the pre-existing pivot rules so orbiting and pan fallback cannot
// choose different reference depths for the same canvas and active tool.
PartPlate* current_plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
if (navigation_type == ECameraNavigationType::Gesture)
return current_plate == nullptr ? std::nullopt :
std::make_optional(current_plate->get_bounding_box().center());
const GLGizmosManager::EType gizmo_type = m_gizmos.get_current_type();
const bool use_scene_target = m_canvas_type == ECanvasType::CanvasAssembleView ||
gizmo_type == GLGizmosManager::FdmSupports || gizmo_type == GLGizmosManager::Seam ||
gizmo_type == GLGizmosManager::MmSegmentation || gizmo_type == GLGizmosManager::FuzzySkin;
if (use_scene_target) {
if (!m_selection.is_empty())
return m_selection.get_bounding_box().center();
// Orca: Preserve the world-origin fallback used by orbit in an empty scene.
return volumes_bounding_box().center();
}
// Orca: Free-camera rotation uses Camera::m_target rather than a plate or selection pivot.
if (wxGetApp().app_config->get_bool("use_free_camera"))
return std::nullopt;
Vec3d target = Vec3d::Zero();
if (m_canvas_type == ECanvasType::CanvasPreview) {
if (current_plate != nullptr)
target = current_plate->get_bounding_box().center();
} else if (!m_selection.is_empty()) {
target = m_selection.get_bounding_box().center();
} else {
// Orca: Match regular mouse orbit: objects on the active plate, then the plate itself.
BoundingBoxf3 bbox = volumes_bounding_box(true);
if (!bbox.defined && current_plate != nullptr)
bbox = current_plate->get_bounding_box();
if (bbox.defined)
target = bbox.center();
}
// Orca: Preserve the existing zero sentinel used by regular mouse orbit.
return target.isZero() ? std::nullopt : std::make_optional(target);
}
bool GLCanvas3D::is_bed_visible() const
{
if (m_canvas_type == ECanvasType::CanvasPreview)
return m_render_preview;
if (m_canvas_type != ECanvasType::CanvasView3D || !m_show_bed)
return false;
if (!m_main_toolbar.is_enabled())
return true;
const auto type = m_gizmos.get_current_type();
return type != GLGizmosManager::FdmSupports && type != GLGizmosManager::Seam &&
type != GLGizmosManager::MmSegmentation && type != GLGizmosManager::FuzzySkin;
}
Vec3d GLCanvas3D::get_camera_pan_anchor(Camera& camera, ECameraNavigationType navigation_type,
const Vec2d& screen_position) const
{
// Orthographic panning has the same scale at every depth, so no raycast is needed.
if (camera.get_type() != Camera::EType::Perspective)
return camera.get_target();
// Orca: Reject non-finite anchors and points behind the camera before their depth is
// allowed to scale a perspective pan.
const Vec3d camera_position = camera.get_position();
const Vec3d camera_forward = camera.get_dir_forward();
const auto is_valid_anchor = [&camera_position, &camera_forward](const Vec3d& anchor) {
return anchor.allFinite() && (anchor - camera_position).dot(camera_forward) > EPSILON;
};
// Orca: Prefer the nearest visible bed or volume surface and exclude gizmos and
// selected-volume picking priority from navigation depth selection.
const ClippingPlane clipping_plane = get_raycaster_clipping_plane();
const bool bed_visible = is_bed_visible();
const SceneRaycaster::HitResult hit = m_scene_raycaster.hit(screen_position, camera, &clipping_plane,
bed_visible ? SceneRaycaster::EHitMode::SceneOnly : SceneRaycaster::EHitMode::VolumesOnly);
if (hit.is_valid()) {
const Vec3d hit_position = hit.position.cast<double>();
if (is_valid_anchor(hit_position))
return hit_position;
}
// Orca: When the cursor is just outside the visible plate, use the point under it on the active
// plate plane. Using the plate center here would give it a different perspective depth.
PartPlate* current_plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
// Orca: An almost edge-on perspective makes intersection depth extremely sensitive to
// the cursor's vertical position. Use the stable orbit depth around horizontal views.
static constexpr double min_plate_plane_forward_z = 0.05;
if (bed_visible && std::abs(camera_forward.z()) >= min_plate_plane_forward_z &&
current_plate != nullptr && current_plate->get_bounding_box().defined) {
Vec3d ray_origin;
Vec3d ray_direction;
CameraUtils::ray_from_screen_pos(camera, screen_position, ray_origin, ray_direction);
const double z_direction = ray_direction.z();
if (ray_origin.allFinite() && ray_direction.allFinite() && std::abs(z_direction) > EPSILON) {
const double plate_z = current_plate->get_bounding_box().center().z();
const double distance = (plate_z - ray_origin.z()) / z_direction;
const Vec3d plate_position = ray_origin + distance * ray_direction;
const double eye_depth = (plate_position - camera_position).dot(camera_forward);
if (distance >= 0.0 && is_valid_anchor(plate_position) &&
eye_depth >= camera.get_near_z() && eye_depth <= camera.get_far_z())
return plate_position;
}
}
// Orca: Near-horizontal rays and points outside the scene depth use the orbit reference point.
const std::optional<Vec3d> orbit_target = get_camera_orbit_target(navigation_type);
return orbit_target.has_value() && is_valid_anchor(*orbit_target) ? *orbit_target : camera.get_target();
}
// While it looks like we can call
// this->reload_scene(true, true)
// the two functions are quite different:
+24 -1
View File
@@ -338,6 +338,8 @@ class GLCanvas3D
int move_volume_idx{ -1 };
bool move_requires_threshold{ false };
Point move_start_threshold_position_2D{ Invalid_2D_Point };
// Orca: Keep the world-space point selected at the start of a mouse pan.
std::optional<Vec3d> camera_pan_anchor;
};
bool dragging{ false };
@@ -346,7 +348,12 @@ class GLCanvas3D
Drag drag;
bool ignore_right_up;
void set_start_position_2D_as_invalid() { drag.start_position_2D = Drag::Invalid_2D_Point; }
// Orca: The screen-space start and world-space anchor describe the same pan session.
// Invalidating one must invalidate the other so a new drag cannot reuse stale depth.
void set_start_position_2D_as_invalid() {
drag.start_position_2D = Drag::Invalid_2D_Point;
drag.camera_pan_anchor.reset();
}
void set_start_position_3D_as_invalid() { drag.start_position_3D = Drag::Invalid_3D_Point; }
void set_move_start_threshold_position_2D_as_invalid() { drag.move_start_threshold_position_2D = Drag::Invalid_2D_Point; }
@@ -549,6 +556,8 @@ private:
bool m_fps_overlay_tick{ false };
LayersEditing m_layers_editing;
Mouse m_mouse;
// Orca: Gesture pans have their own lifecycle and stable world-space anchor.
std::optional<Vec3d> m_gesture_pan_anchor;
GLGizmosManager m_gizmos;
//BBS: GUI refactor: GLToolbar
mutable GLToolbar m_main_toolbar;
@@ -1406,6 +1415,20 @@ private:
// Convert the screen space coordinate to world coordinate on the bed.
Vec3d _mouse_to_bed_3d(const Point& mouse_pos);
// Orca: Navigation type selects the legacy pivot policy used when no visible surface is hit.
enum class ECameraNavigationType : unsigned char
{
Mouse,
Gesture
};
// Orca: These helpers keep clipping, orbit pivots, and perspective-pan depth selection consistent.
ClippingPlane get_raycaster_clipping_plane() const;
bool is_bed_visible() const;
std::optional<Vec3d> get_camera_orbit_target(ECameraNavigationType navigation_type) const;
Vec3d get_camera_pan_anchor(Camera& camera, ECameraNavigationType navigation_type,
const Vec2d& screen_position) const;
void _start_timer() { m_timer.Start(100, wxTIMER_CONTINUOUS); }
void _stop_timer() { m_timer.Stop(); }
+59 -31
View File
@@ -863,8 +863,11 @@ void GUI_App::post_init()
mainframe->Freeze();
#endif
plater_->canvas3D()->enable_render(false);
mainframe->select_tab(TAB_ID_PREPARE);
mainframe->select_prepare_for_gl_init();
plater_->select_view_3D("3D");
// The first render happens before the queued new_project() sets the same view.
plater_->get_camera().select_view("topfront");
plater_->get_camera().requires_zoom_to_bed = true;
//BBS init the opengl resource here
if (!plater_->canvas3D()->get_wxglcanvas()->IsShownOnScreen() ||
!plater_->canvas3D()->make_current_for_postinit()) {
@@ -900,10 +903,10 @@ void GUI_App::post_init()
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished rendering a first frame for test";
}
}
if (is_editor())
mainframe->select_tab(TAB_ID_HOME);
if (app_config->get("default_page") == "1")
if (starts_on_prepare())
mainframe->select_tab(TAB_ID_PREPARE);
else if (is_editor())
mainframe->select_tab(TAB_ID_HOME);
#ifndef __linux__
mainframe->Thaw();
#endif
@@ -912,6 +915,7 @@ void GUI_App::post_init()
plater_->trigger_restore_project(1);
//#endif
mainframe->prebuild_pages_when_idle();
//BBS: remove GCodeViewer as seperate APP logic
/*if (this->init_params->start_as_gcodeviewer) {
@@ -1926,9 +1930,9 @@ bool GUI_App::hot_reload_network_plugin()
m_device_manager->add_user_subscribe();
}
if (mainframe && mainframe->m_monitor) {
mainframe->m_monitor->update_network_version_footer();
mainframe->m_monitor->set_default();
if (MonitorPanel* monitor = MonitorPanel::if_built()) {
monitor->update_network_version_footer();
monitor->set_default();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": reset monitor panel";
}
@@ -3419,14 +3423,16 @@ bool GUI_App::on_init_inner()
}
BOOST_LOG_TRIVIAL(info) << "create the main window";
mainframe = new MainFrame();
// hide settings tabs after first Layout
if (is_editor()) {
if (starts_on_prepare()) {
mainframe->select_tab(TAB_ID_PREPARE);
} else {
mainframe->select_tab(TAB_ID_HOME);
}
}
sidebar().obj_list()->init();
//sidebar().aux_list()->init_auxiliary();
mainframe->m_project->init_auxiliary();
// update_mode(); // !!! do that later
SetTopWindow(mainframe);
@@ -4137,13 +4143,13 @@ void GUI_App::select_machine(const std::string& agent_id)
// Use MonitorPanel::select_machine() to trigger full selection flow
// This reuses existing logic for machine switching (UI updates, callbacks, etc.)
if (mainframe && mainframe->m_monitor) {
mainframe->m_monitor->select_machine(dev_id);
if (MonitorPanel* monitor = MonitorPanel::if_built()) {
monitor->select_machine(dev_id);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": triggered select_machine for dev_id=" << dev_id;
} else {
// Fallback if MonitorPanel not available
m_device_manager->set_selected_machine(dev_id);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": fallback set_selected_machine dev_id=" << dev_id;
} else if (m_device_manager->set_selected_machine(dev_id)) {
// The Device tab's own state is set when the tab is built.
MonitorPanel::on_machine_selected(m_device_manager->get_selected_machine());
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": set_selected_machine dev_id=" << dev_id;
}
}
@@ -4667,6 +4673,7 @@ void GUI_App::recreate_GUI(const wxString &msg_name)
//BBS: trigger restore project logic here, and skip confirm
plater_->trigger_restore_project(1);
mainframe->prebuild_pages_when_idle();
// #ys_FIXME_delete_after_testing Do we still need this ?
// CallAfter([]() {
@@ -5008,7 +5015,8 @@ void GUI_App::get_login_info(const std::string& provider/* = ORCA_CLOUD_PROVIDER
wxString strJS = wxString::Format("window.postMessage(%s)", from_u8(logout_cmd));
GUI::wxGetApp().run_script(strJS);
}
mainframe->m_webview->SetLoginPanelVisibility(true);
if (WebViewPanel* home = WebViewPanel::if_built())
home->SetLoginPanelVisibility(true);
}
}
@@ -5140,9 +5148,9 @@ std::string GUI_App::handle_web_request(std::string cmd)
"homepage_bambu_login_or_register",
};
if (app_config->get_stealth_mode() && stealth_blocked_info_commands.count(command_str)) {
CallAfter([this] {
if (mainframe && mainframe->m_webview)
mainframe->m_webview->SendCloudProvidersInfo();
CallAfter([] {
if (WebViewPanel* home = WebViewPanel::if_built())
home->SendCloudProvidersInfo();
});
return "";
}
@@ -5156,8 +5164,8 @@ std::string GUI_App::handle_web_request(std::string cmd)
if (dlg.ShowModal() == wxID_OK) {
app_config->set_bool("stealth_mode", false);
app_config->save();
if (mainframe && mainframe->m_webview)
mainframe->m_webview->SendCloudProvidersInfo();
if (WebViewPanel* home = WebViewPanel::if_built())
home->SendCloudProvidersInfo();
// Continue with login
if (command_str == "homepage_login_or_register")
this->request_login(true);
@@ -5238,8 +5246,8 @@ std::string GUI_App::handle_web_request(std::string cmd)
}
else if (command_str.compare("get_recent_projects") == 0) {
if (mainframe) {
if (mainframe->m_webview) {
mainframe->m_webview->SendRecentList(INT_MAX);
if (WebViewPanel* home = WebViewPanel::if_built()) {
home->SendRecentList(INT_MAX);
}
}
}
@@ -7772,8 +7780,8 @@ void GUI_App::on_stealth_mode_enter()
BOOST_LOG_TRIVIAL(info) << "logout: on_stealth_mode_enter";
request_user_logout(ORCA_CLOUD_PROVIDER);
request_user_logout(BBL_CLOUD_PROVIDER);
if (mainframe && mainframe->m_webview) {
mainframe->m_webview->SendCloudProvidersInfo();
if (WebViewPanel* home = WebViewPanel::if_built()) {
home->SendCloudProvidersInfo();
}
}
@@ -8183,6 +8191,24 @@ ConfigOptionMode GUI_App::get_saved_mode()
return saved_mode_from_string(app_config->get("user_mode"));
}
bool GUI_App::starts_on_prepare() const
{
return app_config->get("default_page") == "1";
}
int GUI_App::input_idle_ms() const
{
return int(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - m_last_input).count());
}
// Every wxCommandEvent claims the user-input category, so only real mouse and key events count.
int GUI_App::FilterEvent(wxEvent& event)
{
if (!event.IsCommandEvent() && (event.GetEventCategory() & wxEVT_CATEGORY_USER_INPUT))
m_last_input = std::chrono::steady_clock::now();
return Event_Skip;
}
ConfigOptionMode GUI_App::get_mode()
{
return app_config->get_bool("developer_mode") ? comDevelop : get_saved_mode();
@@ -8234,9 +8260,10 @@ void GUI_App::update_mode()
mainframe->m_param_panel->update_mode();
if (mainframe->m_param_dialog)
mainframe->m_param_dialog->panel()->update_mode();
if (mainframe->m_printer_view)
mainframe->m_printer_view->update_mode();
mainframe->m_webview->update_mode();
if (PrinterWebView* view = PrinterWebView::if_built())
view->update_mode();
if (WebViewPanel* home = WebViewPanel::if_built())
home->update_mode();
#ifdef _MSW_DARK_MODE
if (!wxGetApp().tabs_as_menu())
@@ -8254,9 +8281,10 @@ void GUI_App::update_mode()
}
void GUI_App::update_internal_development() {
mainframe->m_webview->update_mode();
if (mainframe->m_printer_view)
mainframe->m_printer_view->update_mode();
if (WebViewPanel* home = WebViewPanel::if_built())
home->update_mode();
if (PrinterWebView* view = PrinterWebView::if_built())
view->update_mode();
}
void GUI_App::show_ip_address_enter_dialog(wxString title)
+7
View File
@@ -1,6 +1,7 @@
#ifndef slic3r_GUI_App_hpp_
#define slic3r_GUI_App_hpp_
#include <chrono>
#include <functional>
#include <memory>
#include <string>
@@ -248,6 +249,7 @@ private:
bool m_app_conf_exists{ false };
EAppMode m_app_mode{ EAppMode::Editor };
bool m_is_recreating_gui{ false };
std::chrono::steady_clock::time_point m_last_input{ std::chrono::steady_clock::now() };
#ifdef __linux__
bool m_opengl_initialized{ false };
#endif
@@ -387,6 +389,11 @@ public:
bool is_editor() const { return m_app_mode == EAppMode::Editor; }
bool is_gcode_viewer() const { return m_app_mode == EAppMode::GCodeViewer; }
bool is_recreating_gui() const { return m_is_recreating_gui; }
// Milliseconds since the last mouse or keyboard event the app processed.
int input_idle_ms() const;
int FilterEvent(wxEvent& event) override;
// The Preferences "Default page" choice, stored as its index: 0 Home, 1 Prepare.
bool starts_on_prepare() const;
std::string logo_name() const { return is_editor() ? "OrcaSlicer" : "OrcaSlicer-gcodeviewer"; }
bool is_closing() const { return m_is_closing.load(std::memory_order_acquire); }
+2 -1
View File
@@ -122,7 +122,8 @@ HMSNotifyItem::HMSNotifyItem(const std::string& dev_id, wxWindow *parent, DevHMS
m_hms_content->Bind(wxEVT_LEFT_UP, [this](wxMouseEvent& e) {
wxCommandEvent evt(EVT_ALREADY_READ_HMS);
evt.SetString(long_error_code);
wxPostEvent(wxGetApp().mainframe->m_monitor, evt);
if (MonitorPanel* monitor = MonitorPanel::if_built())
wxPostEvent(monitor, evt);
if (!m_url.empty()) wxLaunchDefaultBrowser(m_url);
});
+100
View File
@@ -0,0 +1,100 @@
#include "IdleScheduler.hpp"
#include <chrono>
#include <boost/log/trivial.hpp>
#include "libslic3r/Utils.hpp"
#ifdef _WIN32
#include <windows.h>
#endif
namespace Slic3r { namespace GUI {
namespace {
// The timer's period while waiting for the user to go quiet.
constexpr int tick_ms = 250;
// Input-free time before a slice may start, about a double-click interval, so a user
// mid-gesture is left alone.
constexpr int quiet_ms = 500;
// Budget of one slice. The next slice is a timer message, so paint, timers and input
// queued meanwhile are handled first; a click waits at most a slice plus the unit that
// overran it.
constexpr int slice_ms = 40;
// On GTK a due timer runs ahead of repaints and posted events, so the next slice waits a few ms.
#ifdef __WXGTK__
constexpr int next_slice_ms = 5;
#else
constexpr int next_slice_ms = 0;
#endif
// True when unhandled keyboard, button, touch or pen input is queued; only Windows can ask.
bool input_pending()
{
#ifdef _WIN32
// Windows synthesizes a mouse move whenever a window appears under the cursor, so those
// do not count.
return (GetQueueStatus(QS_INPUT & ~QS_MOUSEMOVE) >> 16) != 0;
#else
return false;
#endif
}
} // namespace
IdleScheduler::IdleScheduler(std::function<int()> input_idle_ms) : m_input_idle_ms(std::move(input_idle_ms))
{
m_timer.Bind(wxEVT_TIMER, [this](wxTimerEvent&) { tick(); });
}
void IdleScheduler::start()
{
if (!m_timer.IsRunning())
m_timer.Start(tick_ms);
}
void IdleScheduler::stop()
{
m_timer.Stop();
}
void IdleScheduler::tick()
{
// A unit that pumps the event loop lets the timer fire inside its own slice.
if (m_in_slice)
return;
if (!m_queue.pending()) {
stop();
return;
}
if (m_input_idle_ms() < quiet_ms || input_pending()) {
start();
return;
}
const auto now_ms = [] {
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
};
const char* const fn = __FUNCTION__;
m_in_slice = true;
PrebuildQueue::Slice slice;
{
ScopeGuard in_slice([this] { m_in_slice = false; });
slice = m_queue.run_slice(slice_ms, now_ms, input_pending, [fn](const std::string& name, long long ms) {
BOOST_LOG_TRIVIAL(debug) << fn << ": " << name << ": unit took " << ms << " ms";
});
}
if (slice.completed)
BOOST_LOG_TRIVIAL(info) << fn << ": task complete: " << slice.name << ", " << slice.units << " unit(s) in " << slice.ms << " ms this slice";
else
BOOST_LOG_TRIVIAL(debug) << fn << ": " << slice.name << ": " << slice.units << " unit(s) in " << slice.ms << " ms, yielding";
if (!slice.remaining)
stop();
else if (input_pending())
m_timer.Start(tick_ms);
else
m_timer.StartOnce(next_slice_ms);
}
}} // namespace Slic3r::GUI
+41
View File
@@ -0,0 +1,41 @@
#pragma once
#include <functional>
#include <string>
#include <wx/event.h>
#include <wx/timer.h>
#include "PrebuildQueue.hpp"
namespace Slic3r { namespace GUI {
// Runs the queue's units while the user is idle. Once the user has been idle long enough,
// slices run one per timer message until the work is done or input arrives, so the event
// loop handles what it has between slices. Main thread only.
class IdleScheduler
{
public:
// input_idle_ms reports how long ago the user last touched the mouse or keyboard.
explicit IdleScheduler(std::function<int()> input_idle_ms);
void add(LazyBase& task) { m_queue.add(task); }
void clear() { m_queue.clear(); }
// The task names in queue order, for a log line.
std::string names() const { return m_queue.names(); }
// Arms the timer; it stops itself once no task is pending, so call again when a task
// appears.
void start();
void stop();
private:
void tick();
PrebuildQueue m_queue;
wxTimer m_timer;
std::function<int()> m_input_idle_ms;
bool m_in_slice{ false };
};
}} // namespace Slic3r::GUI
+27
View File
@@ -0,0 +1,27 @@
#include "Lazy.hpp"
#include <boost/log/trivial.hpp>
#include <wx/app.h>
#include <wx/utils.h>
namespace Slic3r { namespace GUI {
LazyBase::OnDemandBuild::OnDemandBuild(const LazyBase& lazy) : m_lazy(lazy), m_started(std::chrono::steady_clock::now())
{
// The unit tests have no app.
if (wxTheApp != nullptr)
m_busy = std::make_unique<wxBusyCursor>();
}
LazyBase::OnDemandBuild::~OnDemandBuild()
{
BOOST_LOG_TRIVIAL(info) << "Lazy::ensure: built " << m_lazy.name() << " on demand, " << m_units << " unit(s) in "
<< std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - m_started).count() << " ms";
}
void LazyBase::log_null_factory(const std::string& name)
{
BOOST_LOG_TRIVIAL(error) << "Lazy::build_step: the factory for " << name << " returned null";
}
}} // namespace Slic3r::GUI
+198
View File
@@ -0,0 +1,198 @@
#pragma once
#include <atomic>
#include <chrono>
#include <functional>
#include <memory>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "StagedBuild.hpp"
class wxBusyCursor;
// Deferred construction of one object. Lazy<T> holds the factory and builds the object on
// demand or, unit by unit, from an IdleScheduler; LazyBase is what the scheduler sees of
// it; LazyInstance<Self> gives a type with one such object app-wide static access to it.
// docs/HLSD/deferred-page-construction.md covers the design.
namespace Slic3r { namespace GUI {
template <class Self> class LazyInstance;
// A prebuild task: what PrebuildQueue sees of a Lazy<T>. A type with its own unit sequence
// implements it directly.
class LazyBase
{
public:
virtual ~LazyBase() = default;
// Labels the object in logs.
virtual const std::string& name() const = 0;
virtual bool built() const = 0;
// Whether the idle prebuild has work here.
virtual bool pending() const { return !built(); }
// Runs one unit and returns true while more remain.
virtual bool build_step() = 0;
// Position in the idle queue: lower builds first, negative is never prebuilt.
virtual int prebuild_order() const = 0;
protected:
// Busy cursor around a build the user is waiting on, and a log line after it.
class OnDemandBuild
{
public:
explicit OnDemandBuild(const LazyBase& lazy);
~OnDemandBuild();
void unit() { ++m_units; }
private:
const LazyBase& m_lazy;
std::unique_ptr<wxBusyCursor> m_busy;
std::chrono::steady_clock::time_point m_started;
int m_units{ 0 };
};
static void log_null_factory(const std::string& name);
};
// Holds an object a factory makes on the first build_step() or ensure(), followed by one
// StagedBuild step per unit if the type has them, and keeps callbacks until the object is
// complete. The object's parent owns it, not the holder. A LazyInstance type is registered
// for its statics; any other type is reached only through the holder.
template <class T>
class Lazy : public LazyBase
{
public:
using Factory = std::function<T*()>;
Lazy(std::string name, int order, Factory make) : m_name(std::move(name)), m_order(order), m_make(std::move(make))
{
if constexpr (registered())
LazyInstance<T>::s_lazy = this;
}
~Lazy() override
{
if constexpr (registered())
if (LazyInstance<T>::s_lazy == this)
LazyInstance<T>::s_lazy = nullptr;
}
Lazy(const Lazy&) = delete;
Lazy& operator=(const Lazy&) = delete;
// Null until completely built.
T* get() const { return built() ? m_object : nullptr; }
// Builds whatever is left now and returns the object, null if the factory returned null
// or a nested call finds the object mid-build.
T* ensure()
{
if (!built() && !m_building) {
OnDemandBuild build(*this);
do
build.unit();
while (build_step());
}
return get();
}
// Runs fn on the object now if it is built, otherwise once it is.
void when_built(std::function<void(T&)> fn)
{
if (built())
fn(*m_object);
else
m_deferred.push_back(std::move(fn));
}
const std::string& name() const override { return m_name; }
// Read by a worker thread through the statics; the last write in build_step() releases it.
bool built() const override { return m_complete.load(std::memory_order_acquire); }
bool pending() const override { return !built() && !m_failed; }
int prebuild_order() const override { return m_order; }
// The factory is the first unit, then one StagedBuild step each. A unit that pumps the
// event loop cannot re-enter; a nested call does nothing.
bool build_step() override
{
if (built() || m_building || m_failed)
return false;
m_building = true;
try {
if (m_object == nullptr)
m_object = m_make();
else
step();
} catch (...) {
m_building = false;
throw;
}
m_building = false;
if (m_object == nullptr) {
m_failed = true;
log_null_factory(m_name);
return false;
}
if (steps_remain())
return true;
m_complete.store(true, std::memory_order_release);
for (auto& fn : m_deferred)
fn(*m_object);
m_deferred.clear();
return false;
}
private:
static constexpr bool registered() { return std::is_base_of_v<LazyInstance<T>, T>; }
static constexpr bool staged() { return std::is_base_of_v<StagedBuild, T>; }
bool steps_remain() const
{
if constexpr (staged())
return !m_object->built();
else
return false;
}
void step()
{
if constexpr (staged())
m_object->build_step();
}
std::string m_name;
int m_order;
Factory m_make;
T* m_object{ nullptr };
std::atomic<bool> m_complete{ false };
bool m_building{ false };
bool m_failed{ false };
std::vector<std::function<void(T&)>> m_deferred;
};
// Mixin for a type with one lazily built instance in the app. The statics reach that
// instance through its Lazy holder (a recreated MainFrame's holder replaces the old
// frame's).
template <class Self>
class LazyInstance
{
public:
// Null until completely built.
static Self* if_built() { return s_lazy ? s_lazy->get() : nullptr; }
// Builds the object if needed; null while no holder exists or the holder has no
// object.
static Self* ensure() { return s_lazy ? s_lazy->ensure() : nullptr; }
// Runs fn on the object now if it is built, otherwise once it is.
static void when_built(std::function<void(Self&)> fn)
{
if (s_lazy)
s_lazy->when_built(std::move(fn));
}
private:
friend class Lazy<Self>;
inline static Lazy<Self>* s_lazy{ nullptr };
};
}} // namespace Slic3r::GUI
+14
View File
@@ -0,0 +1,14 @@
#include "LazyPage.hpp"
#include "GUI_App.hpp"
namespace Slic3r { namespace GUI {
void apply_dark_ui_to_lazy_panel(wxWindow* panel)
{
#ifdef _MSW_DARK_MODE
wxGetApp().UpdateDarkUIWin(panel);
#endif
}
}} // namespace Slic3r::GUI
+77
View File
@@ -0,0 +1,77 @@
#pragma once
#include <functional>
#include <string>
#include <utility>
#include <wx/bookctrl.h>
#include <wx/panel.h>
#include <wx/sizer.h>
#include "Lazy.hpp"
namespace Slic3r { namespace GUI {
// Applies the app's dark-mode pass to a panel built after the frame's own pass ran.
void apply_dark_ui_to_lazy_panel(wxWindow* panel);
// A page whose panel is built the first time the page is shown, or earlier by an
// IdleScheduler; nothing builds while the frame is still hidden. The Lazy side holds the
// panel and, for a LazyInstance panel type, registers it for the type's statics.
template <class Panel>
class LazyPage : public wxPanel, public Lazy<Panel>
{
public:
using Factory = std::function<Panel*(wxWindow* parent)>;
LazyPage(wxWindow* parent, std::string name, int order, Factory make = [](wxWindow* parent) { return new Panel(parent); })
: wxPanel(parent), Lazy<Panel>(std::move(name), order, [this, make = std::move(make)] {
Panel* panel = make(this);
GetSizer()->Add(panel, 1, wxEXPAND);
// Hidden while its page is, as an inserted page would be.
if (!IsShown())
panel->Hide();
return panel;
})
{
SetSizer(new wxBoxSizer(wxVERTICAL));
// Shown by the book when its tab is selected.
Hide();
this->when_built([this](Panel& panel) {
apply_dark_ui_to_lazy_panel(&panel);
Layout();
});
}
// The parent book currently lists this page.
bool in_book() const
{
auto* book = dynamic_cast<wxBookCtrlBase*>(GetParent());
return book != nullptr && book->FindPage(this) != wxNOT_FOUND;
}
// Pending only while the tab is in the book.
bool pending() const override { return in_book() && Lazy<Panel>::pending(); }
// Forwarded so the panel's own Show() override stays its activation hook; wx calls this
// virtual from the book's ShowWithEffect() only for wxSHOW_EFFECT_NONE, the default.
bool Show(bool show = true) override
{
const bool changed = wxPanel::Show(show);
if (show) {
// The book shows its first page as it is inserted, before startup has chosen the
// start page, so a hidden frame builds nothing; MainFrame::Show() completes it.
if (this->built() || wxGetTopLevelParent(this)->IsShown()) {
if (Panel* panel = this->ensure())
panel->Show(true);
// The sizer skipped the panel while the book kept it hidden.
Layout();
}
} else if (Panel* panel = this->get()) {
panel->Show(false);
}
return changed;
}
};
}} // namespace Slic3r::GUI
+157 -170
View File
@@ -317,7 +317,8 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
// BBS
, m_recent_projects(18)
, m_settings_dialog(this)
, diff_dialog(this)
, m_idle([] { return wxGetApp().input_idle_ms(); })
, m_diff_dialog("compare_presets", 100, [this] { return make_diff_dialog(); })
{
#ifdef __WXOSX__
set_miniaturizable(GetHandle());
@@ -737,9 +738,6 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
wxGetApp().persist_window_geometry(this, true);
wxGetApp().persist_window_geometry(&m_settings_dialog, true);
// bind events from DiffDlg
bind_diff_dialog();
}
bool MainFrame::handle_global_shortcut(const KeyChord& chord)
@@ -832,8 +830,10 @@ bool MainFrame::handle_global_shortcut(const KeyChord& chord)
return true;
}
void MainFrame::bind_diff_dialog()
DiffPresetDialog* MainFrame::make_diff_dialog()
{
auto* dialog = new DiffPresetDialog(this);
auto get_tab = [](Preset::Type type) {
Tab* null_tab = nullptr;
for (Tab* tab : wxGetApp().tabs_list)
@@ -842,23 +842,24 @@ void MainFrame::bind_diff_dialog()
return null_tab;
};
auto transfer = [this, get_tab](Preset::Type type) {
get_tab(type)->transfer_options(diff_dialog.get_left_preset_name(type),
diff_dialog.get_right_preset_name(type),
diff_dialog.get_selected_options(type));
auto transfer = [dialog, get_tab](Preset::Type type) {
get_tab(type)->transfer_options(dialog->get_left_preset_name(type),
dialog->get_right_preset_name(type),
dialog->get_selected_options(type));
};
auto process_options = [this](std::function<void(Preset::Type)> process) {
const Preset::Type diff_dlg_type = diff_dialog.view_type();
auto process_options = [dialog](std::function<void(Preset::Type)> process) {
const Preset::Type diff_dlg_type = dialog->view_type();
if (diff_dlg_type == Preset::TYPE_INVALID) {
for (const Preset::Type& type : diff_dialog.types_list() )
for (const Preset::Type& type : dialog->types_list() )
process(type);
}
else
process(diff_dlg_type);
};
diff_dialog.Bind(EVT_DIFF_DIALOG_TRANSFER, [process_options, transfer](SimpleEvent&) { process_options(transfer); });
dialog->Bind(EVT_DIFF_DIALOG_TRANSFER, [process_options, transfer](SimpleEvent&) { process_options(transfer); });
return dialog;
}
@@ -1186,8 +1187,9 @@ void MainFrame::update_edge_panels()
void MainFrame::shutdown()
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "MainFrame::shutdown enter";
if (m_project != nullptr)
m_project->shutdown();
m_idle.stop();
if (ProjectPanel* project = ProjectPanel::if_built())
project->shutdown();
m_plugin_pages.shutdown();
if (m_plater != nullptr)
m_plater->remove_dock_panes();
@@ -1314,19 +1316,6 @@ void MainFrame::show_option(bool show)
}
}
#ifdef SLIC3R_CAD
DesignPanel* MainFrame::ensure_design_panel()
{
if (m_design_panel == nullptr && m_design_page != nullptr) {
wxBusyCursor busy;
m_design_panel = new DesignPanel(m_design_page);
m_design_page->GetSizer()->Add(m_design_panel, 1, wxEXPAND);
m_design_page->Layout();
}
return m_design_panel;
}
#endif
void MainFrame::init_tabpanel() {
// wxNB_NOPAGETHEME: Disable Windows Vista theme for the Notebook background. The theme performance is terrible on
// Windows 10 with multiple high resolution displays connected.
@@ -1369,23 +1358,20 @@ void MainFrame::init_tabpanel() {
// m_param_panel->OnActivate();
#ifdef SLIC3R_CAD
else if (m_design_page != nullptr && panel == m_design_page) {
// Built on first activation, never at startup: the panel creates several hundred
// controls and its own GL canvas, which a user who does not open the tab should
// not pay for.
ensure_design_panel();
// Re-sync the Design bed to the active printer: the panel is built before the
// printer profile is fully applied, so its bed must refresh on activation or the
// grid (true bed) spills past the stale default bed quad.
m_design_panel->on_tab_shown();
DesignPanel::ensure()->on_tab_shown();
}
#endif
else if (panel == m_monitor) {
else if (panel == m_monitor_page) {
//monitor
}
#ifdef SLIC3R_CAD
// Any page that is not Design takes the Design status line down with it — see
// DesignPanel::on_tab_hidden for why the popup does not follow the page on its own.
if (m_design_panel != nullptr && panel != m_design_page) m_design_panel->on_tab_hidden();
if (DesignPanel* design = DesignPanel::if_built(); design != nullptr && panel != m_design_page)
design->on_tab_hidden();
#endif
#ifndef __APPLE__
if (m_last_selected_tab == TAB_ID_PREPARE) {
@@ -1401,13 +1387,14 @@ void MainFrame::init_tabpanel() {
});
if (wxGetApp().is_editor()) {
m_webview = new WebViewPanel(m_tabpanel);
m_home_page = new LazyPage<WebViewPanel>(m_tabpanel, TAB_ID_HOME, 10);
m_lazy_pages.push_back(m_home_page);
Bind(EVT_LOAD_URL, [this](wxCommandEvent &evt) {
wxString url = evt.GetString();
select_tab(TAB_ID_HOME);
m_webview->load_url(url);
WebViewPanel::ensure()->load_url(url);
});
m_tabpanel->AddPage(TAB_ID_HOME, m_webview, "", "tab_home_active");
m_tabpanel->AddPage(TAB_ID_HOME, m_home_page, "", "tab_home_active");
m_param_panel = new ParamsPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL);
}
@@ -1418,15 +1405,13 @@ void MainFrame::init_tabpanel() {
wxGetApp().plater_ = m_plater;
#ifdef SLIC3R_CAD
// Stand-in page for the Design tab. The real DesignPanel is built into it the first time
// the tab is selected (see the page-changed handler above), so nothing it constructs sits
// on the startup path. The experimental feature is off by default, and when it is off the
// page is never created, so the tab does not appear at all (the preference takes effect on
// the next start, like the other feature toggles).
// The experimental feature is off by default, and when it is off the page is never
// created, so the tab does not appear at all (the preference takes effect on the next
// start, like the other feature toggles).
if (wxGetApp().is_enable_cad_feature()) {
m_design_page = new wxPanel(this);
m_design_page->SetSizer(new wxBoxSizer(wxVERTICAL));
m_design_page->Hide();
// Experimental and heavy enough that building it unasked would cost more than it saves.
m_design_page = new LazyPage<DesignPanel>(this, TAB_ID_DESIGN, -1);
m_lazy_pages.push_back(m_design_page);
start_mcp_control_if_enabled(); // opens the MCP socket iff ORCA_CAD_MCP is set
}
#endif
@@ -1434,33 +1419,41 @@ void MainFrame::init_tabpanel() {
create_preset_tabs();
//BBS add pages
m_monitor = new MonitorPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_monitor->SetBackgroundColour(*wxWHITE);
m_tabpanel->AddPage(TAB_ID_MONITOR, m_monitor, _L("Device"), "tab_monitor_active");
m_monitor_page = new LazyPage<MonitorPanel>(m_tabpanel, TAB_ID_MONITOR, 20);
m_lazy_pages.push_back(m_monitor_page);
m_tabpanel->AddPage(TAB_ID_MONITOR, m_monitor_page, _L("Device"), "tab_monitor_active");
m_printer_view = new PrinterWebView(m_tabpanel);
m_printer_view_page = new LazyPage<PrinterWebView>(m_tabpanel, TAB_ID_MONITOR_WEB, 50, [this](wxWindow* parent) {
auto* view = new PrinterWebView(parent);
if (!m_printer_url.empty())
view->load_url(m_printer_url, m_printer_api_key);
return view;
});
m_lazy_pages.push_back(m_printer_view_page);
Bind(EVT_LOAD_PRINTER_URL, [this](LoadPrinterViewEvent &evt) {
wxString url = evt.GetString();
wxString key = evt.GetAPIkey();
//select_tab(MainFrame::tpMonitor);
m_printer_view->load_url(url, key);
m_printer_url = evt.GetString();
m_printer_api_key = evt.GetAPIkey();
if (PrinterWebView* view = PrinterWebView::if_built())
view->load_url(m_printer_url, m_printer_api_key);
});
m_printer_view->Hide();
m_multi_machine_page = new LazyPage<MultiMachinePage>(m_tabpanel, TAB_ID_MULTI_DEVICE, 40);
m_lazy_pages.push_back(m_multi_machine_page);
if (wxGetApp().is_enable_multi_machine()) {
m_multi_machine = new MultiMachinePage(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_multi_machine->SetBackgroundColour(*wxWHITE);
// TODO: change the bitmap
m_tabpanel->AddPage(TAB_ID_MULTI_DEVICE, m_multi_machine, _L("Multi-device"), "tab_multi_active");
m_tabpanel->AddPage(TAB_ID_MULTI_DEVICE, m_multi_machine_page, _L("Multi-device"), "tab_multi_active");
}
m_project = new ProjectPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_project->SetBackgroundColour(*wxWHITE);
m_tabpanel->AddPage(TAB_ID_PROJECT, m_project, _L("Project"), "tab_auxiliary_active");
m_project_page = new LazyPage<ProjectPanel>(m_tabpanel, TAB_ID_PROJECT, 60);
m_lazy_pages.push_back(m_project_page);
m_tabpanel->AddPage(TAB_ID_PROJECT, m_project_page, _L("Project"), "tab_auxiliary_active");
m_calibration = new CalibrationPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_calibration->SetBackgroundColour(*wxWHITE);
m_tabpanel->AddPage(TAB_ID_CALIBRATION, m_calibration, _L("Calibration"), "tab_calibration_active");
// show_device() removes this tab for printers without the Bambu device tab, and the page
// then never builds its panel.
m_calibration_page = new LazyPage<CalibrationPanel>(m_tabpanel, TAB_ID_CALIBRATION, 30);
m_lazy_pages.push_back(m_calibration_page);
m_tabpanel->AddPage(TAB_ID_CALIBRATION, m_calibration_page, _L("Calibration"), "tab_calibration_active");
// Plugin pages are appended after the built-in tabs; their ids are namespaced
// (plugin.<plugin_key>.<name>) so they can't collide with the built-in TAB_ID_* constants.
@@ -1495,67 +1488,44 @@ void MainFrame::show_device(bool should_use_native) {
// Remove the extra page before switching to any layout that shouldn't have it.
if (!want_web_device_tab) {
if ((idx = m_tabpanel->FindPageByName(TAB_ID_MONITOR_WEB)) != wxNOT_FOUND) {
m_printer_view->Show(false);
m_printer_view_page->Show(false);
m_tabpanel->RemovePage(idx);
}
}
if (use_printer_agents) {
if (!m_monitor) {
m_monitor = new MonitorPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_monitor->SetBackgroundColour(*wxWHITE);
}
if (m_tabpanel->FindPage(m_monitor) == wxNOT_FOUND) {
if ((idx = m_tabpanel->FindPage(m_printer_view)) != wxNOT_FOUND) {
m_printer_view->Show(false);
if (!m_monitor_page->in_book()) {
if ((idx = m_tabpanel->FindPage(m_printer_view_page)) != wxNOT_FOUND) {
m_printer_view_page->Show(false);
m_tabpanel->RemovePage(idx);
}
m_monitor->Show(false);
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_monitor,
m_monitor_page->Show(false);
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_monitor_page,
_L("Device"), "tab_monitor_active");
}
if (m_printer_view == nullptr) {
m_printer_view = new PrinterWebView(m_tabpanel);
Bind(EVT_LOAD_PRINTER_URL, [this](LoadPrinterViewEvent& evt) {
wxString url = evt.GetString();
wxString key = evt.GetAPIkey();
// select_tab(MainFrame::tpMonitor);
m_printer_view->load_url(url, key);
});
}
if (wxGetApp().is_enable_multi_machine()) {
if (!m_multi_machine) {
m_multi_machine = new MultiMachinePage(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_multi_machine->SetBackgroundColour(*wxWHITE);
}
// TODO: change the bitmap
if (m_tabpanel->FindPage(m_multi_machine) == wxNOT_FOUND) {
m_multi_machine->Show(false);
if (!m_multi_machine_page->in_book()) {
m_multi_machine_page->Show(false);
// Past the web Device tab when it is already there, so enabling multi-machine
// later can't wedge this page between the two Device tabs.
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_MONITOR_WEB, TAB_ID_MONITOR}),
TAB_ID_MULTI_DEVICE, m_multi_machine, _L("Multi-device"), "tab_multi_active");
TAB_ID_MULTI_DEVICE, m_multi_machine_page, _L("Multi-device"), "tab_multi_active");
}
}
if (!m_calibration) {
m_calibration = new CalibrationPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_calibration->SetBackgroundColour(*wxWHITE);
}
if (m_tabpanel->FindPage(m_calibration) == wxNOT_FOUND) {
m_calibration->Show(false);
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PROJECT}), TAB_ID_CALIBRATION, m_calibration,
if (!m_calibration_page->in_book()) {
m_calibration_page->Show(false);
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PROJECT}), TAB_ID_CALIBRATION, m_calibration_page,
_L("Calibration"), "tab_calibration_active");
}
if (want_web_device_tab) {
if ((idx = m_tabpanel->FindPage(m_printer_view)) == wxNOT_FOUND) {
m_printer_view->Show(false);
if ((idx = m_tabpanel->FindPage(m_printer_view_page)) == wxNOT_FOUND) {
m_printer_view_page->Show(false);
// Immediately right of the native Device tab, not at the end of the tab bar.
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_MONITOR}), TAB_ID_MONITOR_WEB,
m_printer_view, _L("Device (Web)"), "tab_monitor_active");
m_printer_view_page, _L("Device (Web)"), "tab_monitor_active");
} else {
m_tabpanel->SetPageText(idx, _L("Device (Web)"));
}
@@ -1567,48 +1537,37 @@ void MainFrame::show_device(bool should_use_native) {
fit_tab_labels(); // ORCA on printer change
m_plugin_pages.relayout(); // re-sync plugin tabs against the native tabs just mutated above
if (m_prebuild_started)
m_idle.start();
return;
}
if (should_use_native) {
if (m_tabpanel->FindPage(m_monitor) != wxNOT_FOUND) {
if (m_monitor_page->in_book()) {
fit_tab_labels(); // ORCA on printer change - same button layout
return;
}
// Remove printer view
if ((idx = m_tabpanel->FindPage(m_printer_view)) != wxNOT_FOUND) {
m_printer_view->Show(false);
if ((idx = m_tabpanel->FindPage(m_printer_view_page)) != wxNOT_FOUND) {
m_printer_view_page->Show(false);
m_tabpanel->RemovePage(idx);
}
// Create/insert monitor page
if (!m_monitor) {
m_monitor = new MonitorPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_monitor->SetBackgroundColour(*wxWHITE);
}
m_monitor->Show(false);
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_monitor,
// Insert monitor page
m_monitor_page->Show(false);
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_monitor_page,
_L("Device"), "tab_monitor_active");
if (wxGetApp().is_enable_multi_machine()) {
if (!m_multi_machine) {
m_multi_machine = new MultiMachinePage(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_multi_machine->SetBackgroundColour(*wxWHITE);
}
// TODO: change the bitmap
m_multi_machine->Show(false);
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_MONITOR}), TAB_ID_MULTI_DEVICE, m_multi_machine,
m_multi_machine_page->Show(false);
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_MONITOR}), TAB_ID_MULTI_DEVICE, m_multi_machine_page,
_L("Multi-device"), "tab_multi_active");
}
if (!m_calibration) {
m_calibration = new CalibrationPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_calibration->SetBackgroundColour(*wxWHITE);
}
m_calibration->Show(false);
m_calibration_page->Show(false);
// Last of the built-in tabs, but plugin tabs already sit past it — anchor rather than
// append, so its position doesn't depend on the relayout() below running afterwards.
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PROJECT}), TAB_ID_CALIBRATION, m_calibration,
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PROJECT}), TAB_ID_CALIBRATION, m_calibration_page,
_L("Calibration"), "tab_calibration_active");
#ifdef _MSW_DARK_MODE
@@ -1616,37 +1575,30 @@ void MainFrame::show_device(bool should_use_native) {
#endif // _MSW_DARK_MODE
} else {
if (m_tabpanel->FindPage(m_printer_view) != wxNOT_FOUND) {
if (m_printer_view_page->in_book()) {
fit_tab_labels(); // ORCA on printer change - same button layout
return;
}
if ((idx = m_tabpanel->FindPage(m_calibration)) != wxNOT_FOUND) {
m_calibration->Show(false);
if ((idx = m_tabpanel->FindPage(m_calibration_page)) != wxNOT_FOUND) {
m_calibration_page->Show(false);
m_tabpanel->RemovePage(idx);
}
if ((idx = m_tabpanel->FindPage(m_multi_machine)) != wxNOT_FOUND) {
m_multi_machine->Show(false);
if ((idx = m_tabpanel->FindPage(m_multi_machine_page)) != wxNOT_FOUND) {
m_multi_machine_page->Show(false);
m_tabpanel->RemovePage(idx);
}
if ((idx = m_tabpanel->FindPage(m_monitor)) != wxNOT_FOUND) {
m_monitor->Show(false);
if ((idx = m_tabpanel->FindPage(m_monitor_page)) != wxNOT_FOUND) {
m_monitor_page->Show(false);
m_tabpanel->RemovePage(idx);
}
if (m_printer_view == nullptr) {
m_printer_view = new PrinterWebView(m_tabpanel);
Bind(EVT_LOAD_PRINTER_URL, [this](LoadPrinterViewEvent& evt) {
wxString url = evt.GetString();
wxString key = evt.GetAPIkey();
// select_tab(MainFrame::tpMonitor);
m_printer_view->load_url(url, key);
});
}
m_printer_view->Show(false);
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_printer_view,
m_printer_view_page->Show(false);
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_printer_view_page,
_L("Device"), "tab_monitor_active");
}
fit_tab_labels(); // ORCA on printer change
m_plugin_pages.relayout(); // re-sync plugin tabs against the native tabs just mutated above
if (m_prebuild_started)
m_idle.start();
}
bool MainFrame::is_prepare_or_preview_tab() const
@@ -2692,13 +2644,11 @@ void MainFrame::on_dpi_changed(const wxRect& suggested_rect)
//BBS GUI refactor: remove unused layout new/dlg
//if (m_layout != ESettingsLayout::Dlg) // Do not update tabs if the Settings are in the separated dialog
m_param_panel->msw_rescale();
m_project->msw_rescale();
if(m_monitor)
m_monitor->msw_rescale();
if(m_multi_machine)
m_multi_machine->msw_rescale();
if(m_calibration)
m_calibration->msw_rescale();
// A panel mid-build gets the pass once it is complete.
ProjectPanel::when_built([](ProjectPanel& project) { project.msw_rescale(); });
MonitorPanel::when_built([](MonitorPanel& monitor) { monitor.msw_rescale(); });
MultiMachinePage::when_built([](MultiMachinePage& multi_machine) { multi_machine.msw_rescale(); });
CalibrationPanel::when_built([](CalibrationPanel& calibration) { calibration.msw_rescale(); });
// BBS
#if 0
@@ -2752,7 +2702,8 @@ void MainFrame::on_sys_color_changed()
#endif
#endif
diff_dialog.on_sys_color_changed();
if (DiffPresetDialog* dialog = DiffPresetDialog::if_built())
dialog->on_sys_color_changed();
// BBS
m_tabpanel->Rescale();
@@ -2760,10 +2711,8 @@ void MainFrame::on_sys_color_changed()
// update Plater
wxGetApp().plater()->sys_color_changed();
if(m_monitor)
m_monitor->on_sys_color_changed();
if(m_calibration)
m_calibration->on_sys_color_changed();
MonitorPanel::when_built([](MonitorPanel& monitor) { monitor.on_sys_color_changed(); });
CalibrationPanel::when_built([](CalibrationPanel& calibration) { calibration.on_sys_color_changed(); });
// update Tabs
for (auto tab : wxGetApp().tabs_list)
tab->sys_color_changed();
@@ -3696,7 +3645,8 @@ void MainFrame::set_max_recent_count(int max)
}
wxGetApp().app_config->set_recent_projects(recent_projects);
wxGetApp().app_config->save();
m_webview->SendRecentList(-1);
if (WebViewPanel* home = WebViewPanel::if_built())
home->SendRecentList(-1);
}
}
@@ -4040,23 +3990,56 @@ void MainFrame::select_tab(wxPanel* panel)
select_tab(page_name);
}
// Selects the Prepare page without the page-changed event, so the GL canvas is on screen
// and nothing else is built for the pass.
void MainFrame::select_prepare_for_gl_init()
{
m_tabpanel->ChangeSelection(m_tabpanel->FindPageByName(TAB_ID_PREPARE));
}
// The book shows its first page as it is inserted, while the frame is hidden and nothing
// may build; the first show completes that page.
bool MainFrame::Show(bool show)
{
const bool changed = DPIFrame::Show(show);
if (show && changed && m_tabpanel != nullptr)
if (wxWindow* page = m_tabpanel->GetCurrentPage())
page->Show(true);
return changed;
}
// A page out of the book stays registered and is passed over; a negative order is never
// registered.
void MainFrame::prebuild_pages_when_idle()
{
m_idle.clear();
if (m_param_panel)
m_idle.add(m_param_panel->settings_page_prebuild());
for (LazyBase* page : m_lazy_pages)
if (page->prebuild_order() >= 0)
m_idle.add(*page);
m_idle.add(m_diff_dialog);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": prebuild queue: " << m_idle.names();
m_idle.start();
m_prebuild_started = true;
}
//BBS
void MainFrame::jump_to_monitor(std::string dev_id)
{
if(!m_monitor)
return;
m_tabpanel->SelectPageByName(TAB_ID_MONITOR);
if (!dev_id.empty()) {
((MonitorPanel*)m_monitor)->select_machine(dev_id);
MonitorPanel::ensure()->select_machine(dev_id);
}
}
void MainFrame::jump_to_multipage()
{
if(!m_multi_machine)
if (!m_multi_machine_page->in_book())
return;
m_tabpanel->SelectPageByName(TAB_ID_MULTI_DEVICE);
((MultiMachinePage*)m_multi_machine)->jump_to_send_page();
if (MultiMachinePage* page = m_multi_machine_page->ensure())
page->jump_to_send_page();
}
@@ -4105,8 +4088,8 @@ void MainFrame::request_select_tab(const wxString& id)
}
int MainFrame::get_calibration_curr_tab() {
if (m_calibration)
return m_calibration->get_tabpanel()->GetSelection();
if (CalibrationPanel* calibration = CalibrationPanel::if_built())
return calibration->get_tabpanel()->GetSelection();
return -1;
}
@@ -4215,7 +4198,8 @@ void MainFrame::add_to_recent_projects(const wxString& filename)
recent_projects.push_back(into_u8(m_recent_projects.GetHistoryFile(i)));
}
wxGetApp().app_config->set_recent_projects(recent_projects);
m_webview->SendRecentList(0);
if (WebViewPanel* home = WebViewPanel::if_built())
home->SendRecentList(0);
}
}
@@ -4331,7 +4315,8 @@ void MainFrame::open_recent_project(size_t file_id, wxString const & filename)
recent_projects.push_back(into_u8(m_recent_projects.GetHistoryFile(i)));
}
wxGetApp().app_config->set_recent_projects(recent_projects);
m_webview->SendRecentList(-1);
if (WebViewPanel* home = WebViewPanel::if_built())
home->SendRecentList(-1);
}
}
}
@@ -4354,7 +4339,8 @@ void MainFrame::remove_recent_project(size_t file_id, wxString const &filename)
recent_projects.push_back(into_u8(m_recent_projects.GetHistoryFile(i)));
}
wxGetApp().app_config->set_recent_projects(recent_projects);
m_webview->SendRecentList(-1);
if (WebViewPanel* home = WebViewPanel::if_built())
home->SendRecentList(-1);
}
void MainFrame::load_url(wxString url)
@@ -4408,14 +4394,14 @@ bool MainFrame::is_printer_view() const { return m_tabpanel->GetSelectedPageName
void MainFrame::refresh_plugin_tips()
{
if (m_webview != nullptr)
m_webview->ShowNetpluginTip();
if (WebViewPanel* home = WebViewPanel::if_built())
home->ShowNetpluginTip();
}
void MainFrame::RunScript(wxString js)
{
if (m_webview != nullptr)
m_webview->RunScript(js);
if (WebViewPanel* home = WebViewPanel::if_built())
home->RunScript(js);
}
void MainFrame::technology_changed()
@@ -4524,7 +4510,8 @@ void MainFrame::update_side_preset_ui()
//take off multi machine
if(m_multi_machine){m_multi_machine->clear_page();}
if (MultiMachinePage* multi_machine = MultiMachinePage::if_built())
multi_machine->clear_page();
}
void MainFrame::on_select_default_preset(SimpleEvent& evt)
+27 -17
View File
@@ -26,6 +26,8 @@
#include "Widgets/SideButton.hpp"
#include "Widgets/SideMenuPopup.hpp"
#include "FilamentGroupPopup.hpp"
#include "LazyPage.hpp"
#include "IdleScheduler.hpp"
#include <boost/property_tree/ptree_fwd.hpp>
@@ -136,6 +138,13 @@ class MainFrame : public DPIFrame
#endif
bool m_loaded {false};
wxTimer* m_reset_title_text_colour_timer{ nullptr };
IdleScheduler m_idle;
bool m_prebuild_started{ false };
// Every LazyPage, in and out of the book; prebuild_pages_when_idle() registers them.
std::vector<LazyBase*> m_lazy_pages;
// The latest EVT_LOAD_PRINTER_URL, applied when the web Device view is built.
wxString m_printer_url;
wxString m_printer_api_key;
wxString m_qs_last_input_file = wxEmptyString;
wxString m_qs_last_output_file = wxEmptyString;
@@ -180,7 +189,7 @@ class MainFrame : public DPIFrame
bool can_delete() const;
bool can_delete_all() const;
bool can_reslice() const;
void bind_diff_dialog();
DiffPresetDialog* make_diff_dialog();
// BBS
wxBoxSizer* create_side_tools();
@@ -377,6 +386,12 @@ public:
void select_tab(wxPanel* panel);
void select_tab(const wxString& id = wxString());
void request_select_tab(const wxString& id);
// post_init() needs the plater's canvas on screen to initialize OpenGL; this pass does not
// build the settings page.
void select_prepare_for_gl_init();
// Builds the lazy tab pages while the user is idle; post_init() calls it once.
void prebuild_pages_when_idle();
bool Show(bool show = true) override;
int get_calibration_curr_tab();
void select_view(const std::string& direction);
void update_shortcut_labels();
@@ -435,27 +450,21 @@ public:
BBLTopbar* m_topbar{ nullptr };
PrintHostQueueDialog* printhost_queue_dlg() { return m_printhost_queue_dlg; }
Plater* m_plater { nullptr };
// Lazy pages, created once and kept for the frame's life; their panels are reached
// through LazyInstance's statics, and show_device() only moves pages in and out of the book.
#ifdef SLIC3R_CAD
// The tab page is the placeholder; m_design_panel stays null until the tab is first
// selected, so everything the Design panel builds stays off the startup path.
wxPanel* m_design_page { nullptr };
DesignPanel* m_design_panel { nullptr };
// Builds the Design panel if it does not exist yet and returns it (null only before the
// placeholder page itself exists). Main thread only -- it creates wx controls. Both the
// tab activation and the MCP socket go through this: the socket is driven headlessly,
// with nobody to click the tab, and without this every verb would answer "not ready".
DesignPanel* ensure_design_panel();
LazyPage<DesignPanel>* m_design_page { nullptr };
#endif
//BBS: GUI refactor
MonitorPanel* m_monitor{ nullptr };
LazyPage<MonitorPanel>* m_monitor_page{ nullptr };
//AuxiliaryPanel* m_auxiliary{ nullptr };
MultiMachinePage* m_multi_machine{ nullptr };
ProjectPanel* m_project{ nullptr };
LazyPage<MultiMachinePage>* m_multi_machine_page{ nullptr };
LazyPage<ProjectPanel>* m_project_page{ nullptr };
CalibrationPanel* m_calibration{ nullptr };
WebViewPanel* m_webview { nullptr };
PrinterWebView* m_printer_view{nullptr};
LazyPage<CalibrationPanel>* m_calibration_page{ nullptr };
LazyPage<WebViewPanel>* m_home_page { nullptr };
LazyPage<PrinterWebView>* m_printer_view_page{nullptr};
PluginPages m_plugin_pages;
wxLogWindow* m_log_window { nullptr };
// BBS
@@ -466,7 +475,8 @@ public:
ParamsDialog* m_param_dialog{ nullptr };
//BBS
SettingsDialog m_settings_dialog;
DiffPresetDialog diff_dialog;
// The Compare presets dialog, built on first use or at idle through its holder.
Lazy<DiffPresetDialog> m_diff_dialog;
wxWindow* m_plater_page{ nullptr };
PrintHostQueueDialog* m_printhost_queue_dlg;
+31 -24
View File
@@ -103,6 +103,7 @@ MonitorPanel::MonitorPanel(wxWindow* parent, wxWindowID id, const wxPoint& pos,
: wxPanel(parent, id, pos, size, style),
m_select_machine(SelectMachinePopup(this))
{
SetBackgroundColour(*wxWHITE);
#ifdef __WINDOWS__
SetDoubleBuffered(true);
#endif //__WINDOWS__
@@ -180,21 +181,26 @@ void MonitorPanel::init_tabpanel()
auto title = m_tabpanel->GetPageText(m_tabpanel->GetSelection());
m_media_file_panel->SwitchStorage(title == _L("Storage"));
}
// The first page is selected while the panel is built off screen.
if (page->IsShownOnScreen())
page->SetFocus();
update_all();
}, m_tabpanel->GetId());
//m_status_add_machine_panel = new AddMachinePanel(m_tabpanel);
m_status_info_panel = new StatusPanel(m_tabpanel);
// Queued before the page is added, since adding it selects it and runs the handler above,
// where built() must already be false.
add_build_steps_of(*m_status_info_panel);
m_tabpanel->AddPage(m_status_info_panel, _L("Status"), true);
add_build_step([this] {
m_media_file_panel = new MediaFilePanel(m_tabpanel);
m_tabpanel->AddPage(m_media_file_panel, _L("Storage"), false);
//m_tabpanel->AddPage(m_media_file_panel, _L("Internal Storage"), false);
});
add_build_step([this] {
m_upgrade_panel = new UpgradePanel(m_tabpanel);
m_tabpanel->AddPage(m_upgrade_panel, _L_CONTEXT(L_CONTEXT("Update", "Firmware"), "Firmware"), false);
});
add_build_step([this] {
m_hms_panel = new HMSPanel(m_tabpanel);
m_tabpanel->AddPage(m_hms_panel, _L("Assistant(HMS)"), false);
@@ -203,8 +209,8 @@ void MonitorPanel::init_tabpanel()
m_tabpanel->SetFooterText(wxString::Format(_L("Network plug-in v%s"), network_ver));
}
m_initialized = true;
show_status((int)MonitorStatus::MONITOR_NO_PRINTER);
});
}
void MonitorPanel::set_default()
@@ -288,19 +294,23 @@ void MonitorPanel::on_select_printer(wxCommandEvent& event)
set_default();
update_all();
MachineObject *obj_ = dev->get_selected_machine();
if (obj_) {
obj_->last_cali_version = -1;
obj_->reset_pa_cali_history_result();
obj_->reset_pa_cali_result();
Sidebar &sidebar = GUI::wxGetApp().sidebar();
sidebar.update_sync_status(obj_);
sidebar.set_need_auto_sync_after_connect_printer(sidebar.need_auto_sync_extruder_list_after_connect_priner(obj_));
}
on_machine_selected(dev->get_selected_machine());
Layout();
}
void MonitorPanel::on_machine_selected(MachineObject* obj)
{
if (obj == nullptr)
return;
obj->last_cali_version = -1;
obj->reset_pa_cali_history_result();
obj->reset_pa_cali_result();
Sidebar& sidebar = GUI::wxGetApp().sidebar();
sidebar.update_sync_status(obj);
sidebar.set_need_auto_sync_after_connect_printer(sidebar.need_auto_sync_extruder_list_after_connect_priner(obj));
}
void MonitorPanel::on_printer_clicked(wxMouseEvent &event)
{
auto mouse_pos = ClientToScreen(event.GetPosition());
@@ -331,7 +341,8 @@ void MonitorPanel::on_size(wxSizeEvent &event)
void MonitorPanel::update_all()
{
if (!m_initialized)
// Every page exists once the last build step has run.
if (!built())
return;
NetworkAgent* m_agent = wxGetApp().getAgent();
@@ -412,16 +423,12 @@ void MonitorPanel::update_hms_tag()
bool MonitorPanel::Show(bool show)
{
#ifdef __APPLE__
// Notebook::InsertPage() hides every page it appends, so this also runs while MainFrame is
// still constructing, before GUI_App::mainframe is assigned. Same guard as Plater::Show().
if (wxGetApp().mainframe)
wxGetApp().mainframe->SetMinSize(wxGetApp().plater()->GetMinSize());
#endif
NetworkAgent* m_agent = wxGetApp().getAgent();
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
if (show) {
#ifdef __APPLE__
wxGetApp().mainframe->SetMinSize(wxGetApp().plater()->GetMinSize());
#endif
start_update();
update_network_version_footer();
@@ -448,7 +455,7 @@ bool MonitorPanel::Show(bool show)
void MonitorPanel::show_status(int status)
{
if (!m_initialized) return;
if (!built()) return;
if (last_status == status)return;
if ((last_status & (int)MonitorStatus::MONITOR_CONNECTING) != 0) {
NetworkAgent* agent = wxGetApp().getAgent();
+8 -6
View File
@@ -49,6 +49,7 @@
#include "slic3r/GUI/AmsWidgets.hpp"
#include "Widgets/SideTools.hpp"
#include "SelectMachinePop.hpp"
#include "Lazy.hpp"
namespace Slic3r {
namespace GUI {
@@ -72,16 +73,16 @@ public:
void msw_rescale();
};
class MonitorPanel : public wxPanel
class MonitorPanel : public wxPanel, public StagedBuild, public LazyInstance<MonitorPanel>
{
private:
Tabbook* m_tabpanel{ nullptr };
wxSizer* m_main_sizer{ nullptr };
StatusPanel* m_status_info_panel;
MediaFilePanel* m_media_file_panel;
UpgradePanel* m_upgrade_panel;
HMSPanel* m_hms_panel;
StatusPanel* m_status_info_panel{ nullptr };
MediaFilePanel* m_media_file_panel{ nullptr };
UpgradePanel* m_upgrade_panel{ nullptr };
HMSPanel* m_hms_panel{ nullptr };
/* side tools */
SideTools* m_side_tools{nullptr};
@@ -96,7 +97,6 @@ private:
wxBitmap m_arrow_img;
int last_status;
bool m_initialized { false };
bool update_flag{false};
wxTimer* m_refresh_timer = nullptr;
@@ -125,6 +125,8 @@ public:
StatusPanel* get_status_panel() {return m_status_info_panel;};
void select_machine(std::string machine_sn);
// Resets the selected printer's calibration results and syncs the sidebar, without the Device tab.
static void on_machine_selected(MachineObject* obj);
void on_timer(wxTimerEvent& event);
void on_select_printer(wxCommandEvent& event);
void on_printer_clicked(wxMouseEvent &event);
+3 -2
View File
@@ -21,8 +21,9 @@ MultiMachineItem::MultiMachineItem(wxWindow* parent, MachineObject* obj)
Bind(wxEVT_MOTION, &MultiMachineItem::OnMove, this);
Bind(EVT_MULTI_DEVICE_VIEW, [obj](auto& e) {
wxGetApp().mainframe->jump_to_monitor(obj->get_dev_id());
if (wxGetApp().mainframe->m_monitor->get_status_panel()->get_media_play_ctrl()) {
wxGetApp().mainframe->m_monitor->get_status_panel()->get_media_play_ctrl()->jump_to_play();
MonitorPanel* monitor = MonitorPanel::if_built();
if (monitor && monitor->get_status_panel()->get_media_play_ctrl()) {
monitor->get_status_panel()->get_media_play_ctrl()->jump_to_play();
}
});
wxGetApp().UpdateDarkUIWin(this);
+1
View File
@@ -11,6 +11,7 @@ namespace GUI {
MultiMachinePage::MultiMachinePage(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
: wxPanel(parent, id, pos, size, style)
{
SetBackgroundColour(*wxWHITE);
init_tabpanel();
m_main_sizer = new wxBoxSizer(wxHORIZONTAL);
m_main_sizer->Add(m_tabpanel, 1, wxEXPAND | wxLEFT, 0);
+2 -1
View File
@@ -7,6 +7,7 @@
#include "MultiTaskManagerPage.hpp"
#include "MultiMachineManagerPage.hpp"
#include "Tabbook.hpp"
#include "Lazy.hpp"
#include "wx/button.h"
@@ -19,7 +20,7 @@ namespace GUI {
#define PICK_LEFT_DEV_STATUS 250
#define PICK_DEVICE_MAX 6
class MultiMachinePage : public wxPanel
class MultiMachinePage : public wxPanel, public LazyInstance<MultiMachinePage>
{
private:
wxTimer* m_refresh_timer = nullptr;
+23 -5
View File
@@ -290,7 +290,7 @@ ParamsPanel::ParamsPanel( wxWindow* parent, wxWindowID id, const wxPoint& pos, c
m_compare_btn = new ScalableButton(m_top_panel, wxID_ANY, "compare", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, true);
m_compare_btn->SetToolTip(_L("Compare presets"));
m_compare_btn->Bind(wxEVT_BUTTON, ([](wxCommandEvent e) { wxGetApp().mainframe->diff_dialog.show(); }));
m_compare_btn->Bind(wxEVT_BUTTON, ([](wxCommandEvent e) { DiffPresetDialog::ensure()->show(); }));
m_setting_btn = new ScalableButton(m_top_panel, wxID_ANY, "table", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, true);
m_setting_btn->SetToolTip(_L("View all object's settings"));
@@ -550,16 +550,34 @@ void ParamsPanel::clear_page()
void ParamsPanel::OnActivate()
{
if (m_current_tab == NULL)
select_default_tab();
Tab* cur_tab = dynamic_cast<Tab *> (m_current_tab);
if (cur_tab)
cur_tab->OnActivate();
}
void ParamsPanel::select_default_tab()
{
//the first time
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": first time opened, set current tab to print");
// BBS: open/close tab
//m_current_tab = m_tab_print;
set_active_tab(m_tab_print ? m_tab_print : m_tab_filament);
}
Tab* cur_tab = dynamic_cast<Tab *> (m_current_tab);
if (cur_tab)
cur_tab->OnActivate();
bool ParamsPanel::SettingsPagePrebuild::built() const
{
Tab* tab = dynamic_cast<Tab*>(m_panel.m_current_tab);
return tab != nullptr && !tab->page_build_pending();
}
bool ParamsPanel::SettingsPagePrebuild::build_step()
{
if (m_panel.m_current_tab == nullptr) {
m_panel.select_default_tab();
return !built();
}
Tab* tab = dynamic_cast<Tab*>(m_panel.m_current_tab);
return tab != nullptr && tab->page_build_step();
}
void ParamsPanel::OnToggled(wxCommandEvent& event)
+21
View File
@@ -30,6 +30,7 @@
#include "wxExtensions.hpp"
#include "GUI_Utils.hpp"
#include "Widgets/Button.hpp"
#include "Lazy.hpp"
class ModeSwitchButton;
class SwitchButton;
@@ -121,6 +122,22 @@ class ParamsPanel : public wxPanel
wxPanel* m_current_tab { nullptr };
// Builds the selected page's option groups at idle; while no tab is selected yet,
// its first unit selects the default one.
class SettingsPagePrebuild : public LazyBase
{
public:
explicit SettingsPagePrebuild(ParamsPanel& panel) : m_panel(panel) {}
const std::string& name() const override { return m_name; }
bool built() const override;
bool build_step() override;
int prebuild_order() const override { return 0; }
private:
ParamsPanel& m_panel;
std::string m_name{ "settings_page" };
} m_settings_page_prebuild{ *this };
bool m_has_object_config { false };
struct Highlighter
@@ -149,6 +166,10 @@ class ParamsPanel : public wxPanel
//clear the right page
void clear_page();
void OnActivate();
// The print tab, or the filament tab without one.
void select_default_tab();
// The settings page's prebuild task.
LazyBase& settings_page_prebuild() { return m_settings_page_prebuild; }
void set_active_tab(wxPanel*tab);
bool is_active_and_shown_tab(wxPanel*tab);
void update_mode();
+4 -1
View File
@@ -3555,8 +3555,11 @@ void PartPlate::render(const Transform3d& view_matrix, const Transform3d& projec
shader->stop_using();
}
if (wxGetApp().show_plate_gridlines() && show_grid)
if (wxGetApp().show_plate_gridlines() && show_grid) {
glsafe(::glDepthMask(bottom ? GL_TRUE : GL_FALSE));
render_grid(bottom);
glsafe(::glDepthMask(GL_TRUE));
}
if (!hide_chrome && !bottom && m_selected && !force_background_color) {
if (m_partplate_list)
+25 -23
View File
@@ -7033,7 +7033,7 @@ struct Plater::priv
void remove(size_t obj_idx);
bool delete_object_from_model(size_t obj_idx, bool refresh_immediately = true); //BBS
void delete_all_objects_from_model();
void reset(bool apply_presets_change = false);
void reset(bool apply_presets_change = false, bool reload_presets = true);
void center_selection();
void drop_selection();
void mirror(Axis axis);
@@ -7891,7 +7891,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
if (this->q->get_project_filename().IsEmpty() && this->q->is_empty_project()) {
int skip_confirm = e.GetInt();
this->q->new_project(skip_confirm, true);
// Skips the preset reload; trigger_restore_project()'s callers load the presets first.
this->q->new_project(skip_confirm, true, wxString(), false);
}
});
//wxPostEvent(this->q, wxCommandEvent{EVT_RESTORE_PROJECT});
@@ -10356,7 +10357,7 @@ void Plater::priv::delete_all_objects_from_model()
model.plates_custom_gcodes.clear();
}
void Plater::priv::reset(bool apply_presets_change)
void Plater::priv::reset(bool apply_presets_change, bool reload_presets)
{
// TakeSnapshot below and load_current_presets() further down each re-evaluate the
// aggregate dirty flag against a baseline that hasn't been reset yet, so they can toggle
@@ -10411,8 +10412,8 @@ void Plater::priv::reset(bool apply_presets_change)
// Same reason, one level up: the Design tab keeps the editable document, not the Model, so
// clearing the recipe alone leaves the tab showing the previous project's feature tree —
// and its next edit syncs that tree straight back into the new project.
if (wxGetApp().mainframe != nullptr && wxGetApp().mainframe->m_design_panel != nullptr)
wxGetApp().mainframe->m_design_panel->clear_document();
if (DesignPanel* design = DesignPanel::if_built())
design->clear_document();
#endif
assemble_view->get_canvas3d()->reset_explosion_ratio();
update();
@@ -10431,7 +10432,7 @@ void Plater::priv::reset(bool apply_presets_change)
wxGetApp().preset_bundle->reset_project_embedded_presets();
if (apply_presets_change)
wxGetApp().apply_keeped_preset_modifications();
else
else if (reload_presets)
wxGetApp().load_current_presets(false, false);
//BBS
@@ -13183,17 +13184,17 @@ void Plater::priv::on_tab_selection_changing(wxBookCtrlEvent& e)
// Pointer test, not a name lookup: in printer-agents mode this page is TAB_ID_MONITOR_WEB
// while the native Device tab holds TAB_ID_MONITOR, and in legacy-web mode it holds
// TAB_ID_MONITOR itself.
const bool selecting_web_device_tab = main_frame->m_printer_view &&
main_frame->m_tabpanel->GetPage(new_sel) == main_frame->m_printer_view;
const bool selecting_web_device_tab = main_frame->m_printer_view_page &&
main_frame->m_tabpanel->GetPage(new_sel) == main_frame->m_printer_view_page;
if (selecting_web_device_tab) {
// Use the selected discovered machine when the preset has no host.
main_frame->load_printer_url();
} else if (new_name == TAB_ID_MONITOR && wxGetApp().preset_bundle != nullptr) {
auto cfg = wxGetApp().preset_bundle->printers.get_edited_preset().config;
wxString url = from_u8(PrintHost::get_print_host_webui(&cfg));
if (main_frame->m_printer_view && url.empty()) {
if (PrinterWebView* view = PrinterWebView::if_built(); view != nullptr && url.empty()) {
// It's missing_connection page, reload so that we can replay the gif image
main_frame->m_printer_view->reload();
view->reload();
}
}
}
@@ -13924,8 +13925,8 @@ void Plater::priv::unbind_canvas_event_handlers()
// The Design tab's viewport is a fourth GLCanvas3D on the same shared GL context, owned by
// MainFrame rather than by us — same reach as reset() uses for clear_document(). Null until
// the tab has been opened once, so most sessions skip it.
if (wxGetApp().mainframe != nullptr && wxGetApp().mainframe->m_design_panel != nullptr)
wxGetApp().mainframe->m_design_panel->unbind_canvas_event_handlers();
if (DesignPanel* design = DesignPanel::if_built())
design->unbind_canvas_event_handlers();
#endif
}
@@ -13938,8 +13939,8 @@ void Plater::priv::reset_canvas_volumes()
preview->get_canvas3d()->reset_volumes();
#ifdef SLIC3R_CAD
if (wxGetApp().mainframe != nullptr && wxGetApp().mainframe->m_design_panel != nullptr)
wxGetApp().mainframe->m_design_panel->reset_canvas_volumes();
if (DesignPanel* design = DesignPanel::if_built())
design->reset_canvas_volumes();
#endif
}
@@ -15351,7 +15352,7 @@ Print& Plater::fff_print() { return p->fff_print; }
const SLAPrint& Plater::sla_print() const { return p->sla_print; }
SLAPrint& Plater::sla_print() { return p->sla_print; }
int Plater::new_project(bool skip_confirm, bool silent, const wxString& project_name)
int Plater::new_project(bool skip_confirm, bool silent, const wxString& project_name, bool reload_presets)
{
model().calib_pa_pattern.reset(nullptr);
model().plates_custom_gcodes.clear();
@@ -15399,7 +15400,7 @@ int Plater::new_project(bool skip_confirm, bool silent, const wxString& project_
// completes, so hold notifications until then to avoid firing on transient flips.
ProjectDirtyStateManager::NotificationSuppressor dirty_notify_suppressor(p->dirty_state);
reset(transfer_preset_changes);
reset(transfer_preset_changes, reload_presets);
reset_project_dirty_after_save();
reset_project_dirty_initial_presets();
wxGetApp().update_saved_preset_from_current_preset();
@@ -17984,7 +17985,7 @@ void Plater::deselect_all() { p->deselect_all(); }
void Plater::exit_gizmo() { p->exit_gizmo(); }
void Plater::remove(size_t obj_idx) { p->remove(obj_idx); }
void Plater::reset(bool apply_presets_change) { p->reset(apply_presets_change); }
void Plater::reset(bool apply_presets_change, bool reload_presets) { p->reset(apply_presets_change, reload_presets); }
void Plater::reset_with_confirm()
{
if (p->model.objects.empty() || MessageDialog(static_cast<wxWindow *>(this), _L("All objects will be removed, continue?"),
@@ -20001,7 +20002,7 @@ int Plater::export_config_3mf(int plate_idx, Export3mfProgressFn proFn)
void Plater::send_calibration_job_finished(wxCommandEvent & evt)
{
p->main_frame->request_select_tab(TAB_ID_CALIBRATION);
auto calibration_panel = p->main_frame->m_calibration;
CalibrationPanel* calibration_panel = CalibrationPanel::ensure();
if (calibration_panel) {
auto curr_wizard = static_cast<CalibrationWizard*>(calibration_panel->get_tabpanel()->GetPage(evt.GetInt()));
wxCommandEvent event(EVT_CALIBRATION_JOB_FINISHED);
@@ -20033,8 +20034,8 @@ void Plater::print_job_finished(wxCommandEvent &evt)
dev->set_selected_machine(evt.GetString().ToStdString());
p->main_frame->request_select_tab(TAB_ID_MONITOR);
//jump to monitor and select device status panel
MonitorPanel* curr_monitor = p->main_frame->m_monitor;
// Selects the status page on a built Device tab; one built by the switch starts there.
MonitorPanel* curr_monitor = MonitorPanel::if_built();
if(curr_monitor)
curr_monitor->get_tabpanel()->ChangeSelection(MonitorPanel::PrinterTab::PT_STATUS);
}
@@ -20774,8 +20775,8 @@ void Plater::update_print_error_info(int code, std::string msg, std::string extr
if (p->m_send_to_sdcard_dlg) {
p->m_send_to_sdcard_dlg->update_print_error_info(code, msg, extra);
}
if (p->main_frame->m_calibration)
p->main_frame->m_calibration->update_print_error_info(code, msg, extra);
if (CalibrationPanel* calibration = CalibrationPanel::if_built())
calibration->update_print_error_info(code, msg, extra);
}
wxString Plater::get_project_filename(const wxString& extension) const
@@ -21068,7 +21069,8 @@ void Plater::pop_warning_and_go_to_device_page(wxString printer_name, PrinterWar
{
printer_name.Replace("Bambu Lab", "", false);
wxString content;
bool device_page = (wxGetApp().mainframe == nullptr) && (wxGetApp().mainframe->m_monitor->IsShown());
MainFrame* frame = wxGetApp().mainframe;
bool device_page = frame != nullptr && frame->m_monitor_page->in_book();
if (type == PrinterWarningType::NOT_CONNECTED) {
if (device_page) {
content = wxString::Format(_L("Printer not connected. Please go to the device page to connect %s before syncing."),
+2 -2
View File
@@ -339,7 +339,7 @@ public:
// Helper: returns config indices where filament_is_mixed == false
std::vector<size_t> physical_filament_config_indices() const;
int new_project(bool skip_confirm = false, bool silent = false, const wxString& project_name = wxString());
int new_project(bool skip_confirm = false, bool silent = false, const wxString& project_name = wxString(), bool reload_presets = true);
// BBS: save & backup
void load_project(wxString const & filename = "", wxString const & originfile = "-");
int save_project(bool saveAs = false);
@@ -499,7 +499,7 @@ public:
void deselect_all();
void exit_gizmo();
void remove(size_t obj_idx);
void reset(bool apply_presets_change = false);
void reset(bool apply_presets_change = false, bool reload_presets = true);
void reset_with_confirm();
//BBS: return int for various result
int close_with_confirm(std::function<bool(bool yes_or_no)> second_check = nullptr); // BBS close project
+89
View File
@@ -0,0 +1,89 @@
#pragma once
#include <algorithm>
#include <functional>
#include <string>
#include <vector>
#include "Lazy.hpp"
namespace Slic3r { namespace GUI {
// Holders to build at idle, ordered by prebuild_order(), run one slice at a time. The
// holders outlive the queue. Main thread only.
class PrebuildQueue
{
public:
struct Slice
{
std::string name; // the task that ran; empty when nothing was pending
int units{ 0 }; // units run
long long ms{ 0 }; // time spent
bool completed{ false }; // the task has no more units
bool remaining{ false }; // some task is still pending
};
// Lower order runs first, equal order in the order added.
void add(LazyBase& task)
{
auto after = std::find_if(m_tasks.begin(), m_tasks.end(), [&](const LazyBase* t) { return t->prebuild_order() > task.prebuild_order(); });
m_tasks.insert(after, &task);
}
void clear() { m_tasks.clear(); }
bool pending() const
{
return std::any_of(m_tasks.begin(), m_tasks.end(), [](const LazyBase* t) { return t->pending(); });
}
// The task names in queue order, comma separated.
std::string names() const
{
std::string out;
for (const LazyBase* t : m_tasks)
out += (out.empty() ? "" : ", ") + t->name();
return out;
}
// Runs units of the first pending task until it completes, budget_ms of now_ms() have
// passed, or interrupt() is true after a unit; on_unit gets the task name and each
// unit's duration. A task with no work is passed over and stays in the queue, so one
// whose work returns is pending again.
Slice run_slice(int budget_ms,
const std::function<long long()>& now_ms,
const std::function<bool()>& interrupt,
const std::function<void(const std::string&, long long)>& on_unit = {})
{
Slice slice;
auto next = std::find_if(m_tasks.begin(), m_tasks.end(), [](const LazyBase* t) { return t->pending(); });
if (next == m_tasks.end())
return slice;
// The pointer is copied out, since a unit may add tasks and reallocate m_tasks.
LazyBase* const task = *next;
slice.name = task->name();
const long long started = now_ms();
for (;;) {
const long long unit_started = now_ms();
const bool more = task->build_step();
const long long now = now_ms();
++slice.units;
if (on_unit)
on_unit(slice.name, now - unit_started);
slice.ms = now - started;
if (!more) {
slice.completed = true;
break;
}
if (slice.ms >= budget_ms || interrupt())
break;
}
slice.remaining = pending();
return slice;
}
private:
std::vector<LazyBase*> m_tasks;
};
}} // namespace Slic3r::GUI
+4 -5
View File
@@ -1044,8 +1044,8 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too
if (m_bambu_cloud_checkbox) m_bambu_cloud_checkbox->Enable(!enabled);
}
else if (param == "hide_login_side_panel") {
if (wxGetApp().mainframe && wxGetApp().mainframe->m_webview) {
wxGetApp().mainframe->m_webview->SendCloudProvidersInfo();
if (WebViewPanel* home = WebViewPanel::if_built()) {
home->SendCloudProvidersInfo();
}
}
// ORCA: apply the preview dimming change immediately to the currently loaded preview
@@ -1254,9 +1254,8 @@ wxBoxSizer *PreferencesDialog::create_item_bambu_cloud(wxString title, wxString
app_config->save();
// Update homepage visibility immediately
auto *mainframe = wxGetApp().mainframe;
if (mainframe && mainframe->m_webview)
mainframe->m_webview->SendCloudProvidersInfo();
if (WebViewPanel* home = WebViewPanel::if_built())
home->SendCloudProvidersInfo();
});
m_sizer->Add(cb, 0, wxALIGN_CENTER);
+2 -1
View File
@@ -26,6 +26,7 @@
#include "wx/textctrl.h"
#include <wx/timer.h>
#include <memory>
#include "Lazy.hpp"
namespace Slic3r {
@@ -34,7 +35,7 @@ namespace GUI {
class PrinterWebViewHandler;
class PrinterWebView : public wxPanel {
class PrinterWebView : public wxPanel, public LazyInstance<PrinterWebView> {
public:
PrinterWebView(wxWindow *parent);
virtual ~PrinterWebView();
+2
View File
@@ -44,6 +44,7 @@ const std::vector<std::string> license_list = {
ProjectPanel::ProjectPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style) : wxPanel(parent, id, pos, size, style)
{
SetBackgroundColour(*wxWHITE);
m_project_home_url = wxString::Format("file://%s/web/model/index.html", from_u8(resources_dir()));
wxString strlang = wxGetApp().current_language_code_safe();
if (strlang != "")
@@ -67,6 +68,7 @@ ProjectPanel::ProjectPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos,
m_auxiliary = new AuxiliaryPanel(this);
m_auxiliary->Hide();
main_sizer->Add(m_auxiliary, wxSizerFlags().Expand().Proportion(1));
add_build_steps_of(*m_auxiliary);
Bind(EVT_AUXILIARY_DONE, [this](wxCommandEvent& e) { update_model_data();});
SetSizer(main_sizer);
+2 -2
View File
@@ -35,6 +35,7 @@
#include "libslic3r/ProjectTask.hpp"
#include "wxExtensions.hpp"
#include "Auxiliary.hpp"
#include "Lazy.hpp"
#define AUFILE_GREY700 wxColour(107, 107, 107)
#define AUFILE_GREY500 wxColour(158, 158, 158)
@@ -59,7 +60,7 @@ struct project_file{
std::string size;
};
class ProjectPanel : public wxPanel
class ProjectPanel : public wxPanel, public StagedBuild, public LazyInstance<ProjectPanel>
{
private:
std::atomic<bool> m_web_init_completed{false};
@@ -90,7 +91,6 @@ public:
void msw_rescale();
void update_model_data();
void clear_model_info();
void init_auxiliary() { m_auxiliary->init_auxiliary(); }
bool Show(bool show);
void OnScriptMessage(wxWebViewEvent& evt);
+27 -6
View File
@@ -99,8 +99,11 @@ void SceneRaycaster::remove_raycaster(std::shared_ptr<SceneRaycasterItem> item)
}
}
SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Camera& camera, const ClippingPlane* clipping_plane) const
SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Camera& camera,
const ClippingPlane* clipping_plane, SceneRaycaster::EHitMode mode) const
{
// Orca: Picking may favor an already selected volume for interaction, while camera
// navigation must always use the geometrically closest visible scene surface.
// helper class used to return currently selected volume as hit when overlapping with other volumes
// to allow the user to click and drag on a selected volume
class VolumeKeeper
@@ -110,7 +113,11 @@ SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Came
bool m_selected_volume_already_found{ false };
public:
VolumeKeeper() {
explicit VolumeKeeper(bool enabled) {
// Orca: Disable selected-volume bias for navigation raycasts.
if (!enabled)
return;
const Selection& selection = wxGetApp().plater()->get_selection();
if (selection.is_single_volume() || selection.is_single_modifier()) {
const GLVolume* volume = selection.get_first_volume();
@@ -135,7 +142,7 @@ SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Came
}
};
VolumeKeeper volume_keeper;
VolumeKeeper volume_keeper(mode == EHitMode::Picking);
double closest_hit_squared_distance = std::numeric_limits<double>::max();
auto is_closest = [&closest_hit_squared_distance, &volume_keeper](const Camera& camera, const Vec3f& hit) {
@@ -154,7 +161,7 @@ SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Came
HitResult ret;
auto test_raycasters = [this, is_closest, clipping_plane, &volume_keeper](EType type, const Vec2d& mouse_pos, const Camera& camera, HitResult& ret) {
auto test_raycasters = [this, is_closest, clipping_plane, mode, &volume_keeper](EType type, const Vec2d& mouse_pos, const Camera& camera, HitResult& ret) {
const ClippingPlane* clip_plane = (clipping_plane != nullptr && type == EType::Volume) ? clipping_plane : nullptr;
const std::vector<std::shared_ptr<SceneRaycasterItem>>* raycasters = get_raycasters(type);
const Vec3f camera_forward = camera.get_dir_forward().cast<float>();
@@ -163,12 +170,22 @@ SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Came
if (!item->is_active())
continue;
// Each plate's component 0 is its surface; the remaining Bed IDs are controls.
// Keep controls clickable, but do not use them as camera-pan anchors.
if (mode != EHitMode::Picking && type == EType::Bed &&
decode_id(type, item->get_id()) % PartPlate::GRABBER_COUNT != 0)
continue;
current_hit.raycaster_id = item->get_id();
const Transform3d& trafo = item->get_transform();
if (item->get_raycaster()->closest_hit(mouse_pos, trafo, camera, current_hit.position, current_hit.normal, clip_plane)) {
current_hit.position = (trafo * current_hit.position.cast<double>()).cast<float>();
current_hit.normal = (trafo.matrix().block(0, 0, 3, 3).inverse().transpose() * current_hit.normal.cast<double>()).normalized().cast<float>();
if (item->use_back_faces() || current_hit.normal.dot(camera_forward) < 0.0f) {
// Orca: Perspective rays away from the viewport center are not parallel to camera_forward.
// Keep picking's legacy policy, but accept every front-facing navigation surface.
const Vec3f view_direction = mode != EHitMode::Picking && camera.get_type() == Camera::EType::Perspective ?
Vec3f((current_hit.position.cast<double>() - camera.get_position()).cast<float>()) : camera_forward;
if (item->use_back_faces() || current_hit.normal.dot(view_direction) < 0.0f) {
if (is_closest(camera, current_hit.position)) {
if (volume_keeper.is_active()) {
if (volume_keeper.check_hit_result(current_hit))
@@ -182,14 +199,18 @@ SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Came
}
};
// Orca: Gizmo geometry is an interaction target, not a valid depth anchor for camera movement.
if (mode == EHitMode::Picking) {
if (!m_gizmos.empty())
test_raycasters(EType::Gizmo, mouse_pos, camera, ret);
if (!m_fallback_gizmos.empty() && !ret.is_valid())
test_raycasters(EType::FallbackGizmo, mouse_pos, camera, ret);
}
if (!m_gizmos_on_top || !ret.is_valid()) {
if (camera.is_looking_downward() && !m_bed.empty())
// Orca: In perspective the bottom of the viewport can see the bed even at a horizontal view.
if ((mode == EHitMode::SceneOnly || (mode == EHitMode::Picking && camera.is_looking_downward())) && !m_bed.empty())
test_raycasters(EType::Bed, mouse_pos, camera, ret);
if (!m_volumes.empty())
test_raycasters(EType::Volume, mouse_pos, camera, ret);
+11 -1
View File
@@ -57,6 +57,15 @@ public:
FallbackGizmo = 2000000
};
// Orca: Navigation needs the closest visible scene surface, without picking-specific
// gizmo and selected-volume priority.
enum class EHitMode : unsigned char
{
Picking,
SceneOnly,
VolumesOnly // Navigation when the bed is hidden.
};
struct HitResult
{
EType type{ EType::None };
@@ -97,7 +106,8 @@ public:
void set_gizmos_on_top(bool value) { m_gizmos_on_top = value; }
HitResult hit(const Vec2d& mouse_pos, const Camera& camera, const ClippingPlane* clipping_plane = nullptr) const;
HitResult hit(const Vec2d& mouse_pos, const Camera& camera, const ClippingPlane* clipping_plane = nullptr,
EHitMode mode = EHitMode::Picking) const;
#if ENABLE_RAYCAST_PICKING_DEBUG
void render_hit(const Camera& camera);
+1 -1
View File
@@ -3470,7 +3470,7 @@ void SelectMachineDialog::navigate_to_timelapse_page()
main_frame->jump_to_monitor();
// then switch to Storage (Media) tab inside Monitor
auto* monitor = dynamic_cast<MonitorPanel*>(main_frame->m_monitor);
MonitorPanel* monitor = MonitorPanel::if_built();
if (monitor) {
auto* tabpanel = monitor->get_tabpanel();
if (tabpanel) {
+60
View File
@@ -0,0 +1,60 @@
#pragma once
#include <algorithm>
#include <functional>
#include <vector>
namespace Slic3r { namespace GUI {
// A panel whose constructor builds the skeleton and queues the rest as steps, run in order
// on the main thread, one at a time.
class StagedBuild
{
public:
virtual ~StagedBuild() = default;
bool built() const
{
return m_next_step == m_steps.size() &&
std::all_of(m_children.begin(), m_children.end(), [](const StagedBuild* child) { return child->built(); });
}
// Runs one step and returns true while more remain. Own steps first, then whatever a
// child queued after its steps were forwarded.
bool build_step()
{
if (m_next_step < m_steps.size()) {
// Copied out, since a step may queue more steps and reallocate m_steps.
auto step = std::move(m_steps[m_next_step++]);
step();
} else if (StagedBuild* child = unfinished_child()) {
child->build_step();
}
return !built();
}
protected:
void add_build_step(std::function<void()> step) { m_steps.push_back(std::move(step)); }
// Queues one step per step the child has now, and keeps the child so that built() waits
// for any it queues later.
void add_build_steps_of(StagedBuild& child)
{
m_children.push_back(&child);
for (size_t i = child.m_next_step; i < child.m_steps.size(); ++i)
add_build_step([&child] { child.build_step(); });
}
private:
StagedBuild* unfinished_child() const
{
auto it = std::find_if(m_children.begin(), m_children.end(), [](const StagedBuild* child) { return !child->built(); });
return it == m_children.end() ? nullptr : *it;
}
std::vector<std::function<void()>> m_steps;
std::vector<StagedBuild*> m_children;
size_t m_next_step{ 0 };
};
}} // namespace Slic3r::GUI
+46 -13
View File
@@ -1328,6 +1328,8 @@ StatusBasePanel::StatusBasePanel(wxWindow *parent, wxWindowID id, const wxPoint
wxBoxSizer *bSizer_left = new wxBoxSizer(wxVERTICAL);
// The sizers are nested here so each step only appends to its own.
add_build_step([this, bSizer_left] {
auto m_monitoring_sizer = create_monitoring_page();
bSizer_left->Add(m_monitoring_sizer, 1, wxEXPAND | wxALL, 0);
@@ -1340,6 +1342,7 @@ StatusBasePanel::StatusBasePanel(wxWindow *parent, wxWindowID id, const wxPoint
m_project_task_panel = new PrintingTaskPanel(this, PrintingTaskType::PRINGINT);
m_project_task_panel->init_bitmaps();
m_monitoring_sizer->Add(m_project_task_panel, 0, wxALL | wxEXPAND , 0);
});
// auto m_panel_separotor2 = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
// m_panel_separotor2->SetBackgroundColour(STATUS_PANEL_BG);
@@ -1359,8 +1362,10 @@ StatusBasePanel::StatusBasePanel(wxWindow *parent, wxWindowID id, const wxPoint
m_machine_ctrl_panel->SetDoubleBuffered(true);
auto m_machine_control = create_machine_control_page(m_machine_ctrl_panel);
m_machine_ctrl_panel->SetSizer(m_machine_control);
add_build_step([this, m_machine_control] {
m_machine_ctrl_panel->Layout();
m_machine_control->Fit(m_machine_ctrl_panel);
});
bSizer_status_below->Add(m_machine_ctrl_panel, 0, wxALL, 0);
@@ -1375,8 +1380,11 @@ StatusBasePanel::StatusBasePanel(wxWindow *parent, wxWindowID id, const wxPoint
m_panel_separotor_bottom->SetBackgroundColour(STATUS_PANEL_BG);
bSizer_status->Add(m_panel_separotor_bottom, 0, wxEXPAND | wxALL, 0);
this->SetSizerAndFit(bSizer_status);
this->SetSizer(bSizer_status);
add_build_step([this, bSizer_status] {
bSizer_status->SetSizeHints(this);
this->Layout();
});
}
StatusBasePanel::~StatusBasePanel()
@@ -1614,16 +1622,24 @@ wxBoxSizer *StatusBasePanel::create_machine_control_page(wxWindow *parent)
wxBoxSizer *bSizer_control = new wxBoxSizer(wxVERTICAL);
auto temp_axis_ctrl_sizer = create_temp_axis_group(parent);
auto m_filament_load_sizer = create_filament_group(parent);
// The slot sizers keep each group's place in bSizer_control.
wxBoxSizer *temp_axis_slot = new wxBoxSizer(wxVERTICAL);
wxBoxSizer *filament_slot = new wxBoxSizer(wxVERTICAL);
/* ams control box or live nozzle-rack panel (rack printers switch between the two) */
wxSizer *ams_rack_sizer = new wxBoxSizer(wxHORIZONTAL);
ams_rack_sizer->Add(create_ams_group(parent), 0, wxEXPAND | wxLEFT);
add_build_step([this, parent, temp_axis_slot, filament_slot] {
temp_axis_slot->Add(create_temp_axis_group(parent), 0, wxEXPAND);
filament_slot->Add(create_filament_group(parent), 0, wxEXPAND);
});
add_build_step([this, parent, ams_rack_sizer] {
ams_rack_sizer->Add(create_ams_group(parent), 0, wxEXPAND | wxLEFT);
});
add_build_step([this, parent, ams_rack_sizer] {
m_panel_nozzle_rack = new wgtDeviceNozzleRack(parent);
m_panel_nozzle_rack->Show(false);
ams_rack_sizer->Add(m_panel_nozzle_rack, 0, wxEXPAND | wxLEFT);
});
m_ams_rack_switch = new SwitchBoard(parent, _L("Filament"), _L("Hotends"), wxSize(FromDIP(126), FromDIP(26)));
m_ams_rack_switch->updateState("left");
@@ -1631,12 +1647,12 @@ wxBoxSizer *StatusBasePanel::create_machine_control_page(wxWindow *parent)
m_ams_rack_switch->Bind(wxCUSTOMEVT_SWITCH_POS, &StatusBasePanel::on_ams_rack_switch, this);
bSizer_control->Add(0, 0, 0, wxTOP, FromDIP(8));
bSizer_control->Add(temp_axis_ctrl_sizer, 0, wxALIGN_CENTER|wxLEFT|wxRIGHT, FromDIP(8));
bSizer_control->Add(temp_axis_slot, 0, wxALIGN_CENTER|wxLEFT|wxRIGHT, FromDIP(8));
bSizer_control->Add(m_ams_rack_switch, 0, wxALIGN_CENTRE|wxTOP, FromDIP(6));
bSizer_control->Add(0, 0, 0, wxTOP, FromDIP(6));
bSizer_control->Add(ams_rack_sizer, 0, wxALIGN_CENTER|wxLEFT|wxRIGHT, FromDIP(8));
bSizer_control->Add(0, 0, 0, wxTOP, FromDIP(6));
bSizer_control->Add(m_filament_load_sizer, 0, wxALIGN_CENTER|wxLEFT|wxRIGHT, FromDIP(8));
bSizer_control->Add(filament_slot, 0, wxALIGN_CENTER|wxLEFT|wxRIGHT, FromDIP(8));
bSizer_control->Add(0, 0, 0, wxTOP, FromDIP(4));
bSizer_right->Add(bSizer_control, 1, wxEXPAND | wxALL, 0);
@@ -2228,8 +2244,10 @@ void StatusBasePanel::expand_filament_loading(wxMouseEvent& e)
m_filament_step->Show(tag_show);
Layout();
Fit();
wxGetApp().mainframe->m_monitor->get_status_panel()->Layout();
wxGetApp().mainframe->m_monitor->Layout();
if (MonitorPanel* monitor = MonitorPanel::if_built()) {
monitor->get_status_panel()->Layout();
monitor->Layout();
}
}
void StatusBasePanel::show_ams_group(bool show)
@@ -2240,7 +2258,8 @@ void StatusBasePanel::show_ams_group(bool show)
m_ams_control->Fit();
Layout();
Fit();
wxGetApp().mainframe->m_monitor->Layout();
if (MonitorPanel* monitor = MonitorPanel::if_built())
monitor->Layout();
}
// On rack printers, don't clobber the rack view when the user has the switch on "Hotends".
@@ -2253,7 +2272,8 @@ void StatusBasePanel::show_ams_group(bool show)
m_ams_control->Fit();
Layout();
Fit();
wxGetApp().mainframe->m_monitor->Layout();
if (MonitorPanel* monitor = MonitorPanel::if_built())
monitor->Layout();
}
}
@@ -2277,8 +2297,10 @@ void StatusBasePanel::show_filament_load_group(bool show)
Layout();
Fit();
wxGetApp().mainframe->m_monitor->get_status_panel()->Layout();
wxGetApp().mainframe->m_monitor->Layout();
if (MonitorPanel* monitor = MonitorPanel::if_built()) {
monitor->get_status_panel()->Layout();
monitor->Layout();
}
}
}
@@ -2403,6 +2425,12 @@ void StatusPanel::update_camera_state(MachineObject* obj)
StatusPanel::StatusPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style, const wxString &name)
: StatusBasePanel(parent, id, pos, size, style)
{
// Wires the controls the base class builds in steps, so it is the last step.
add_build_step([this] { wire_controls(); });
}
void StatusPanel::wire_controls()
{
init_scaled_buttons();
m_buttons.push_back(m_bpButton_z_10);
@@ -2514,6 +2542,8 @@ StatusPanel::StatusPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, co
StatusPanel::~StatusPanel()
{
// The controls only exist once the last build step has run.
if (built()) {
// Disconnect Events
m_project_task_panel->get_bitmap_thumbnail()->Disconnect(wxEVT_LEFT_DOWN, wxMouseEventHandler(StatusPanel::refresh_thumbnail_webrequest), NULL, this);
m_project_task_panel->get_partskip_button()->Disconnect(wxEVT_LEFT_DOWN, wxCommandEventHandler(StatusPanel::on_subtask_partskip), NULL, this);
@@ -2555,6 +2585,7 @@ StatusPanel::~StatusPanel()
m_options_btn->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_show_print_options), NULL, this);
m_safety_btn->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_show_safety_options), NULL, this);
m_parts_btn->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_show_parts_options), NULL, this);
}
// remove warning dialogs
if (abort_dlg != nullptr)
@@ -5216,6 +5247,8 @@ void StatusPanel::set_default()
m_filament_step->Hide();
error_info_reset();
#ifndef __WXGTK__
// Also reached while the panel is built off screen.
if (IsShownOnScreen())
SetFocus();
#endif
}
+5 -3
View File
@@ -36,6 +36,7 @@
#include "HMS.hpp"
#include "PartSkipDialog.hpp"
#include "DeviceErrorDialog.hpp"
#include "StagedBuild.hpp"
class StepIndicator;
@@ -375,7 +376,7 @@ public:
void paint(wxPaintEvent&);
};
class StatusBasePanel : public wxScrolledWindow
class StatusBasePanel : public wxScrolledWindow, public StagedBuild
{
protected:
wxBitmap m_item_placeholder;
@@ -443,7 +444,7 @@ protected:
wxMediaCtrl3 * m_media_ctrl;
MediaPlayCtrl * m_media_play_ctrl;
MediaPlayCtrl * m_media_play_ctrl{nullptr};
Label * m_staticText_printing;
wxStaticBitmap *m_bitmap_thumbnail;
@@ -629,6 +630,7 @@ class StatusPanel : public StatusBasePanel
{
private:
friend class MonitorPanel;
void wire_controls();
protected:
std::shared_ptr<SliceInfoPopup> m_slice_info_popup;
@@ -679,7 +681,7 @@ protected:
std::map<std::string, std::string> m_print_connect_types;
std::vector<Button *> m_buttons;
int last_status;
ScoreData *m_score_data;
ScoreData *m_score_data = nullptr;
wxBitmap* calib_bitmap = nullptr;
CalibMode m_calib_mode;
CalibrationMethod m_calib_method;
+56 -17
View File
@@ -804,6 +804,10 @@ void Tab::OnActivate()
}
#endif
// The OnActivate() that shows the tab builds the page.
if (wxGetApp().mainframe != nullptr && !wxGetApp().mainframe->is_active_and_shown_tab(m_parent))
return;
// BBS: select on first active
if (!m_active_page)
restore_last_select_item();
@@ -7159,6 +7163,16 @@ void Tab::restore_last_select_item()
m_tabctrl->SelectItem(item);
}
bool Tab::page_build_pending() const
{
return m_active_page != nullptr && m_active_page->build_pending();
}
bool Tab::page_build_step()
{
return m_active_page != nullptr && m_active_page->build_step(m_mode);
}
void Tab::update_description_lines()
{
if (m_active_page && m_active_page->title() == "Dependencies" && m_parent_preset_description_line)
@@ -7375,7 +7389,7 @@ void Tab::OnKeyDown(wxKeyEvent& event)
void Tab::compare_preset()
{
wxGetApp().mainframe->diff_dialog.show(m_type);
DiffPresetDialog::ensure()->show(m_type);
}
void Tab::transfer_options(const std::string &name_from, const std::string &name_to, std::vector<std::string> options)
@@ -7543,8 +7557,10 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach, bool save_to_proje
wxGetApp().get_tab(preset_type)->update_tab_ui();
}
// update preset comboboxes in DiffPresetDlg
wxGetApp().mainframe->diff_dialog.update_presets(m_type);
// show() reloads the presets, so only a visible Compare dialog needs updating.
DiffPresetDialog* diff_dialog = DiffPresetDialog::if_built();
if (diff_dialog != nullptr && diff_dialog->IsShown())
diff_dialog->update_presets(m_type);
}
// Called for a currently selected preset.
@@ -8604,20 +8620,8 @@ void Page::activate(ConfigOptionMode mode, std::function<void()> throw_if_cancel
#else
//m_vsizer->AddSpacer(10);
#endif
#if HIDE_FIRST_SPLIT_LINE
// BBS: no line spliter for first group
bool first = true;
#endif
for (auto group : m_optgroups) {
if (!group->activate(throw_if_canceled))
continue;
m_vsizer->Add(group->sizer, 0, wxEXPAND | (group->is_legend_line() ? (wxLEFT|wxTOP) : wxALL), m_parent->FromDIP(5)); // ORCA use less margin on parameters section
group->update_visibility(mode);
#if HIDE_FIRST_SPLIT_LINE
if (first) group->stb->Hide();
first = false;
#endif
group->reload_config();
for (size_t i = 0; i < m_optgroups.size(); ++i) {
activate_group(i, mode, throw_if_canceled);
throw_if_canceled();
}
@@ -8636,6 +8640,41 @@ void Page::activate(ConfigOptionMode mode, std::function<void()> throw_if_cancel
#endif
}
// Builds one option group; false when it already has its controls.
bool Page::activate_group(size_t i, ConfigOptionMode mode, std::function<void()> throw_if_canceled)
{
auto& group = m_optgroups[i];
if (!group->activate(throw_if_canceled))
return false;
m_vsizer->Add(group->sizer, 0, wxEXPAND | (group->is_legend_line() ? (wxLEFT|wxTOP) : wxALL), m_parent->FromDIP(5)); // ORCA use less margin on parameters section
group->update_visibility(mode);
#if HIDE_FIRST_SPLIT_LINE
// BBS: no line spliter for first group
if (i == 0) group->stb->Hide();
#endif
group->reload_config();
return true;
}
// The first group without controls.
size_t Page::next_group_to_build() const
{
return std::find_if(m_optgroups.begin(), m_optgroups.end(), [](const auto& group) { return !group->is_activated(); }) - m_optgroups.begin();
}
bool Page::build_pending() const
{
return next_group_to_build() < m_optgroups.size();
}
bool Page::build_step(ConfigOptionMode mode)
{
const size_t i = next_group_to_build();
if (i < m_optgroups.size())
activate_group(i, mode, [] {});
return build_pending();
}
void Page::clear()
{
for (auto group : m_optgroups)
+10
View File
@@ -94,6 +94,10 @@ public:
void reload_config();
void update_visibility(ConfigOptionMode mode, bool update_contolls_visibility);
void activate(ConfigOptionMode mode, std::function<void()> throw_if_canceled);
// Whether an option group has no controls yet.
bool build_pending() const;
// Builds the next option group that has no controls yet; true while some remain.
bool build_step(ConfigOptionMode mode);
void clear();
void msw_rescale();
void sys_color_changed();
@@ -121,6 +125,8 @@ public:
std::map<std::string, std::string> m_opt_id_map;
protected:
size_t next_group_to_build() const;
bool activate_group(size_t i, ConfigOptionMode mode, std::function<void()> throw_if_canceled);
// Color of TreeCtrlItem. The wxColour will be updated only if the new wxColour pointer differs from the currently rendered one.
const wxColour* m_item_color;
};
@@ -437,6 +443,10 @@ public:
// BBS: new layout
void set_expanded(bool value);
void restore_last_select_item();
// page_build_pending() says whether the selected page has groups without controls, and
// page_build_step() builds one.
bool page_build_pending() const;
bool page_build_step();
static bool validate_custom_gcode(const wxString& title, const std::string& gcode);
bool validate_custom_gcodes();
+3 -2
View File
@@ -1962,8 +1962,9 @@ DiffPresetDialog::DiffPresetDialog(MainFrame* mainframe)
assert(wxGetApp().preset_bundle);
m_preset_bundle_left = std::make_unique<PresetBundle>(*wxGetApp().preset_bundle);
m_preset_bundle_right = std::make_unique<PresetBundle>(*wxGetApp().preset_bundle);
// show() copies the app's bundle into both before anything is displayed.
m_preset_bundle_left = std::make_unique<PresetBundle>();
m_preset_bundle_right = std::make_unique<PresetBundle>();
// Create UI items
+2 -1
View File
@@ -10,6 +10,7 @@
#include "libslic3r/PresetBundle.hpp"
#include "Widgets/Button.hpp"
#include "Widgets/ScrolledWindow.hpp"
#include "Lazy.hpp"
class ScalableButton;
class wxStaticText;
@@ -416,7 +417,7 @@ public:
//------------------------------------------
// DiffPresetDialog
//------------------------------------------
class DiffPresetDialog : public DPIDialog
class DiffPresetDialog : public DPIDialog, public LazyInstance<DiffPresetDialog>
{
DiffViewCtrl* m_tree { nullptr };
wxBoxSizer* m_presets_sizer { nullptr };
+20 -15
View File
@@ -1159,10 +1159,25 @@ bool GuideFrame::run()
}
int GuideFrame::GetFilamentInfo( std::string VendorDirectory, json & pFilaList, std::string filepath, std::string &sVendor, std::string &sType)
{
std::unordered_set<std::string> visiting;
return GetFilamentInfo(VendorDirectory, pFilaList, filepath, sVendor, sType, visiting);
}
int GuideFrame::GetFilamentInfo(const std::string& VendorDirectory, json& pFilaList,
const std::string& filepath, std::string& sVendor,
std::string& sType, std::unordered_set<std::string>& visiting)
{
//GetStardardFilePath(filepath);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " GetFilamentInfo:VendorDirectory - " << VendorDirectory << ", Filepath - "<<filepath;
// Path-scoped guard: without it an `inherits` cycle would recurse forever.
// The repeated file was not inserted, so the cycle hit has nothing to erase.
if (!visiting.insert(filepath).second) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " inherits cycle at " << filepath;
return -1;
}
// Resolve this file's own vendor/type into LOCAL variables, independent of
// whatever the caller already accumulated. The cache entry for `filepath`
// must reflect only this file's own inherits chain — passing the caller's
@@ -1197,7 +1212,6 @@ int GuideFrame::GetFilamentInfo( std::string VendorDirectory, json & pFilaList,
else
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << filepath << " - Not Contains filament_type";
if (vendor == "" || type == "") {
if (jLocal.contains("inherits")) {
std::string FName = jLocal["inherits"];
@@ -1213,8 +1227,8 @@ int GuideFrame::GetFilamentInfo( std::string VendorDirectory, json & pFilaList,
inherits_path = (boost::filesystem::path(m_OrcaFilaLibPath) / boost::filesystem::path(FPath)).make_preferred();
if (boost::filesystem::exists(inherits_path)) {
// Recurse with this file's own (vendor, type) as the chain accumulator.
status = GetFilamentInfo(VendorDirectory, pFilaList, inherits_path.string(), vendor, type);
// Traverse the full chain for errors; inherited values only fill local gaps.
status = GetFilamentInfo(VendorDirectory, pFilaList, inherits_path.string(), vendor, type, visiting);
} else {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " inherits File Not Exist: " << inherits_path;
status = -1;
@@ -1222,16 +1236,6 @@ int GuideFrame::GetFilamentInfo( std::string VendorDirectory, json & pFilaList,
}
} else {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << filepath << " - Not Contains inherits";
if (type == "") {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "sType is Empty";
status = -1;
} else {
if (vendor == "")
vendor = "Generic";
status = 0;
}
}
} else {
status = 0;
}
}
@@ -1250,6 +1254,7 @@ int GuideFrame::GetFilamentInfo( std::string VendorDirectory, json & pFilaList,
// Merge this file's resolved values into the caller's out-params (fill empties only).
if (sVendor.empty()) sVendor = vendor;
if (sType.empty()) sType = type;
visiting.erase(filepath);
return status;
}
@@ -1803,8 +1808,8 @@ int GuideFrame::LoadProfileFamily(std::string strVendor, std::string strFilePath
std::string sT;
int nRet = GetFilamentInfo(vendor_dir.string(),tFilaList, sub_file, sV, sT);
if (nRet != 0) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "Load Filament:" << s1 << ",GetFilamentInfo Failed, Vendor:" << sV << ",Type:"<< sT;
if (nRet != 0 || sV.empty() || sT.empty()) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "Load Filament:" << s1 << ",unresolved vendor/type, Vendor:" << sV << ",Type:"<< sT;
continue;
}
+4
View File
@@ -33,6 +33,7 @@
#include <atomic>
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <nlohmann/json.hpp>
@@ -110,6 +111,9 @@ public:
void on_dpi_changed(const wxRect &suggested_rect) {}
private:
int GetFilamentInfo(const std::string& VendorDirectory, json& pFilaList, const std::string& filepath,
std::string& sVendor, std::string& sType, std::unordered_set<std::string>& visiting);
GUI_App *m_MainPtr;
AppConfig m_appconfig_new;
+2 -2
View File
@@ -219,6 +219,7 @@ WebViewPanel::WebViewPanel(wxWindow *parent)
Bind(wxEVT_CLOSE_WINDOW, &WebViewPanel::OnClose, this);
m_LoginUpdateTimer = nullptr;
update_mode();
}
WebViewPanel::~WebViewPanel()
@@ -416,8 +417,7 @@ void WebViewPanel::OnClose(wxCloseEvent& evt)
void WebViewPanel::OnFreshLoginStatus(wxTimerEvent &event)
{
auto mainframe = Slic3r::GUI::wxGetApp().mainframe;
if (mainframe && mainframe->m_webview == this) {
if (WebViewPanel::if_built() == this) {
auto* app_config = Slic3r::GUI::wxGetApp().app_config;
if (app_config && app_config->get_stealth_mode()) return;
Slic3r::GUI::wxGetApp().get_login_info(ORCA_CLOUD_PROVIDER);
+2 -1
View File
@@ -24,6 +24,7 @@
#include <wx/tbarbase.h>
#include "wx/textctrl.h"
#include <wx/timer.h>
#include "Lazy.hpp"
namespace Slic3r {
@@ -33,7 +34,7 @@ class NetworkAgent;
namespace GUI {
class WebViewPanel : public wxPanel
class WebViewPanel : public wxPanel, public LazyInstance<WebViewPanel>
{
public:
WebViewPanel(wxWindow *parent);
+50 -8
View File
@@ -1,7 +1,9 @@
#import "MacDarkMode.hpp"
#include "../GUI/Widgets/Label.hpp"
#include "wx/graphics.h"
#include "wx/osx/core/cfstring.h"
#include "wx/osx/private.h"
#import <algorithm>
@@ -348,11 +350,28 @@ bool addObserver = false;
}
@end
// Orca: A Shift-trackpad pan belongs to one GL view; sharing its lifecycle across views
// could make a gesture reuse another canvas's world-space anchor.
static char scroll_pan_active_key;
static char gesture_handler_key;
static wxEvtHandler* get_gesture_handler(NSView* view)
{
return static_cast<wxEvtHandler*>([objc_getAssociatedObject(view, &gesture_handler_key) pointerValue]);
}
static bool is_scroll_pan_active(NSView* view)
{
return [objc_getAssociatedObject(view, &scroll_pan_active_key) boolValue];
}
static void set_scroll_pan_active(NSView* view, bool active)
{
objc_setAssociatedObject(view, &scroll_pan_active_key, active ? @YES : nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@implementation wxNSCustomOpenGLView (Gesture)
wxEvtHandler * _gestureHandler = nullptr;
- (void) onGestureMove: (NSPanGestureRecognizer*) gesture
{
wxPanGestureEvent evt;
@@ -378,22 +397,43 @@ wxEvtHandler * _gestureHandler = nullptr;
- (void) postEvent: (wxGestureEvent &) evt withGesture: (NSGestureRecognizer* ) gesture
{
NSPoint pos = [gesture locationInView: self];
evt.SetPosition({(int) pos.x, (int) pos.y});
evt.SetPosition(wxFromNSPoint(self, pos));
if (gesture.state == NSGestureRecognizerStateBegan)
evt.SetGestureStart();
else if (gesture.state == NSGestureRecognizerStateEnded)
evt.SetGestureEnd();
_gestureHandler->ProcessEvent(evt);
if (wxEvtHandler* handler = get_gesture_handler(self))
handler->ProcessEvent(evt);
}
- (void) scrollWheel2:(NSEvent *)event
{
bool shiftDown = [event modifierFlags] & NSShiftKeyMask;
if (_gestureHandler && shiftDown && event.hasPreciseScrollingDeltas) {
wxEvtHandler* handler = get_gesture_handler(self);
if (handler && shiftDown && event.hasPreciseScrollingDeltas) {
wxPanGestureEvent evt;
evt.SetDelta({-(int)[event scrollingDeltaX], - (int)[event scrollingDeltaY]});
_gestureHandler->ProcessEvent(evt);
// NSOpenGLView uses bottom-left coordinates; wx gestures use top-left coordinates.
const wxPoint pos = wxFromNSPoint(self, [self convertPoint:[event locationInWindow] fromView:nil]);
const wxPoint delta(-(int)[event scrollingDeltaX], -(int)[event scrollingDeltaY]);
// Orca: GLCanvas3D derives the anchor position as position - delta, so synthesize
// the post-delta position from the native cursor coordinate.
evt.SetPosition(pos + delta);
evt.SetDelta(delta);
// Orca: Preserve the anchor throughout a trackpad scroll, including its momentum events.
// Keep it after phase Ended: momentum may follow. The next Began replaces it.
const NSEventPhase phase = event.phase;
const NSEventPhase momentum_phase = event.momentumPhase;
const bool unphased = phase == NSEventPhaseNone && momentum_phase == NSEventPhaseNone;
if (!is_scroll_pan_active(self) || unphased || (phase & (NSEventPhaseMayBegin | NSEventPhaseBegan)))
evt.SetGestureStart();
if (unphased || (phase & NSEventPhaseCancelled) ||
(momentum_phase & (NSEventPhaseEnded | NSEventPhaseCancelled)))
evt.SetGestureEnd();
set_scroll_pan_active(self, !evt.IsGestureEnd());
handler->ProcessEvent(evt);
} else {
// Orca: Switching away from Shift-pan must not reuse its depth when Shift is pressed again.
set_scroll_pan_active(self, false);
[self scrollWheel2: event];
}
}
@@ -415,7 +455,9 @@ wxEvtHandler * _gestureHandler = nullptr;
// [self addGestureRecognizer:pan];
// [self addGestureRecognizer:magnification];
// [self addGestureRecognizer:rotation];
_gestureHandler = handler;
objc_setAssociatedObject(self, &gesture_handler_key, handler ? [NSValue valueWithPointer:handler] : nil,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
set_scroll_pan_active(self, false);
}
@end

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