Compare commits

..
Author SHA1 Message Date
SoftFever 12b6d6b9ba Merge branch 'main' into hanif/preview-drag-detail 2026-09-29 16:14:34 +08:00
Hanif Koh c6d89d6978 Hide Infill in the Shell Drag Mode Only Where the Profile Covers It
A profile with no top or bottom shell layers, or no walls, leaves the infill on the surface,
so hiding it changed the picture. The viewer now reads the print's default region settings
when it loads a print and leaves the infill in place for such a print; per-object overrides
are not consulted.
2026-09-28 17:10:54 +08:00
Hanif Koh abecef53b0 Keep Only the Solid Model and Shell Modes While Dragging
The skip-layers and outer-walls modes drew one layer in every N, and every visual
complaint on the review came from the gaps that leaves at close range. The shell mode
draws every layer and looks the same from outside, and the solid model is the one mode
whose cost does not depend on the toolpaths, so the two of them cover what the stride
modes were for. The layer stride goes with them; a saved preference naming a removed
mode falls back to off.
2026-09-28 17:10:54 +08:00
Hanif Koh 44d5a22a32 Add a Shell-Only Drag Mode That Keeps What Can Be Seen From Outside
A third mode of the drag preference: the reduced set keeps every role but the sparse
infill, internal solid infill, internal bridge infill and gap fill, which lie under the
skins or between the walls, on every layer. Walls, top and bottom surfaces, bridges,
supports and the prime tower are drawn whole, so from outside the print looks as it does
at rest, and the two end layers of the visible range stay whole as in every mode.

The roles come from the slicer, so the classification is exact wherever the slicer's is.
What a role cannot tell apart is the inside of the prime tower or of a support from its
outside; both are kept whole. A profile with no top or bottom shell layers exposes infill,
which this mode hides.
2026-09-28 17:10:54 +08:00
Hanif Koh dec146f1e6 Let the User Choose What the Preview Draws While Dragging
The solid model is one answer to a preview that cannot keep up with a drag; a
reviewer asked for the two that keep the drag view made of toolpaths. The
checkbox is now a combobox, "Simplify preview while dragging", with Off, Solid
model, Skip layers and Outer walls, and a spin, "Draw one layer in every N",
for the toolpath modes. Off is the default, and with it nothing is built.

Skip layers keeps one layer in every N. Outer walls keeps the outer and overhang
perimeters of those layers, so the prime tower and supports, whose segments have
other roles, drop out of it. Both keep the bottom and top layers of the visible
range whole, as the solid model does, so the faces the range cuts open stay what
was printed there.

libvgcode takes the choice as EReducedDetailMode and a stride; the sets are
rebuilt when either changes, and drawing from the reduced one is still a buffer
binding. A mode change reaches the loaded preview at once, as the checkbox did.
2026-09-28 16:20:11 +08:00
Hanif Koh a9fec0ffc4 Release the Reduced Set When Unused and Share the Drag Check With the Scene Cache
The reduced index buffers were only re-uploaded while the preference was on, so
the last set stayed allocated until the next load once it was switched off, and
their size was missing from get_used_gpu_memory(). They are now uploaded on every
rebuild, empty when nothing was built, and counted.

The scene cache and the solid model both asked whether the user was dragging,
with different lists: the cache knew about gizmos and the rectangle selection,
the solid model about the navigator and the sliders. One is_user_interacting()
now answers both.

The tooltip says that negative volumes are not cut out of the solid model, since
load_shells() drops every non-model-part volume, and the preferences handler
keeps the dimming comment with the branch it documents.
2026-09-28 16:20:11 +08:00
Hanif Koh 9ab6bbebf0 Offer the Sliced Objects as a Solid Model While Dragging
The G-code preview draws every toolpath segment as an instanced box, and its frame cost is
linear in the number of segments drawn. On a plate of large objects that is enough that
dragging the camera or a preview slider cannot keep up, and no amount of per-segment work
changes that; only drawing fewer segments does.

The objects themselves are cheaper: a mesh costs its triangles once, however many layers it
has, and the preview already loads the objects as shells for its translucent ghost. A new
preference, "Only render solid model when dragging" (off by default), draws those shells
opaque in their filament colours instead of the toolpaths while the user drags.

The visible layer range still holds. The shells are cut at the range's top and bottom through
the gouraud shader's z range, and libvgcode keeps a second index buffer holding just the
range's bottom and top layers, filled in the same walk as the full one, which is drawn
afterwards so that it caps the cut with what was really printed there. Switching between the
two sets is a buffer binding, never a rebuild. The prime tower is added to the shells from its
sliced mesh while the preference is on, positioned as the print placed it; it keeps its opaque
colour and so stays out of the translucent ghost. Supports have no mesh and are not shown.

Dragging is the camera, the navigator or either slider being held; a slider reports it from
ImGui's active id, since its dirty flag is raised and consumed inside one frame. A wheel step
holds the solid model for a 150 ms settle time, with the frame that restores the toolpaths
scheduled for when it runs out. The switch is decided at the top of the frame, before the
cached scene is consulted, and a frame that switches redraws the scene. A drag cut short by
focus or capture loss is ended explicitly, and a button release wakes the idle loop, since on
some platforms no idle event follows it until the next input.

With the preference off, nothing is built and the preview is unchanged.
2026-09-28 16:20:11 +08:00
126 changed files with 1894 additions and 3960 deletions
@@ -189,7 +189,8 @@ the presets to a base is in [shared-bases.md](shared-bases.md#variant-arrays-on-
2. Every key in `filament_options_with_variant` ([the full list](#the-four-key-sets)) that reaches the 2. Every key in `filament_options_with_variant` ([the full list](#the-four-key-sets)) that reaches the
preset, whether written, included or inherited, is resized to its variant count: write it at exactly preset, whether written, included or inherited, is resized to its variant count: write it at exactly
that width, or leave it out. Keys outside the set that width, or leave it out. Keys outside the set
(`filament_type`, plate temperatures, `fan_max_speed`, `slow_down_min_speed`, …) are never addressed by variant index; the preset contributes their first value however wide a (`filament_type`, plate temperatures, `fan_max_speed`, `slow_down_min_speed`, `pressure_advance`,
…) are never addressed by variant index; the preset contributes their first value however wide a
file writes them. file writes them.
3. Cover every variant the material is meant to print on across its `compatible_printers`. Leave out 3. Cover every variant the material is meant to print on across its `compatible_printers`. Leave out
a variant deliberately when the material should not be tuned for it (e.g. a TPU High Flow variant a variant deliberately when the material should not be tuned for it (e.g. a TPU High Flow variant
@@ -454,7 +455,7 @@ no set: the variant-length resize leaves it alone, and only the pair rebuild of
last for contrast; it is not a variant set. last for contrast; it is not a variant set.
`check` and `fix-variant` read the four sets from `PrintConfig.cpp` on every run, so they follow the `check` and `fix-variant` read the four sets from `PrintConfig.cpp` on every run, so they follow the
engine. The lists below are from the 2026-09-29 checkout; regenerate them from the repository root engine. The lists below are from the 2026-09-25 checkout; regenerate them from the repository root
before relying on them (the recipe strips comments, since an initializer can carry a commented-out entry): before relying on them (the recipe strips comments, since an initializer can carry a commented-out entry):
```bash ```bash
@@ -477,7 +478,7 @@ EOF
**`print_options_with_variant`**, process, stride 1 (45): `bridge_acceleration`, `bridge_speed`, `default_acceleration`, `default_jerk`, `default_junction_deviation`, `enable_overhang_speed`, `gap_infill_speed`, `infill_jerk`, `initial_layer_acceleration`, `initial_layer_infill_speed`, `initial_layer_jerk`, `initial_layer_speed`, `initial_layer_travel_acceleration`, `initial_layer_travel_jerk`, `initial_layer_travel_speed`, `inner_wall_acceleration`, `inner_wall_jerk`, `inner_wall_speed`, `internal_bridge_speed`, `internal_solid_infill_acceleration`, `internal_solid_infill_speed`, `outer_wall_acceleration`, `outer_wall_jerk`, `outer_wall_speed`, `overhang_1_4_speed`, `overhang_2_4_speed`, `overhang_3_4_speed`, `overhang_4_4_speed`, `print_extruder_id`, `print_extruder_variant`, `slowdown_for_curled_perimeters`, `small_perimeter_speed`, `small_perimeter_threshold`, `sparse_infill_acceleration`, `sparse_infill_speed`, `support_interface_speed`, `support_speed`, `top_solid_infill_flow_ratio`, `top_surface_acceleration`, `top_surface_jerk`, `top_surface_speed`, `travel_acceleration`, `travel_jerk`, `travel_speed`, `travel_speed_z` **`print_options_with_variant`**, process, stride 1 (45): `bridge_acceleration`, `bridge_speed`, `default_acceleration`, `default_jerk`, `default_junction_deviation`, `enable_overhang_speed`, `gap_infill_speed`, `infill_jerk`, `initial_layer_acceleration`, `initial_layer_infill_speed`, `initial_layer_jerk`, `initial_layer_speed`, `initial_layer_travel_acceleration`, `initial_layer_travel_jerk`, `initial_layer_travel_speed`, `inner_wall_acceleration`, `inner_wall_jerk`, `inner_wall_speed`, `internal_bridge_speed`, `internal_solid_infill_acceleration`, `internal_solid_infill_speed`, `outer_wall_acceleration`, `outer_wall_jerk`, `outer_wall_speed`, `overhang_1_4_speed`, `overhang_2_4_speed`, `overhang_3_4_speed`, `overhang_4_4_speed`, `print_extruder_id`, `print_extruder_variant`, `slowdown_for_curled_perimeters`, `small_perimeter_speed`, `small_perimeter_threshold`, `sparse_infill_acceleration`, `sparse_infill_speed`, `support_interface_speed`, `support_speed`, `top_solid_infill_flow_ratio`, `top_surface_acceleration`, `top_surface_jerk`, `top_surface_speed`, `travel_acceleration`, `travel_jerk`, `travel_speed`, `travel_speed_z`
**`filament_options_with_variant`**, filament, stride 1 (54): `activate_air_filtration`, `activate_air_filtration_during_print`, `activate_air_filtration_on_completion`, `adaptive_pressure_advance`, `adaptive_pressure_advance_bridges`, `adaptive_pressure_advance_model`, `adaptive_pressure_advance_overhangs`, `complete_print_exhaust_fan_speed`, `during_print_exhaust_fan_speed`, `enable_pressure_advance`, `filament_adaptive_volumetric_speed`, `filament_cooling_before_tower`, `filament_deretraction_speed`, `filament_extruder_variant`, `filament_flow_ratio`, `filament_flush_temp`, `filament_flush_volumetric_speed`, `filament_ironing_flow`, `filament_ironing_inset`, `filament_ironing_spacing`, `filament_ironing_speed`, `filament_long_retractions_when_cut`, `filament_max_volumetric_speed`, `filament_pre_cooling_temperature`, `filament_pre_cooling_temperature_nc`, `filament_preheat_temperature_delta`, `filament_ramming_travel_time`, `filament_ramming_travel_time_nc`, `filament_ramming_volumetric_speed`, `filament_ramming_volumetric_speed_nc`, `filament_retract_after_wipe`, `filament_retract_before_wipe`, `filament_retract_length_nc`, `filament_retract_length_toolchange`, `filament_retract_lift_above`, `filament_retract_lift_below`, `filament_retract_lift_enforce`, `filament_retract_restart_extra`, `filament_retract_restart_extra_toolchange`, `filament_retract_when_changing_layer`, `filament_retraction_distances_when_cut`, `filament_retraction_length`, `filament_retraction_minimum_travel`, `filament_retraction_speed`, `filament_wipe`, `filament_wipe_distance`, `filament_z_hop`, `filament_z_hop_types`, `long_retractions_when_ec`, `nozzle_temperature`, `nozzle_temperature_initial_layer`, `pressure_advance`, `retraction_distances_when_ec`, `volumetric_speed_coefficients` **`filament_options_with_variant`**, filament, stride 1 (48): `activate_air_filtration`, `activate_air_filtration_during_print`, `activate_air_filtration_on_completion`, `complete_print_exhaust_fan_speed`, `during_print_exhaust_fan_speed`, `filament_adaptive_volumetric_speed`, `filament_cooling_before_tower`, `filament_deretraction_speed`, `filament_extruder_variant`, `filament_flow_ratio`, `filament_flush_temp`, `filament_flush_volumetric_speed`, `filament_ironing_flow`, `filament_ironing_inset`, `filament_ironing_spacing`, `filament_ironing_speed`, `filament_long_retractions_when_cut`, `filament_max_volumetric_speed`, `filament_pre_cooling_temperature`, `filament_pre_cooling_temperature_nc`, `filament_preheat_temperature_delta`, `filament_ramming_travel_time`, `filament_ramming_travel_time_nc`, `filament_ramming_volumetric_speed`, `filament_ramming_volumetric_speed_nc`, `filament_retract_after_wipe`, `filament_retract_before_wipe`, `filament_retract_length_nc`, `filament_retract_length_toolchange`, `filament_retract_lift_above`, `filament_retract_lift_below`, `filament_retract_lift_enforce`, `filament_retract_restart_extra`, `filament_retract_restart_extra_toolchange`, `filament_retract_when_changing_layer`, `filament_retraction_distances_when_cut`, `filament_retraction_length`, `filament_retraction_minimum_travel`, `filament_retraction_speed`, `filament_wipe`, `filament_wipe_distance`, `filament_z_hop`, `filament_z_hop_types`, `long_retractions_when_ec`, `nozzle_temperature`, `nozzle_temperature_initial_layer`, `retraction_distances_when_ec`, `volumetric_speed_coefficients`
**`printer_extruder_options`**, machine, one value per extruder, not a variant set (8): **`printer_extruder_options`**, machine, one value per extruder, not a variant set (8):
`default_nozzle_volume_type`, `extruder_max_nozzle_count`, `extruder_printable_area`, `default_nozzle_volume_type`, `extruder_max_nozzle_count`, `extruder_printable_area`,
@@ -244,10 +244,10 @@ preset for a 0.2 nozzle, explicitly revisit flow limits; do not infer a pressure
required direction of change, from diameter alone. required direction of change, from diameter alone.
On a printer with extruder variants, a filament tunes these per variant too: On a printer with extruder variants, a filament tunes these per variant too:
`filament_max_volumetric_speed`, `filament_flow_ratio`, `nozzle_temperature`, pressure advance and the `filament_max_volumetric_speed`, `filament_flow_ratio`, `nozzle_temperature` and the retraction
retraction overrides carry one value per variant of `filament_extruder_variant` (Standard, High Flow, …). The overrides carry one value per variant of `filament_extruder_variant` (Standard, High Flow, …). The
exact key set is [`filament_options_with_variant`](extruder-variants.md#the-four-key-sets); exact key set is [`filament_options_with_variant`](extruder-variants.md#the-four-key-sets);
`slow_down_min_speed` and `fan_max_speed` are not in it. Keep every such array at exactly that width, even where the `slow_down_min_speed` and `pressure_advance` are not in it. Keep every such array at exactly that width, even where the
setting does not differ per variant, and measure the High Flow variant rather than copying Standard setting does not differ per variant, and measure the High Flow variant rather than copying Standard
([extruder-variants.md](extruder-variants.md#filament)). ([extruder-variants.md](extruder-variants.md#filament)).
+1 -1
View File
@@ -146,7 +146,7 @@ jobs:
"repos/${{ github.repository }}/actions/workflows/post_merge_profiles.yml/runs" \ "repos/${{ github.repository }}/actions/workflows/post_merge_profiles.yml/runs" \
-f branch="$branch" -f event=workflow_dispatch -f per_page=100)" -f branch="$branch" -f event=workflow_dispatch -f per_page=100)"
run_id="$(jq -r --arg marker "[OFL cron $dispatch_id]" \ run_id="$(jq -r --arg marker "[OFL cron $dispatch_id]" \
'[.workflow_runs[] | select((.display_title // "") | contains($marker))] '[.workflow_runs[] | select((.display_title // "") | contains($marker))] \
| sort_by(.created_at) | last | .id // empty' <<< "$runs_json")" | sort_by(.created_at) | last | .id // empty' <<< "$runs_json")"
[ -n "$run_id" ] && break [ -n "$run_id" ] && break
sleep 5 sleep 5
+80
View File
@@ -0,0 +1,80 @@
# G-code preview while dragging
The sliced preview draws every toolpath segment of the plate as an instanced box. On a large
plate that is tens of millions of segments, and the frame is GPU-bound: the cost is the number of
instances drawn, not anything the CPU does per frame. Dragging the camera over such a plate cannot
keep up. The `preview_reduced_detail_mode` preference (*Graphics > G-code Preview*, off by
default) lets the preview draw less while the user drags and put the full toolpaths back when they
let go.
| Preference | Values | Effect |
|---|---|---|
| `preview_reduced_detail_mode` | `off`, `solid`, `shell` | what is drawn while dragging |
libvgcode (`src/libvgcode`) builds and binds the reduced toolpath set, `GCodeViewer` maps the
preferences onto it and draws the solid model, and `GLCanvas3D` decides when the user is dragging.
The OpenGL ES path keeps a single set and ignores the preference.
## Two sets, one walk
`ViewerImpl::update_enabled_entities()` walks the visible vertex range once and fills two segment
index buffers side by side: the **full** set and the **reduced** set (segments and options).
Building them together is what makes switching free: starting or ending a drag is a buffer
binding, never a rebuild. A change of mode does rebuild. Nothing is built while the mode
is off, and the reduced buffers are then uploaded empty so that the last set does not stay
allocated.
Whatever the mode leaves out, the bottom and top layers of the visible range are kept whole: they
are the faces the range cuts open, and the top is what the user is looking at.
### Modes
- `EndLayersOnly` (`solid` in the preference) keeps only the two end layers. `GCodeViewer` then
draws the sliced objects and the prime tower as opaque solids, see below.
- `ShellOnly` (`shell`) keeps every role but the sparse infill, internal solid infill, internal
bridge infill and gap fill, which lie under the skins or between the walls. Walls, top and
bottom surfaces, bridges, supports and the prime tower are drawn whole, so from outside the
print looks as it does at rest. The roles come from
the slicer, so the classification is exact wherever the slicer's is; what it cannot express is
the inside of the prime tower and of a support, which share one role with their outside and are
kept whole. A profile with no top or bottom shell layers, or no walls, leaves the infill on the
surface, so `GCodeViewer` turns the hiding off for such a print when it loads it, from the
print's default region settings; per-object overrides are not consulted.
## The solid model
The preview already loads the sliced objects as shells for its translucent ghost.
`GCodeViewer::render_solid_model()` draws those shells opaque, in their filament colors, with the
`gouraud` shader, whose z range cuts them to the visible layer range. The two toolpath layers of
the reduced set are drawn afterwards and cap the cut with what was really printed there. The
shells hold only the objects, so while this mode is on the prime tower is added from its sliced
mesh, positioned as the print placed it. It is added or removed on its own when the mode changes,
without reloading the objects, keeps its opaque color so that it never appears among the
translucent shells, and stays out of their bounding box. Supports have no mesh and are not shown,
and `load_shells()` drops every non-model-part volume, so a negative volume is not cut out.
A plate whose shells are not loaded keeps drawing toolpaths, since the solid model would leave
only the end layers.
## Deciding that the user is dragging
`GLCanvas3D::_update_preview_interaction()` runs at the top of every preview frame, before the
canvas decides whether to reuse its cached scene, so that the switch lands in that frame. Dragging
is `GLCanvas3D::is_user_interacting()`, the same answer the scene cache reads: the camera, the
navigator, a gizmo, the rectangle selection or either slider being held. A slider reports this from
ImGui's active id rather than its dirty flag, which is raised and consumed inside one frame. A
wheel step has no duration, so it holds the reduced set for a 150 ms settle time instead, and the
frame that restores the toolpaths is scheduled for when that time runs out, since the render timer
only wakes the idle loop. A drag cut short by focus or capture loss is ended explicitly, and a
button release wakes the idle loop, because on some platforms nothing else would until the next
input.
## Reused scene frames
`GLCanvas3D` keeps its last scene pass for frames that only rebuild the overlay (`SceneCache`). Its
key covers the canvas size, the camera and hover state, not what the toolpath sets draw, so a frame
that reuses the scene must never be one on which the set is switched.
`_update_preview_interaction()` therefore reports whether the bound set changed, and a frame on
which it did redraws the scene. The canvas neither captures nor reuses the scene while the user
drags, so no reduced frame outlives a drag, and the frame that ends a wheel's settle time is
requested as a full frame.
+19 -45
View File
@@ -28,8 +28,8 @@ Per-vendor granularity is what makes the system practical:
- A vendor whose profile is bumped invalidates only its own cache. The other 60-odd - A vendor whose profile is bumped invalidates only its own cache. The other 60-odd
vendors keep theirs — even when the bumped vendor is the shared Orca filament vendors keep theirs — even when the bumped vendor is the shared Orca filament
library everyone else inherits from. library everyone else inherits from.
- The setup wizard loads its vendors through the same routine as startup, so it - The setup wizard, which loads vendors one at a time, gets the same speedup as
gets the same speedup without a second code path. startup without a second code path.
- A vendor with no cache, or a broken one, costs only that vendor a parse. - A vendor with no cache, or a broken one, costs only that vendor a parse.
A cache holds *system* presets only. User presets, project settings and modified A cache holds *system* presets only. User presets, project settings and modified
@@ -162,20 +162,15 @@ cache nothing can invalidate is worse than no cache.
Vendors load in a fixed order, because filament inheritance crosses exactly one Vendors load in a fixed order, because filament inheritance crosses exactly one
boundary: any vendor's filament may inherit from the shared Orca filament library, boundary: any vendor's filament may inherit from the shared Orca filament library,
and nothing else reaches across vendors — an `include` is always vendor-local. Only and nothing else reaches across vendors — an `include` is always vendor-local. The
installing a vendor's presets crosses it; reading the vendor, from its cache or its library therefore goes first, alone; every other vendor follows in parallel, resolving
JSONs, needs nothing from the library. So every other vendor is read while the library against it; and the results are merged in a stable order:
loads, each is installed against it as soon as both are done, and the results are
merged in a stable order:
```mermaid ```mermaid
flowchart LR flowchart LR
lib["1 · OrcaFilamentLibrary loaded;<br/>meanwhile every other vendor read<br/>from its cache or its JSONs"] --> par["2 · every other vendor installed<br/>in parallel, each into its own bundle,<br/>filaments resolving against the library"] --> merge["3 · bundles merged into one,<br/>in one pass per collection,<br/>in stable vendor order"] lib["1 · OrcaFilamentLibrary<br/>loaded first, synchronously"] --> par["2 · every other vendor in parallel,<br/>each into its own bundle, filaments<br/>resolving against the loaded library"] --> merge["3 · bundles merged into one,<br/>sequentially, in stable vendor order"]
``` ```
`PresetBundle::load_vendors` runs these steps for startup and for the setup wizard,
which hand it the vendors to load and the directory each is installed in.
Whether a vendor comes from its cache or from a parse changes nothing in that Whether a vendor comes from its cache or from a parse changes nothing in that
order — both produce the same bundle, so cached and parsed vendors mix freely in order — both produce the same bundle, so cached and parsed vendors mix freely in
one startup. one startup.
@@ -216,12 +211,11 @@ shipped cache answered first, so the profile in `<data_dir>/system/` was never p
and its cache was never written back. and its cache was never written back.
Serving from a cache is not a memory-image restore. The entries are deserialized and Serving from a cache is not a memory-image restore. The entries are deserialized and
then installed by `install_vendor`, the routine the JSON path hands the vendor's entries then installed one by one — inheritance resolved against the presets installed before
to once it has parsed the sub-files: inheritance resolved against the presets installed them and the currently loaded filament library, includes layered in, configs flattened
before them and the currently loaded filament library, includes layered in, configs onto the collection defaults, validated and registered — by the same function the JSON
flattened onto the collection defaults, validated and registered. An `include` layers path calls straight after parsing a sub-file. An `include` layers what the included
what the included base states, between the parent and the preset's own keys: the base's base states, between the parent and the preset's own keys: the base's diff against the
diff against the
default, taken when the base itself was installed and before the per-variant padding default, taken when the base itself was installed and before the per-variant padding
`inherits` sees, so only what a template sets reaches the presets including it. The two `inherits` sees, so only what a template sets reaches the presets including it. The two
paths share everything below the parse, which is what makes a cache-loaded bundle paths share everything below the parse, which is what makes a cache-loaded bundle
@@ -229,16 +223,6 @@ indistinguishable from a JSON-loaded one by construction rather than by test cov
Installation also rebuilds each preset's file path from the local data directory, so a Installation also rebuilds each preset's file path from the local data directory, so a
shipped cache never carries the generating machine's paths. shipped cache never carries the generating machine's paths.
Installing an entry is split in two. `resolve_vendor_preset` flattens it, reading only
what is registered under the names it inherits and includes, and `commit_vendor_preset`
registers it, the only step that writes anything shared. Entries resolve across threads
in runs and commit in the order the vendor lists them. A run ends before an entry that
inherits or includes one already in it, since that one's commit registers what the
entry resolves against, so no entry in a run reads what another in it registers. An
entry's parse messages are held until it commits. The bundle, the log's parse and
install messages and the error count therefore come out as parsing and installing one
entry at a time would leave them, whatever the listing order.
App upgrades work because a cache normally survives one. Only a deliberate App upgrades work because a cache normally survives one. Only a deliberate
`CACHE_VERSION` bump makes an installed cache unreadable, and that is handled at `CACHE_VERSION` bump makes an installed cache unreadable, and that is handled at
install time rather than at load: a vendor whose cache this build cannot read counts install time rather than at load: a vendor whose cache this build cannot read counts
@@ -270,20 +254,17 @@ the wizard caches the *derived JSON*, not another form of the inputs:
open, the wizard computes the current stamps (one version peek per vendor) and, when open, the wizard computes the current stamps (one version peek per vendor) and, when
they match, serves the catalog from the file — no bundle built, no preset installed. they match, serves the catalog from the file — no bundle built, no preset installed.
Caching bundle inputs instead was tried and measured: rebuilding the bundle from Caching bundle inputs instead was tried and measured: rebuilding the bundle from
per-vendor caches costs over a second of preset installation whatever feeds it, so per-vendor caches costs ~2 s of preset installation whatever feeds it, so only
only skipping the rebuild entirely wins. skipping the rebuild entirely wins.
Any change to the set — a vendor added, removed or updated, or its cache-only Any change to the set — a vendor added, removed or updated, or its cache-only
`.opc` replaced by a newer one — changes the stamps and retires the whole file; `.opc` replaced by a newer one — changes the stamps and retires the whole file;
the wizard then rebuilds the bundle with `PresetBundle::load_vendors`, the load the wizard then rebuilds the bundle vendor by vendor (per-vendor caches serving where
startup uses (per-vendor caches serving where they cover), and writes the catalog they cover) and writes the catalog back. Selections, region and per-open decorations
back. When a vendor fails to load, the filament library included, that open falls are applied downstream of the cache either way, so a served catalog is
back to the wizard's own scan of the vendor JSONs, as when no bundle can be built, indistinguishable from a rebuilt one. Nothing ships this file and the updater never
and writes nothing. Selections, region and per-open decorations are applied touches it; it is a locally written artifact, re-derived whenever stale, written
downstream of the cache either way, so a served catalog is indistinguishable from a through a temp file and rename so half a cache is never readable.
rebuilt one. Nothing ships this file and the updater never touches it; it is a
locally written artifact, re-derived whenever stale, written through a temp file and
rename so half a cache is never readable.
The cache lives under `<data_dir>/cache/`, not beside the vendors: everything that The cache lives under `<data_dir>/cache/`, not beside the vendors: everything that
scans `<data_dir>/system/` treats any `.opc` there as a vendor, so a non-vendor scans `<data_dir>/system/` treats any `.opc` there as a vendor, so a non-vendor
@@ -397,13 +378,6 @@ enumerates only `*.json` will find no vendors at all in a packaged build.
the `CachedPreset` field list — written and read by `visit_entry` in the `CachedPreset` field list — written and read by `visit_entry` in
`PresetCacheFormat.cpp`, one list for the save, the load and the name peek alike — or `PresetCacheFormat.cpp`, one list for the save, the load and the name peek alike — or
the cache's own layout or stamps, requires bumping `CACHE_VERSION` by hand. the cache's own layout or stamps, requires bumping `CACHE_VERSION` by hand.
- **Adding a kind of reference between presets**, as `inherits` and `include` are:
parse the names into `CachedPreset` (a field change, so `CACHE_VERSION` is bumped),
have `install_vendor_entries` end a run before an entry that names one already in it
and retain what the names point at, look them up only in `resolve_vendor_preset`, and
register what they point at only in `commit_vendor_preset`. The listing-order test in
`test_vendor_cache.cpp` fails for a kind the runs do not check once its fixture uses
it.
- **The dictionary indexes with a `uint16`**, so `print_config_def` may hold at most - **The dictionary indexes with a `uint16`**, so `print_config_def` may hold at most
65535 options and one cache at most 65535 distinct enum value names. 65535 options and one cache at most 65535 distinct enum value names.
`CacheDictionary::save` throws past that, which surfaces when CI generates the `CacheDictionary::save` throws past that, which surfaces when CI generates the
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "OrcaFilamentLibrary", "name": "OrcaFilamentLibrary",
"version": "02.04.00.16", "version": "02.04.00.15",
"force_update": "0", "force_update": "0",
"description": "Orca Filament Library", "description": "Orca Filament Library",
"filament_list": [ "filament_list": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"1" "1"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"0" "0"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"0" "0"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
+30 -129
View File
@@ -214,9 +214,9 @@ function ShowModelInfo( pModel )
SendWXDebugInfo("Model Name: "+sModelName); SendWXDebugInfo("Model Name: "+sModelName);
$('#ModelName').text(sModelName); $('#ModelName').html(sModelName);
$('#ModelName').attr('title',sModelName); $('#ModelName').attr('title',sModelName);
$('#ModelAuthorName').text(sModelAuthor); $('#ModelAuthorName').html(sModelAuthor);
switch(UploadType) switch(UploadType)
{ {
@@ -268,7 +268,7 @@ function ShowModelInfo( pModel )
break; break;
} }
$('#Model_Desc').empty().append( SanitizeDescHtml( html_decode(sModelDesc) ) ); $('#Model_Desc').html( html_decode(sModelDesc) );
let ModelPreviewList=pModel.preview_img; let ModelPreviewList=pModel.preview_img;
let TotalPreview=ModelPreviewList.length; let TotalPreview=ModelPreviewList.length;
@@ -281,15 +281,16 @@ function ShowModelInfo( pModel )
if(TotalPreview>0) if(TotalPreview>0)
{ {
$('#ModelPreviewList').empty(); let htmlPreview='';
for(let pn=0;pn<TotalPreview;pn++) for(let pn=0;pn<TotalPreview;pn++)
{ {
//let FTmpPath=decodeURIComponent(ModelPreviewList[pn]); //let FTmpPath=decodeURIComponent(ModelPreviewList[pn]);
let FTmpPath=ModelPreviewList[pn]['filepath']; let FTmpPath=ModelPreviewList[pn]['filepath'];
$('#ModelPreviewList').append( $('<div class="swiper-slide"></div>').append( $('<img class="Model_PrevImg" />').attr('src',FTmpPath) ) ); htmlPreview+='<div class="swiper-slide"><img class="Model_PrevImg" src="'+FTmpPath+'" /></div>';
} }
$('#ModelPreviewList').html(htmlPreview);
$('#Model_Preview_Image').viewer({ $('#Model_Preview_Image').viewer({
title: false, title: false,
fullsreen: false, fullsreen: false,
@@ -409,8 +410,7 @@ function ConstructFileHtml( ID, pItem )
{ {
let fTotal=pItem.length; let fTotal=pItem.length;
let pBoard=$('#'+ID+' .FileListBoard'); let strHtml='';
pBoard.empty();
for( let f=0;f<fTotal;f++ ) for( let f=0;f<fTotal;f++ )
{ {
let pOne=pItem[f]; let pOne=pItem[f];
@@ -443,139 +443,39 @@ function ConstructFileHtml( ID, pItem )
ImgPath='img/default.png'; ImgPath='img/default.png';
} }
//Add html. File names come from the 3MF, so build the nodes rather than concatenating markup. //Add html
let pIconImg=$('<img />').attr('src',ImgPath);
let pMenu=$('<div class="FileMenu"><img src="img/s.svg" /></div>');
if( strClass!='ImageIcon' ) if( strClass!='ImageIcon' )
{ {
pMenu.on('click', function(){ OnClickOpenFile(tPath); }); strHtml+='<div class="FileItem">'+
' <div class="'+strClass+'"><img src="'+ImgPath+'" /></div>'+
' <div class="FileText">'+
' <div class="FileName">'+tName+'</div>'+
' </div>'+
' <div class="FileMenu" onClick="OnClickOpenFile(\''+tPath+'\')"><img src="img/s.svg" /></div>'+
'</div>';
} }
else else
{ {
ImgID++; ImgID++;
let TmpImgID="AF"+ImgID; let TmpImgID="AF"+ImgID;
pIconImg.attr('id',TmpImgID); strHtml+='<div class="FileItem">'+
pMenu.on('click', function(){ OnClickOpenImage(TmpImgID); }); ' <div class="'+strClass+'"><img id="'+TmpImgID+'" src="'+ImgPath+'" /></div>'+
' <div class="FileText">'+
' <div class="FileName">'+tName+'</div>'+
' </div>'+
' <div class="FileMenu" onClick="OnClickOpenImage(\''+TmpImgID+'\')"><img src="img/s.svg" /></div>'+
'</div>';
} }
let pFileItem=$('<div class="FileItem"></div>');
pFileItem.append( $('<div></div>').addClass(strClass).append(pIconImg) );
pFileItem.append( $('<div class="FileText"></div>').append( $('<div class="FileName"></div>').text(tName).attr('title',tName) ) );
pFileItem.append( pMenu );
pBoard.append( pFileItem );
} }
$('#'+ID+' .FileListBoard').html(strHtml);
if( fTotal>0 ) if( fTotal>0 )
$('#'+ID).show(); $('#'+ID).show();
} }
// Descriptions are untrusted 3MF metadata that may carry rich-text HTML (e.g. from MakerWorld).
// Rebuild them from an inert parse, keeping only plain formatting tags and http(s) links and images.
var DescAllowedTags=['P','BR','B','STRONG','I','EM','U','S','STRIKE','DEL','INS','SUB','SUP','SMALL','MARK',
'Q','ABBR','KBD','WBR','H1','H2','H3','H4','H5','H6','UL','OL','LI','DL','DT','DD','BLOCKQUOTE','PRE','CODE',
'HR','SPAN','DIV','FIGURE','FIGCAPTION','TABLE','CAPTION','THEAD','TBODY','TFOOT','TR','TH','TD','A','IMG'];
// Plain attributes kept per tag; the numeric ones must be plain non-negative integers.
var DescAllowedAttrs={'IMG':['alt','title','width','height'],'TD':['colspan','rowspan'],'TH':['colspan','rowspan'],'OL':['start']};
var DescNumericAttrs=['width','height','colspan','rowspan','start'];
// Dropped together with their content; any other unknown tag is unwrapped to its children.
var DescDroppedTags=['SCRIPT','STYLE','TEMPLATE','NOSCRIPT','TEXTAREA','TITLE','IFRAME','FRAME','OBJECT','EMBED','SVG','MATH'];
function IsHttpUrl( strUrl )
{
// The scheme must be written as is, so nothing the URL parser would strip can precede or split it.
if( typeof strUrl!='string' || !/^https?:/i.test(strUrl) )
return false;
try
{
let sProtocol=new URL(strUrl).protocol;
return sProtocol=='http:' || sProtocol=='https:';
}
catch(e)
{
return false;
}
}
// Images load as soon as the page opens, so only https sources are kept: no plain-http requests to the local network.
function IsHttpsUrl( strUrl )
{
return IsHttpUrl(strUrl) && new URL(strUrl).protocol=='https:';
}
// Embedded YouTube players become a plain link to the video.
function GetYouTubeEmbedUrl( pNode )
{
let sSrc=pNode.getAttribute('src');
if( !IsHttpUrl(sSrc) )
return null;
let pUrl=new URL(sSrc);
return ( pUrl.origin=='https://www.youtube.com' && pUrl.pathname.indexOf('/embed/')==0 ) ? pUrl.href : null;
}
function CopyDescNodes( pSrc, pDst )
{
for( let pNode=pSrc.firstChild;pNode!=null;pNode=pNode.nextSibling )
{
if( pNode.nodeType==Node.TEXT_NODE )
{
pDst.appendChild( document.createTextNode(pNode.nodeValue) );
continue;
}
if( pNode.nodeType!=Node.ELEMENT_NODE )
continue;
let sTag=pNode.nodeName.toUpperCase();
if( sTag=='IFRAME' )
{
let sVideoUrl=GetYouTubeEmbedUrl(pNode);
if( sVideoUrl!=null )
{
let pLink=document.createElement('A');
pLink.setAttribute('href',sVideoUrl);
pLink.textContent=sVideoUrl;
pDst.appendChild(pLink);
}
continue;
}
if( $.inArray(sTag,DescDroppedTags)>=0 )
continue;
if( $.inArray(sTag,DescAllowedTags)<0 )
{
CopyDescNodes(pNode,pDst);
continue;
}
let pElem=document.createElement(sTag);
if( sTag=='A' && IsHttpUrl(pNode.getAttribute('href')) )
pElem.setAttribute('href',pNode.getAttribute('href'));
else if( sTag=='IMG' )
{
if( !IsHttpsUrl(pNode.getAttribute('src')) )
continue;
pElem.setAttribute('src',pNode.getAttribute('src'));
}
$.each( DescAllowedAttrs[sTag]||[], function(i,sAttr){
let sValue=pNode.getAttribute(sAttr);
if( sValue!=null && ( $.inArray(sAttr,DescNumericAttrs)<0 || /^\d+$/.test(sValue) ) )
pElem.setAttribute(sAttr,sValue);
});
CopyDescNodes(pNode,pElem);
pDst.appendChild(pElem);
}
}
function SanitizeDescHtml( strHtml )
{
let pFragment=document.createDocumentFragment();
// A DOMParser document is inert: it runs no scripts and loads no resources.
let pDoc=new DOMParser().parseFromString(strHtml,'text/html');
if( pDoc && pDoc.body )
CopyDescNodes(pDoc.body,pFragment);
return pFragment;
}
function ShowProfilelInfo( pProfile ) function ShowProfilelInfo( pProfile )
{ {
//==========Profile Info========== //==========Profile Info==========
@@ -583,10 +483,10 @@ function ShowProfilelInfo( pProfile )
let sProfileAuthor=decodeURIComponent(pProfile.author); let sProfileAuthor=decodeURIComponent(pProfile.author);
let sProfileDesc=decodeURIComponent(pProfile.description); let sProfileDesc=decodeURIComponent(pProfile.description);
$('#ProfileName').text(sProfileName); $('#ProfileName').html(sProfileName);
$('#ProfileAuthor').text(sProfileAuthor); $('#ProfileAuthor').html(sProfileAuthor);
$('#Profile_Desc').empty().append( SanitizeDescHtml( html_decode(sProfileDesc) ) ); $('#Profile_Desc').html( html_decode(sProfileDesc) );
let ProfilePreviewList=pProfile.preview_img; let ProfilePreviewList=pProfile.preview_img;
let TotalPreview=ProfilePreviewList.length; let TotalPreview=ProfilePreviewList.length;
@@ -599,14 +499,15 @@ function ShowProfilelInfo( pProfile )
if(TotalPreview>0) if(TotalPreview>0)
{ {
$('#ProfilePreviewList').empty(); let htmlPreview='';
for(let pn=0;pn<TotalPreview;pn++) for(let pn=0;pn<TotalPreview;pn++)
{ {
let FTmpPath=ProfilePreviewList[pn]['filepath']; let FTmpPath=ProfilePreviewList[pn]['filepath'];
$('#ProfilePreviewList').append( $('<div class="swiper-slide"></div>').append( $('<img class="Model_PrevImg" />').attr('src',FTmpPath) ) ); htmlPreview+='<div class="swiper-slide"><img class="Model_PrevImg" src="'+FTmpPath+'" /></div>';
} }
$('#ProfilePreviewList').html(htmlPreview);
$('#Profile_Preview_Image').viewer({ $('#Profile_Preview_Image').viewer({
title: false, title: false,
fullsreen: false, fullsreen: false,
-30
View File
@@ -3476,14 +3476,6 @@ int CLI::run(int argc, char **argv)
} }
new_variant_counts = old_variant_counts; new_variant_counts = old_variant_counts;
//filament_variant_count = old_variant_counts; //filament_variant_count = old_variant_counts;
//ORCA: lay the per-variant options out one value per variant of the current filaments before each
// loaded filament replaces its own variants, including an option only a loaded filament
// defines, which otherwise starts as a single default value and never reaches the others.
for (const DynamicPrintConfig &config : load_filaments_config)
for (const std::string &opt_key : filament_options_with_variant)
if (opt_key != "filament_extruder_variant" && config.has(opt_key))
m_print_config.option(opt_key, true);
normalize_filament_values_to_variants(m_print_config);
for (int index = 0; index < load_filaments_config.size(); index++) { for (int index = 0; index < load_filaments_config.size(); index++) {
DynamicPrintConfig& config = load_filaments_config[index]; DynamicPrintConfig& config = load_filaments_config[index];
int filament_index = load_filaments_index[index]; int filament_index = load_filaments_index[index];
@@ -3655,25 +3647,6 @@ int CLI::run(int argc, char **argv)
} }
} }
//ORCA: a per-variant option the loaded filament does not define keeps the values of the
// variants the filament already had, and a variant new to it takes its first one's.
const int old_start = old_start_indice[filament_index - 1];
std::vector<int> kept_variant_indice = new_variant_indice;
for (int &i : kept_variant_indice)
if (i < 0)
i = old_start;
for (const std::string &opt_key : filament_options_with_variant) {
if (config.has(opt_key))
continue;
auto *opt_vec_dst = dynamic_cast<ConfigOptionVectorBase *>(m_print_config.option(opt_key));
if (opt_vec_dst == nullptr || opt_vec_dst->size() < size_t(old_start + old_variant_count))
continue;
// set_with_restore_2() pads its source in place
std::unique_ptr<ConfigOption> old_values(opt_vec_dst->clone());
opt_vec_dst->set_with_restore_2(static_cast<ConfigOptionVectorBase *>(old_values.get()), kept_variant_indice, old_start,
old_variant_count, true);
}
//update the old index //update the old index
if (old_variant_count != new_variant_count) if (old_variant_count != new_variant_count)
{ {
@@ -4108,9 +4081,6 @@ int CLI::run(int argc, char **argv)
if (printer_technology == ptFFF) { if (printer_technology == ptFFF) {
fff_print_config.apply(m_print_config, true); fff_print_config.apply(m_print_config, true);
m_print_config.apply(fff_print_config, true); m_print_config.apply(fff_print_config, true);
//ORCA: an option no preset or project defines has just come in as its single default value, and a
// command line override may hold one value per filament.
normalize_filament_values_to_variants(m_print_config);
} else { } else {
boost::nowide::cerr << "invalid printer_technology " << std::endl; boost::nowide::cerr << "invalid printer_technology " << std::endl;
record_exit_reson(outfile_dir, CLI_INVALID_PRINTER_TECH, 0, cli_errors[CLI_INVALID_PRINTER_TECH], sliced_info); record_exit_reson(outfile_dir, CLI_INVALID_PRINTER_TECH, 0, cli_errors[CLI_INVALID_PRINTER_TECH], sliced_info);
+7 -4
View File
@@ -205,6 +205,13 @@ void AppConfig::set_defaults()
if (get("seq_top_layer_only").empty()) if (get("seq_top_layer_only").empty())
set("seq_top_layer_only", "1"); set("seq_top_layer_only", "1");
// what the preview draws while the user drags it
{
const std::string mode = get("preview_reduced_detail_mode");
if (mode != "off" && mode != "solid" && mode != "shell")
set("preview_reduced_detail_mode", "off");
}
// ORCA: darken the layers the preview layer slider is not scrubbed to // ORCA: darken the layers the preview layer slider is not scrubbed to
if (get("preview_dim_previous_layers").empty()) if (get("preview_dim_previous_layers").empty())
set_bool("preview_dim_previous_layers", false); set_bool("preview_dim_previous_layers", false);
@@ -511,10 +518,6 @@ void AppConfig::set_defaults()
set_bool("enable_high_low_temp_mixed_printing", false); set_bool("enable_high_low_temp_mixed_printing", false);
} }
if (get("remember_print_action").empty()) {
set_bool("remember_print_action", false);
}
if (get("ignore_ext_filament_in_filament_map").empty()){ if (get("ignore_ext_filament_in_filament_map").empty()){
set_bool("ignore_ext_filament_in_filament_map", false); set_bool("ignore_ext_filament_in_filament_map", false);
} }
-1
View File
@@ -355,7 +355,6 @@ set(lisbslic3r_sources
Optimize/Optimizer.hpp Optimize/Optimizer.hpp
Orient.cpp Orient.cpp
Orient.hpp Orient.hpp
ParallelResolve.hpp
ParameterUtils.cpp ParameterUtils.cpp
ParameterUtils.hpp ParameterUtils.hpp
pchheader.cpp pchheader.cpp
+13 -26
View File
@@ -849,19 +849,6 @@ ConfigSubstitutions ConfigBase::load_from_json(const std::string &file, ForwardC
return std::move(substitutions_ctxt.substitutions); return std::move(substitutions_ctxt.substitutions);
} }
// Case-insensitive compare of a JSON key against a fixed ASCII one, without
// boost::iequals, whose std::locale() takes a lock the whole process shares in the
// MSVC runtime.
static bool ascii_iequals(const std::string &key, const char *literal)
{
auto lower = [](char c) { return (c >= 'A' && c <= 'Z') ? char(c - 'A' + 'a') : c; };
size_t i = 0;
for (; i < key.size() && literal[i] != '\0'; ++ i)
if (lower(key[i]) != lower(literal[i]))
return false;
return i == key.size() && literal[i] == '\0';
}
int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContext& substitution_context, bool load_inherits_to_config, std::map<std::string, std::string>& key_values, std::string& reason) int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContext& substitution_context, bool load_inherits_to_config, std::map<std::string, std::string>& key_values, std::string& reason)
{ {
json j; json j;
@@ -929,44 +916,44 @@ int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContex
} }
//parse the json elements //parse the json elements
for (auto it = j.begin(); it != j.end(); it++) { for (auto it = j.begin(); it != j.end(); it++) {
if (ascii_iequals(it.key(), BBL_JSON_KEY_VERSION)) { if (boost::iequals(it.key(),BBL_JSON_KEY_VERSION)) {
key_values.emplace(BBL_JSON_KEY_VERSION, it.value()); key_values.emplace(BBL_JSON_KEY_VERSION, it.value());
} }
else if (ascii_iequals(it.key(), BBL_JSON_KEY_IS_CUSTOM)) { else if (boost::iequals(it.key(), BBL_JSON_KEY_IS_CUSTOM)) {
//skip it //skip it
} }
else if (ascii_iequals(it.key(), BBL_JSON_KEY_NAME)) { else if (boost::iequals(it.key(), BBL_JSON_KEY_NAME)) {
key_values.emplace(BBL_JSON_KEY_NAME, it.value()); key_values.emplace(BBL_JSON_KEY_NAME, it.value());
if (it.value() == "project_settings") if (it.value() == "project_settings")
is_project_settings = true; is_project_settings = true;
} }
else if (ascii_iequals(it.key(), BBL_JSON_KEY_URL)) { else if (boost::iequals(it.key(), BBL_JSON_KEY_URL)) {
key_values.emplace(BBL_JSON_KEY_URL, it.value()); key_values.emplace(BBL_JSON_KEY_URL, it.value());
} }
else if (ascii_iequals(it.key(), BBL_JSON_KEY_TYPE)) { else if (boost::iequals(it.key(), BBL_JSON_KEY_TYPE)) {
key_values.emplace(BBL_JSON_KEY_TYPE, it.value()); key_values.emplace(BBL_JSON_KEY_TYPE, it.value());
} }
else if (ascii_iequals(it.key(), BBL_JSON_KEY_SETTING_ID)) { else if (boost::iequals(it.key(), BBL_JSON_KEY_SETTING_ID)) {
key_values.emplace(BBL_JSON_KEY_SETTING_ID, it.value()); key_values.emplace(BBL_JSON_KEY_SETTING_ID, it.value());
} }
else if (ascii_iequals(it.key(), BBL_JSON_KEY_FILAMENT_ID)) { else if (boost::iequals(it.key(), BBL_JSON_KEY_FILAMENT_ID)) {
key_values.emplace(BBL_JSON_KEY_FILAMENT_ID, it.value()); key_values.emplace(BBL_JSON_KEY_FILAMENT_ID, it.value());
} }
else if (ascii_iequals(it.key(), BBL_JSON_KEY_FROM)) { else if (boost::iequals(it.key(), BBL_JSON_KEY_FROM)) {
key_values.emplace(BBL_JSON_KEY_FROM, it.value()); key_values.emplace(BBL_JSON_KEY_FROM, it.value());
} }
else if (ascii_iequals(it.key(), BBL_JSON_KEY_DESCRIPTION)) { else if (boost::iequals(it.key(), BBL_JSON_KEY_DESCRIPTION)) {
key_values.emplace(BBL_JSON_KEY_DESCRIPTION, it.value()); key_values.emplace(BBL_JSON_KEY_DESCRIPTION, it.value());
} }
else if (ascii_iequals(it.key(), BBL_JSON_KEY_INSTANTIATION)) { else if (boost::iequals(it.key(), BBL_JSON_KEY_INSTANTIATION)) {
key_values.emplace(BBL_JSON_KEY_INSTANTIATION, it.value()); key_values.emplace(BBL_JSON_KEY_INSTANTIATION, it.value());
} }
else if (!load_inherits_to_config && ascii_iequals(it.key(), BBL_JSON_KEY_INHERITS)) { else if (!load_inherits_to_config && boost::iequals(it.key(), BBL_JSON_KEY_INHERITS)) {
key_values.emplace(BBL_JSON_KEY_INHERITS, it.value()); key_values.emplace(BBL_JSON_KEY_INHERITS, it.value());
} }
else if (!load_inherits_to_config && ascii_iequals(it.key(), BBL_JSON_KEY_INCLUDES)) { else if (!load_inherits_to_config && boost::iequals(it.key(), BBL_JSON_KEY_INCLUDES)) {
key_values.emplace(BBL_JSON_KEY_INCLUDES, it.value().dump()); key_values.emplace(BBL_JSON_KEY_INCLUDES, it.value().dump());
} else if (ascii_iequals(it.key(), ORCA_JSON_KEY_RENAMED_FROM)) { } else if (boost::iequals(it.key(), ORCA_JSON_KEY_RENAMED_FROM)) {
key_values.emplace(ORCA_JSON_KEY_RENAMED_FROM, it.value()); key_values.emplace(ORCA_JSON_KEY_RENAMED_FROM, it.value());
} else { } else {
t_config_option_key opt_key = it.key(); t_config_option_key opt_key = it.key();
+8 -12
View File
@@ -141,7 +141,7 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s
its.indices.emplace_back(indices[0], indices[1], indices[2]); its.indices.emplace_back(indices[0], indices[1], indices[2]);
int face_index =its.indices.size() - 1; int face_index =its.indices.size() - 1;
RGBA face_color; RGBA face_color;
auto set_face_color = [&uvs, &data, &mtl_data, &obj_info, &face_color](int face_index, const std::string mtl_name, const std::array<int, 3> &corners) { auto set_face_color = [&uvs, &data, &mtl_data, &obj_info, &face_color](int face_index, const std::string mtl_name) {
if (mtl_data.new_mtl_unmap.find(mtl_name) != mtl_data.new_mtl_unmap.end()) { if (mtl_data.new_mtl_unmap.find(mtl_name) != mtl_data.new_mtl_unmap.end()) {
bool is_merge_ka_kd = true; bool is_merge_ka_kd = true;
for (size_t n = 0; n < 3; n++) { for (size_t n = 0; n < 3; n++) {
@@ -174,7 +174,7 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s
return Vec2f::Zero(); return Vec2f::Zero();
return Vec2f(data.textureCoordinates[idx * OBJ_TEXCOORD_LENGTH], data.textureCoordinates[idx * OBJ_TEXCOORD_LENGTH + 1]); return Vec2f(data.textureCoordinates[idx * OBJ_TEXCOORD_LENGTH], data.textureCoordinates[idx * OBJ_TEXCOORD_LENGTH + 1]);
}; };
std::array<Vec2f, 3> uv_array{uv_at(uvs[corners[0]]), uv_at(uvs[corners[1]]), uv_at(uvs[corners[2]])}; std::array<Vec2f, 3> uv_array{uv_at(uvs[0]), uv_at(uvs[1]), uv_at(uvs[2])};
obj_info.uvs.emplace_back(uv_array); obj_info.uvs.emplace_back(uv_array);
} }
obj_info.face_colors.emplace_back(face_color); obj_info.face_colors.emplace_back(face_color);
@@ -185,27 +185,27 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s
} }
} }
}; };
auto set_face_color_by_mtl = [&data, &set_face_color](int face_index, const std::array<int, 3> &corners) { auto set_face_color_by_mtl = [&data, &set_face_color](int face_index) {
if (data.usemtls.size() == 1) { if (data.usemtls.size() == 1) {
set_face_color(face_index, data.usemtls[0].name, corners); set_face_color(face_index, data.usemtls[0].name);
} else { } else {
for (size_t k = 0; k < data.usemtls.size(); k++) { for (size_t k = 0; k < data.usemtls.size(); k++) {
auto mtl = data.usemtls[k]; auto mtl = data.usemtls[k];
if (face_index >= mtl.face_start && face_index <= mtl.face_end) { if (face_index >= mtl.face_start && face_index <= mtl.face_end) {
set_face_color(face_index, data.usemtls[k].name, corners); set_face_color(face_index, data.usemtls[k].name);
break; break;
} }
} }
} }
}; };
if (exist_mtl) { if (exist_mtl) {
set_face_color_by_mtl(face_index, {0, 1, 2}); set_face_color_by_mtl(face_index);
} }
if (cnt == 4) { if (cnt == 4) {
its.indices.emplace_back(indices[0], indices[2], indices[3]); its.indices.emplace_back(indices[0], indices[2], indices[3]);
int face_index = its.indices.size() - 1; int face_index = its.indices.size() - 1;
if (exist_mtl) { if (exist_mtl) {
set_face_color_by_mtl(face_index, {0, 2, 3}); set_face_color_by_mtl(face_index);
} }
} }
} }
@@ -217,12 +217,8 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s
message = _L("This OBJ file couldn't be read because it's empty."); message = _L("This OBJ file couldn't be read because it's empty.");
return false; return false;
} }
if (meshptr->volume() < 0) { if (meshptr->volume() < 0)
meshptr->flip_triangles(); meshptr->flip_triangles();
// Flipping swaps corners 1 and 2 of every face, so the UVs have to follow.
for (std::array<Vec2f, 3> &uv : obj_info.uvs)
std::swap(uv[1], uv[2]);
}
// Hand the parsed material table back so callers can build a TexturedMesh from it. // Hand the parsed material table back so callers can build a TexturedMesh from it.
if (out_mtl) if (out_mtl)
*out_mtl = mtl_data; *out_mtl = mtl_data;
+89 -71
View File
@@ -1497,7 +1497,12 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
check_add_eol(toolchange_gcode_str); check_add_eol(toolchange_gcode_str);
// SoftFever: set new PA for new filament // SoftFever: set new PA for new filament
gcode += gcodegen.set_filament_pressure_advance(new_filament_id); if (gcodegen.config().enable_pressure_advance.get_at(new_filament_id)) {
gcode += gcodegen.writer().set_pressure_advance(gcodegen.config().pressure_advance.get_at(new_filament_id));
// Orca: Adaptive PA
// Reset Adaptive PA processor last PA value
gcodegen.m_pa_processor->resetPreviousPA(gcodegen.config().pressure_advance.get_at(new_filament_id));
}
// A phony move to the end position at the wipe tower. // A phony move to the end position at the wipe tower.
gcodegen.writer().travel_to_xy((end_pos + plate_origin_2d).cast<double>()); gcodegen.writer().travel_to_xy((end_pos + plate_origin_2d).cast<double>());
@@ -1821,8 +1826,12 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
check_add_eol(toolchange_gcode_str); check_add_eol(toolchange_gcode_str);
// SoftFever: set new PA for new filament // SoftFever: set new PA for new filament
if (new_extruder_id != -1) if (new_extruder_id != -1 && gcodegen.config().enable_pressure_advance.get_at(new_extruder_id)) {
gcode += gcodegen.set_filament_pressure_advance(new_extruder_id); gcode += gcodegen.writer().set_pressure_advance(gcodegen.config().pressure_advance.get_at(new_extruder_id));
// Orca: Adaptive PA
// Reset Adaptive PA processor last PA value
gcodegen.m_pa_processor->resetPreviousPA(gcodegen.config().pressure_advance.get_at(new_extruder_id));
}
// A phony move to the end position at the wipe tower. // A phony move to the end position at the wipe tower.
gcodegen.writer().travel_to_xy((end_pos + plate_origin_2d).cast<double>()); gcodegen.writer().travel_to_xy((end_pos + plate_origin_2d).cast<double>());
@@ -3223,7 +3232,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
m_cooling_buffer->set_current_extruder(initial_extruder_id, extruder_id); m_cooling_buffer->set_current_extruder(initial_extruder_id, extruder_id);
// Orca: Initialise AdaptivePA processor filter // Orca: Initialise AdaptivePA processor filter
m_pa_processor = std::make_unique<AdaptivePAProcessor>(*this); m_pa_processor = std::make_unique<AdaptivePAProcessor>(*this, tool_ordering.all_extruders());
// Update output variables after the extruders were initialized. // Update output variables after the extruders were initialized.
m_placeholder_parser_integration.init(m_writer); m_placeholder_parser_integration.init(m_writer);
@@ -3788,7 +3797,12 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
} }
} }
// Orca: add missing PA settings for initial filament // Orca: add missing PA settings for initial filament
file.write(set_filament_pressure_advance(initial_non_support_extruder_id)); if (m_config.enable_pressure_advance.get_at(initial_non_support_extruder_id)) {
file.write(m_writer.set_pressure_advance(m_config.pressure_advance.get_at(initial_non_support_extruder_id)));
// Orca: Adaptive PA
// Reset Adaptive PA processor last PA value
m_pa_processor->resetPreviousPA(m_config.pressure_advance.get_at(initial_non_support_extruder_id));
}
} }
//flush FanMover buffer to avoid modifying the start gcode if it's manual. //flush FanMover buffer to avoid modifying the start gcode if it's manual.
@@ -4174,28 +4188,6 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
print.throw_if_canceled(); print.throw_if_canceled();
// Some firmwares only scan the last N lines for the time estimate, so the stats are written
// after the config block.
if (!is_bbl_printers && !skip_config_block) {
file.write("; CONFIG_BLOCK_START\n");
std::string full_config;
append_full_config(print, full_config);
if (!full_config.empty())
file.write(full_config);
// SoftFever: write compatiple info
int first_layer_bed_temperature = get_bed_temperature(0, true, print.config().curr_bed_type);
file.write_format("; first_layer_bed_temperature = %d\n", first_layer_bed_temperature);
file.write_format("; bed_shape = %s\n", print.full_print_config().opt_serialize("printable_area").c_str());
file.write_format("; first_layer_temperature = %d\n", print.config().nozzle_temperature_initial_layer.get_at(0));
file.write_format("; first_layer_height = %.3f\n", print.config().initial_layer_print_height.value);
//SF TODO
// file.write_format("; variable_layer_height = %d\n", print.ad.adaptive_layer_height ? 1 : 0);
file.write("; CONFIG_BLOCK_END\n\n");
}
// Get filament stats. // Get filament stats.
file.write(DoExport::update_print_stats_and_format_filament_stats( file.write(DoExport::update_print_stats_and_format_filament_stats(
// Const inputs // Const inputs
@@ -4222,6 +4214,26 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder) GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder)
.c_str()); .c_str());
file.write("\n"); file.write("\n");
if (!skip_config_block) {
file.write("; CONFIG_BLOCK_START\n");
std::string full_config;
append_full_config(print, full_config);
if (!full_config.empty())
file.write(full_config);
// SoftFever: write compatiple info
int first_layer_bed_temperature = get_bed_temperature(0, true, print.config().curr_bed_type);
file.write_format("; first_layer_bed_temperature = %d\n", first_layer_bed_temperature);
file.write_format("; bed_shape = %s\n", print.full_print_config().opt_serialize("printable_area").c_str());
file.write_format("; first_layer_temperature = %d\n", print.config().nozzle_temperature_initial_layer.get_at(0));
file.write_format("; first_layer_height = %.3f\n", print.config().initial_layer_print_height.value);
//SF TODO
// file.write_format("; variable_layer_height = %d\n", print.ad.adaptive_layer_height ? 1 : 0);
file.write("; CONFIG_BLOCK_END\n\n");
} // !skip_config_block
} }
file.write("\n"); file.write("\n");
@@ -8378,16 +8390,12 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// Orca: Dynamic PA // Orca: Dynamic PA
// If adaptive PA is enabled, by default evaluate PA on all extrusion moves // If adaptive PA is enabled, by default evaluate PA on all extrusion moves
// The PA settings and the PA_CHANGE tags use the filament config index of the filament's extruder variant
const unsigned int pa_config_index = (unsigned int) get_filament_config_index(m_writer.filament()->id());
const bool adaptive_pa = m_config.adaptive_pressure_advance.get_at(pa_config_index) && m_config.enable_pressure_advance.get_at(pa_config_index);
const bool adaptive_pa_overhangs = adaptive_pa && m_config.adaptive_pressure_advance_overhangs.get_at(pa_config_index);
bool is_pa_calib = m_curr_print->calib_mode() == CalibMode::Calib_PA_Line || bool is_pa_calib = m_curr_print->calib_mode() == CalibMode::Calib_PA_Line ||
m_curr_print->calib_mode() == CalibMode::Calib_PA_Pattern || m_curr_print->calib_mode() == CalibMode::Calib_PA_Pattern ||
m_curr_print->calib_mode() == CalibMode::Calib_PA_Tower; m_curr_print->calib_mode() == CalibMode::Calib_PA_Tower;
bool evaluate_adaptive_pa = false; bool evaluate_adaptive_pa = false;
bool role_change = (m_last_extrusion_role != path.role()); bool role_change = (m_last_extrusion_role != path.role());
if (!is_pa_calib && adaptive_pa) { if (!is_pa_calib && FILAMENT_CONFIG(adaptive_pressure_advance) && FILAMENT_CONFIG(enable_pressure_advance)) {
evaluate_adaptive_pa = true; evaluate_adaptive_pa = true;
// If we have already emmited a PA change because the m_multi_flow_segment_path_pa_set is set // If we have already emmited a PA change because the m_multi_flow_segment_path_pa_set is set
// skip re-issuing the PA change tag. // skip re-issuing the PA change tag.
@@ -8470,7 +8478,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// Post processor flag generation code segment when option to emit only at role changes is enabled // Post processor flag generation code segment when option to emit only at role changes is enabled
// Variables published to the post processor: // Variables published to the post processor:
// 1) Tag to trigger a PA evaluation (because a role change was identified and the user has requested dynamic PA adjustments) // 1) Tag to trigger a PA evaluation (because a role change was identified and the user has requested dynamic PA adjustments)
// 2) Current filament config index (to identify the PA model for the currently used filament and its extruder variant) // 2) Current extruder ID (to identify the PA model for the currently used extruder)
// 3) mm3_per_mm value (to then multiply by the final model print speed after slowdown for cooling is applied) // 3) mm3_per_mm value (to then multiply by the final model print speed after slowdown for cooling is applied)
// 4) the current acceleration (to pass to the model for evaluation) // 4) the current acceleration (to pass to the model for evaluation)
// 5) whether this is an external perimeter (for future use) // 5) whether this is an external perimeter (for future use)
@@ -8481,7 +8489,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
if (m_multi_flow_segment_path_average_mm3_per_mm > 0) { if (m_multi_flow_segment_path_average_mm3_per_mm > 0) {
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n", sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
pa_config_index, m_writer.filament()->id(),
m_multi_flow_segment_path_average_mm3_per_mm, m_multi_flow_segment_path_average_mm3_per_mm,
acceleration_i, acceleration_i,
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)), ((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
@@ -8494,7 +8502,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// to issue a zero flow PA change command for this // to issue a zero flow PA change command for this
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n", sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
pa_config_index, m_writer.filament()->id(),
_mm3_per_mm, _mm3_per_mm,
acceleration_i, acceleration_i,
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)), ((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
@@ -8602,7 +8610,11 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// or a flow change, so emit the flag to evaluate PA for the upcomming extrusion // or a flow change, so emit the flag to evaluate PA for the upcomming extrusion
// Emit tag before new speed is set so the post processor reads the next speed immediately and uses it. // Emit tag before new speed is set so the post processor reads the next speed immediately and uses it.
// Dont emit tag if it has just already been emitted from a role change above // Dont emit tag if it has just already been emitted from a role change above
if(_mm3_per_mm >0 && adaptive_pa_overhangs && !evaluate_adaptive_pa){ if(_mm3_per_mm >0 &&
FILAMENT_CONFIG(adaptive_pressure_advance) &&
FILAMENT_CONFIG(enable_pressure_advance) &&
FILAMENT_CONFIG(adaptive_pressure_advance_overhangs) &&
!evaluate_adaptive_pa){
if(writer().get_current_speed() > F){ // Ramping down speed - use overhang logic where the minimum speed is used between current and upcoming extrusion if(writer().get_current_speed() > F){ // Ramping down speed - use overhang logic where the minimum speed is used between current and upcoming extrusion
if(m_config.gcode_comments){ if(m_config.gcode_comments){
sprintf(buf, "; Ramp down-non-variable\n"); sprintf(buf, "; Ramp down-non-variable\n");
@@ -8610,7 +8622,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
} }
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n", sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
pa_config_index, m_writer.filament()->id(),
_mm3_per_mm, _mm3_per_mm,
acceleration_i, acceleration_i,
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)), ((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
@@ -8625,7 +8637,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
} }
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n", sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
pa_config_index, m_writer.filament()->id(),
_mm3_per_mm, _mm3_per_mm,
acceleration_i, acceleration_i,
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)), ((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
@@ -8831,7 +8843,10 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// ORCA: Adaptive PA code segment when adjusting PA within the same feature // ORCA: Adaptive PA code segment when adjusting PA within the same feature
// There is a speed change or flow change so emit the flag to evaluate PA for the upcomming extrusion // There is a speed change or flow change so emit the flag to evaluate PA for the upcomming extrusion
// Emit tag before new speed is set so the post processor reads the next speed immediately and uses it. // Emit tag before new speed is set so the post processor reads the next speed immediately and uses it.
if(_mm3_per_mm >0 && adaptive_pa_overhangs){ if(_mm3_per_mm >0 &&
EXTRUDER_CONFIG(adaptive_pressure_advance) &&
EXTRUDER_CONFIG(enable_pressure_advance) &&
EXTRUDER_CONFIG(adaptive_pressure_advance_overhangs) ){
if(last_set_speed > new_speed){ // Ramping down speed - use overhang logic where the minimum speed is used between current and upcoming extrusion if(last_set_speed > new_speed){ // Ramping down speed - use overhang logic where the minimum speed is used between current and upcoming extrusion
if(m_config.gcode_comments) { if(m_config.gcode_comments) {
sprintf(buf, "; Ramp up-variable\n"); sprintf(buf, "; Ramp up-variable\n");
@@ -8839,7 +8854,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
} }
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n", sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
pa_config_index, m_writer.filament()->id(),
_mm3_per_mm, _mm3_per_mm,
acceleration_i, acceleration_i,
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)), ((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
@@ -8854,7 +8869,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
} }
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n", sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
pa_config_index, m_writer.filament()->id(),
_mm3_per_mm, _mm3_per_mm,
acceleration_i, acceleration_i,
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)), ((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
@@ -9407,29 +9422,33 @@ void GCode::update_placeholder_parser_with_variant_params()
if (num_filaments == 0) if (num_filaments == 0)
return; return;
// Helper: remap config arrays from variant index space to filament_id index space. // Helpers: remap config arrays from variant index space to filament_id index space.
// After remapping, gcode templates can use param[filament_id] directly. // After remapping, gcode templates can use param[filament_id] directly.
auto remap_by_filament = [&](const auto &src) { auto remap_floats_by_filament = [&](const auto &src) {
std::decay_t<decltype(src.values)> dst(num_filaments); std::vector<double> dst(num_filaments);
for (size_t i = 0; i < num_filaments; ++i)
dst[i] = src.get_at(get_filament_config_index(i));
return dst;
};
auto remap_ints_by_filament = [&](const auto &src) {
std::vector<int> dst(num_filaments);
for (size_t i = 0; i < num_filaments; ++i) for (size_t i = 0; i < num_filaments; ++i)
dst[i] = src.get_at(get_filament_config_index(i)); dst[i] = src.get_at(get_filament_config_index(i));
return dst; return dst;
}; };
// --- filament_options_with_variant: gcode indexes by filament_id --- // --- filament_options_with_variant: gcode indexes by filament_id ---
this->placeholder_parser().set("filament_max_volumetric_speed", new ConfigOptionFloats(remap_by_filament(m_config.filament_max_volumetric_speed))); this->placeholder_parser().set("filament_max_volumetric_speed", new ConfigOptionFloats(remap_floats_by_filament(m_config.filament_max_volumetric_speed)));
this->placeholder_parser().set("filament_pre_cooling_temperature", new ConfigOptionInts(remap_by_filament(m_config.filament_pre_cooling_temperature))); this->placeholder_parser().set("filament_pre_cooling_temperature", new ConfigOptionInts(remap_ints_by_filament(m_config.filament_pre_cooling_temperature)));
this->placeholder_parser().set("filament_pre_cooling_temperature_nc", new ConfigOptionInts(remap_by_filament(m_config.filament_pre_cooling_temperature_nc))); this->placeholder_parser().set("filament_pre_cooling_temperature_nc", new ConfigOptionInts(remap_ints_by_filament(m_config.filament_pre_cooling_temperature_nc)));
this->placeholder_parser().set("filament_cooling_before_tower", new ConfigOptionFloats(remap_by_filament(m_config.filament_cooling_before_tower))); this->placeholder_parser().set("filament_cooling_before_tower", new ConfigOptionFloats(remap_floats_by_filament(m_config.filament_cooling_before_tower)));
this->placeholder_parser().set("nozzle_temperature_initial_layer", new ConfigOptionInts(remap_by_filament(m_config.nozzle_temperature_initial_layer))); this->placeholder_parser().set("nozzle_temperature_initial_layer", new ConfigOptionInts(remap_ints_by_filament(m_config.nozzle_temperature_initial_layer)));
this->placeholder_parser().set("nozzle_temperature", new ConfigOptionInts(remap_by_filament(m_config.nozzle_temperature))); this->placeholder_parser().set("nozzle_temperature", new ConfigOptionInts(remap_ints_by_filament(m_config.nozzle_temperature)));
// first_layer_temperature is a legacy alias of nozzle_temperature_initial_layer // first_layer_temperature is a legacy alias of nozzle_temperature_initial_layer
this->placeholder_parser().set("first_layer_temperature", new ConfigOptionInts(remap_by_filament(m_config.nozzle_temperature_initial_layer))); this->placeholder_parser().set("first_layer_temperature", new ConfigOptionInts(remap_ints_by_filament(m_config.nozzle_temperature_initial_layer)));
this->placeholder_parser().set("pressure_advance", new ConfigOptionFloats(remap_by_filament(m_config.pressure_advance)));
this->placeholder_parser().set("enable_pressure_advance", new ConfigOptionBools(remap_by_filament(m_config.enable_pressure_advance)));
// --- printer_options_with_variant_1: in m_config these are already merged as filament-indexed --- // --- printer_options_with_variant_1: in m_config these are already merged as filament-indexed ---
this->placeholder_parser().set("retraction_distances_when_cut", new ConfigOptionFloats(remap_by_filament(m_config.retraction_distances_when_cut))); this->placeholder_parser().set("retraction_distances_when_cut", new ConfigOptionFloats(remap_floats_by_filament(m_config.retraction_distances_when_cut)));
// hotend_cooling_rate / hotend_heating_rate: gcode uses [filament_map[x]-1] (extruder_id), no remap needed // hotend_cooling_rate / hotend_heating_rate: gcode uses [filament_map[x]-1] (extruder_id), no remap needed
// --- filament_map: per-layer dynamic, sync from m_config to placeholder_parser --- // --- filament_map: per-layer dynamic, sync from m_config to placeholder_parser ---
@@ -9439,10 +9458,10 @@ void GCode::update_placeholder_parser_with_variant_params()
{ {
// Fast purge mode uses filament_flush_temp_fast; Default is inert. // Fast purge mode uses filament_flush_temp_fast; Default is inert.
bool use_fast_flush = m_config.prime_volume_mode == PrimeVolumeMode::pvmFast; bool use_fast_flush = m_config.prime_volume_mode == PrimeVolumeMode::pvmFast;
auto flush_v_speed = remap_by_filament(m_config.filament_flush_volumetric_speed); auto flush_v_speed = remap_floats_by_filament(m_config.filament_flush_volumetric_speed);
auto filament_max_v = remap_by_filament(m_config.filament_max_volumetric_speed); auto filament_max_v = remap_floats_by_filament(m_config.filament_max_volumetric_speed);
auto flush_temps = remap_by_filament(use_fast_flush ? m_config.filament_flush_temp_fast auto flush_temps = remap_ints_by_filament(use_fast_flush ? m_config.filament_flush_temp_fast
: m_config.filament_flush_temp); : m_config.filament_flush_temp);
for (size_t i = 0; i < num_filaments; ++i) { for (size_t i = 0; i < num_filaments; ++i) {
if (flush_v_speed[i] == 0) if (flush_v_speed[i] == 0)
flush_v_speed[i] = filament_max_v[i]; flush_v_speed[i] = filament_max_v[i];
@@ -9494,7 +9513,12 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
gcode += this->placeholder_parser_process("filament_start_gcode", filament_start_gcode, new_filament_id, &config); gcode += this->placeholder_parser_process("filament_start_gcode", filament_start_gcode, new_filament_id, &config);
check_add_eol(gcode); check_add_eol(gcode);
} }
gcode += set_filament_pressure_advance(new_filament_id); if (m_config.enable_pressure_advance.get_at(new_filament_id)) {
gcode += m_writer.set_pressure_advance(m_config.pressure_advance.get_at(new_filament_id));
// Orca: Adaptive PA
// Reset Adaptive PA processor last PA value
m_pa_processor->resetPreviousPA(m_config.pressure_advance.get_at(new_filament_id));
}
gcode += m_writer.toolchange(new_filament_id, new_extruder_id); gcode += m_writer.toolchange(new_filament_id, new_extruder_id);
if (Extruder *fil = m_writer.filament()) if (Extruder *fil = m_writer.filament())
@@ -9885,24 +9909,18 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
if (m_ooze_prevention.enable && !defer_temp_wait) if (m_ooze_prevention.enable && !defer_temp_wait)
gcode += m_ooze_prevention.post_toolchange(*this); gcode += m_ooze_prevention.post_toolchange(*this);
gcode += set_filament_pressure_advance(new_filament_id); if (m_config.enable_pressure_advance.get_at(new_filament_id)) {
gcode += m_writer.set_pressure_advance(m_config.pressure_advance.get_at(new_filament_id));
// Orca: Adaptive PA
// Reset Adaptive PA processor last PA value
m_pa_processor->resetPreviousPA(m_config.pressure_advance.get_at(new_filament_id));
}
//Orca: tool changer or IDEX's firmware may change Z position, so we set it to unknown/undefined //Orca: tool changer or IDEX's firmware may change Z position, so we set it to unknown/undefined
m_last_pos_defined = false; m_last_pos_defined = false;
return gcode; return gcode;
} }
std::string GCode::set_filament_pressure_advance(unsigned int filament_id)
{
const size_t fi = get_filament_config_index(filament_id);
if (!m_config.enable_pressure_advance.get_at(fi))
return {};
// Orca: Adaptive PA
// Reset Adaptive PA processor last PA value
m_pa_processor->resetPreviousPA(m_config.pressure_advance.get_at(fi));
return m_writer.set_pressure_advance(m_config.pressure_advance.get_at(fi));
}
inline std::string polygon_to_string(const Polygon &polygon, Print *print, bool is_print_space = false) { inline std::string polygon_to_string(const Polygon &polygon, Print *print, bool is_print_space = false) {
std::ostringstream gcode; std::ostringstream gcode;
gcode << "["; gcode << "[";
-2
View File
@@ -281,8 +281,6 @@ public:
// extra_retract forwards a PETG pre-extrusion over-extrusion; default 0 -> identical to the plain deretract. // extra_retract forwards a PETG pre-extrusion over-extrusion; default 0 -> identical to the plain deretract.
std::string unretract(float extra_retract = 0.f) { return m_writer.unlift() + m_writer.unretract(extra_retract); } std::string unretract(float extra_retract = 0.f) { return m_writer.unlift() + m_writer.unretract(extra_retract); }
std::string set_extruder(unsigned int extruder_id, double print_z, bool by_object=false, int toolchange_temp_override = -1, bool defer_temp_wait = false); std::string set_extruder(unsigned int extruder_id, double print_z, bool by_object=false, int toolchange_temp_override = -1, bool defer_temp_wait = false);
// Sets the pressure advance of the filament's extruder variant, if enabled for it.
std::string set_filament_pressure_advance(unsigned int filament_id);
bool is_BBL_Printer(); bool is_BBL_Printer();
WipeTowerType wipe_tower_type(); WipeTowerType wipe_tower_type();
+33 -26
View File
@@ -21,30 +21,38 @@ namespace Slic3r {
* *
* @param gcodegen A reference to the GCode object that generates the G-code. * @param gcodegen A reference to the GCode object that generates the G-code.
*/ */
AdaptivePAProcessor::AdaptivePAProcessor(GCode &gcodegen) AdaptivePAProcessor::AdaptivePAProcessor(GCode &gcodegen, const std::vector<unsigned int> &tools_used)
: m_gcodegen(gcodegen), : m_gcodegen(gcodegen),
m_config(gcodegen.config()), m_config(gcodegen.config()),
m_last_predicted_pa(0.0), m_last_predicted_pa(0.0),
m_max_next_feedrate(0.0), m_max_next_feedrate(0.0),
m_next_feedrate(0.0), m_next_feedrate(0.0),
m_current_feedrate(0.0), m_current_feedrate(0.0),
m_last_config_index(-1), m_last_extruder_id(-1),
m_pa_change_pattern(R"(; PA_CHANGE:T(\d+) MM3MM:([0-9]*\.[0-9]+) ACCEL:(\d+) BR:(\d+) RC:(\d+) OV:(\d+))"), m_pa_change_pattern(R"(; PA_CHANGE:T(\d+) MM3MM:([0-9]*\.[0-9]+) ACCEL:(\d+) BR:(\d+) RC:(\d+) OV:(\d+))"),
m_g1_f_pattern(R"(G1 F([0-9]+))") m_g1_f_pattern(R"(G1 F([0-9]+))")
{ {
// Constructor body can be used for further initialization if necessary
for (unsigned int tool : tools_used) {
// Only enable model for the tool if both PA and adaptive PA options are enabled
if(m_config.adaptive_pressure_advance.get_at(tool) && m_config.enable_pressure_advance.get_at(tool)){
auto interpolator = std::make_unique<AdaptivePAInterpolator>();
// Get calibration values from extruder
std::string pa_calibration_values = m_config.adaptive_pressure_advance_model.get_at(tool);
// Setup the model and store it in the tool-interpolation model map
interpolator->parseAndSetData(pa_calibration_values);
m_AdaptivePAInterpolators[tool] = std::move(interpolator);
}
}
} }
// Method to get the interpolator for a specific filament config index. // Method to get the interpolator for a specific tool ID
// The model is built the first time an index is requested, as the indices in use depend on AdaptivePAInterpolator* AdaptivePAProcessor::getInterpolator(unsigned int tool_id) {
// the extruder variant each filament prints with. auto it = m_AdaptivePAInterpolators.find(tool_id);
AdaptivePAInterpolator* AdaptivePAProcessor::getInterpolator(unsigned int config_index) { if (it != m_AdaptivePAInterpolators.end()) {
auto [it, inserted] = m_AdaptivePAInterpolators.try_emplace(config_index); return it->second.get();
// Only enable model for the index if both PA and adaptive PA options are enabled
if (inserted && m_config.adaptive_pressure_advance.get_at(config_index) && m_config.enable_pressure_advance.get_at(config_index)) {
it->second = std::make_unique<AdaptivePAInterpolator>();
it->second->parseAndSetData(m_config.adaptive_pressure_advance_model.get_at(config_index));
} }
return it->second.get(); return nullptr; // Handle the case where the tool_id was not found
} }
/** /**
@@ -100,17 +108,16 @@ std::string AdaptivePAProcessor::process_layer(std::string &&gcode) {
// the PA for that material is set. As no tag below will be found for this extruder, the original PA is retained. // the PA for that material is set. As no tag below will be found for this extruder, the original PA is retained.
if (line.find("; PA_CHANGE") == 0) { // prune lines quickly before running regex check as regex is more expensive to run if (line.find("; PA_CHANGE") == 0) { // prune lines quickly before running regex check as regex is more expensive to run
if (std::regex_search(line, m_match, m_pa_change_pattern)) { if (std::regex_search(line, m_match, m_pa_change_pattern)) {
// The tag carries the filament config index, which selects the PA settings of the filament's extruder variant int extruder_id = std::stoi(m_match[1].str());
int config_index = std::stoi(m_match[1].str());
mm3mm_value = std::stod(m_match[2].str()); mm3mm_value = std::stod(m_match[2].str());
accel_value = std::stod(m_match[3].str()); accel_value = std::stod(m_match[3].str());
int isBridge = std::stoi(m_match[4].str()); int isBridge = std::stoi(m_match[4].str());
int roleChange = std::stoi(m_match[5].str()); int roleChange = std::stoi(m_match[5].str());
int isOverhang = std::stoi(m_match[6].str()); int isOverhang = std::stoi(m_match[6].str());
// Check if the filament config index has changed // Check if the extruder ID has changed
bool config_index_changed = (config_index != m_last_config_index); bool extruder_changed = (extruder_id != m_last_extruder_id);
m_last_config_index = config_index; m_last_extruder_id = extruder_id;
// Save the PA_CHANGE line to output later after finding feedrate // Save the PA_CHANGE line to output later after finding feedrate
pa_change_line = line; pa_change_line = line;
@@ -205,23 +212,23 @@ std::string AdaptivePAProcessor::process_layer(std::string &&gcode) {
// Calculate the predicted PA using the upcomming feature maximum feedrate // Calculate the predicted PA using the upcomming feature maximum feedrate
// Get the interpolator for the active tool // Get the interpolator for the active tool
AdaptivePAInterpolator* interpolator = getInterpolator(m_last_config_index); AdaptivePAInterpolator* interpolator = getInterpolator(m_last_extruder_id);
double predicted_pa = 0; double predicted_pa = 0;
double adaptive_PA_speed = 0; double adaptive_PA_speed = 0;
if(!interpolator){ // Tool not found in the interpolator map if(!interpolator){ // Tool not found in the interpolator map
// Tool not found in the PA interpolator to tool map // Tool not found in the PA interpolator to tool map
predicted_pa = m_config.enable_pressure_advance.get_at(m_last_config_index) ? m_config.pressure_advance.get_at(m_last_config_index) : 0; predicted_pa = m_config.enable_pressure_advance.get_at(m_last_extruder_id) ? m_config.pressure_advance.get_at(m_last_extruder_id) : 0;
if(m_config.gcode_comments) output << "; APA: Tool doesnt have APA enabled\n"; if(m_config.gcode_comments) output << "; APA: Tool doesnt have APA enabled\n";
} else if (!interpolator->isInitialised() || (!m_config.adaptive_pressure_advance.get_at(m_last_config_index)) ) } else if (!interpolator->isInitialised() || (!m_config.adaptive_pressure_advance.get_at(m_last_extruder_id)) )
// Check if the model is not initialised by the constructor for the active extruder // Check if the model is not initialised by the constructor for the active extruder
// Also check that adaptive PA is enabled for that extruder. This should not be needed // Also check that adaptive PA is enabled for that extruder. This should not be needed
// as the PA change flag should not be set upstream (in the GCode.cpp file) if adaptive PA is disabled // as the PA change flag should not be set upstream (in the GCode.cpp file) if adaptive PA is disabled
// however check for robustness sake. // however check for robustness sake.
{ {
// Model failed or adaptive pressure advance not enabled - use default value from m_config // Model failed or adaptive pressure advance not enabled - use default value from m_config
predicted_pa = m_config.enable_pressure_advance.get_at(m_last_config_index) ? m_config.pressure_advance.get_at(m_last_config_index) : 0; predicted_pa = m_config.enable_pressure_advance.get_at(m_last_extruder_id) ? m_config.pressure_advance.get_at(m_last_extruder_id) : 0;
if(m_config.gcode_comments) output << "; APA: Interpolator setup failed, using default pressure advance\n"; if(m_config.gcode_comments) output << "; APA: Interpolator setup failed, using default pressure advance\n";
} else { // Model setup succeeded } else { // Model setup succeeded
// Proceed to identify the print speed to use to calculate the adaptive PA value // Proceed to identify the print speed to use to calculate the adaptive PA value
@@ -242,18 +249,18 @@ std::string AdaptivePAProcessor::process_layer(std::string &&gcode) {
predicted_pa = (*interpolator)(mm3mm_value * adaptive_PA_speed, accel_value); predicted_pa = (*interpolator)(mm3mm_value * adaptive_PA_speed, accel_value);
// This is a bridge, use the dedicated PA setting. // This is a bridge, use the dedicated PA setting.
if(isBridge && m_config.adaptive_pressure_advance_bridges.get_at(m_last_config_index) > EPSILON) if(isBridge && m_config.adaptive_pressure_advance_bridges.get_at(m_last_extruder_id) > EPSILON)
predicted_pa = m_config.adaptive_pressure_advance_bridges.get_at(m_last_config_index); predicted_pa = m_config.adaptive_pressure_advance_bridges.get_at(m_last_extruder_id);
if (predicted_pa < 0) { // If extrapolation fails, fall back to the default PA for the extruder. if (predicted_pa < 0) { // If extrapolation fails, fall back to the default PA for the extruder.
predicted_pa = m_config.enable_pressure_advance.get_at(m_last_config_index) ? m_config.pressure_advance.get_at(m_last_config_index) : 0; predicted_pa = m_config.enable_pressure_advance.get_at(m_last_extruder_id) ? m_config.pressure_advance.get_at(m_last_extruder_id) : 0;
if(m_config.gcode_comments) output << "; APA: Interpolation failed, using fallback pressure advance value\n"; if(m_config.gcode_comments) output << "; APA: Interpolation failed, using fallback pressure advance value\n";
} }
} }
if(m_config.gcode_comments) { if(m_config.gcode_comments) {
// Output debug GCode comments // Output debug GCode comments
output << pa_change_line << '\n'; // Output PA change command tag output << pa_change_line << '\n'; // Output PA change command tag
if(isBridge && m_config.adaptive_pressure_advance_bridges.get_at(m_last_config_index) > EPSILON) if(isBridge && m_config.adaptive_pressure_advance_bridges.get_at(m_last_extruder_id) > EPSILON)
output << "; APA Model Override (bridge)\n"; output << "; APA Model Override (bridge)\n";
output << "; APA Current Speed: " << std::to_string(m_current_feedrate) << "\n"; output << "; APA Current Speed: " << std::to_string(m_current_feedrate) << "\n";
output << "; APA Next Speed: " << std::to_string(m_next_feedrate) << "\n"; output << "; APA Next Speed: " << std::to_string(m_next_feedrate) << "\n";
@@ -262,7 +269,7 @@ std::string AdaptivePAProcessor::process_layer(std::string &&gcode) {
output << "; APA Flow rate: " << std::to_string(mm3mm_value * m_max_next_feedrate) << "\n"; output << "; APA Flow rate: " << std::to_string(mm3mm_value * m_max_next_feedrate) << "\n";
output << "; APA Prev PA: " << std::to_string(m_last_predicted_pa) << " New PA: " << std::to_string(predicted_pa) << "\n"; output << "; APA Prev PA: " << std::to_string(m_last_predicted_pa) << " New PA: " << std::to_string(predicted_pa) << "\n";
} }
if (config_index_changed || std::fabs(predicted_pa - m_last_predicted_pa) > EPSILON) { if (extruder_changed || std::fabs(predicted_pa - m_last_predicted_pa) > EPSILON) {
output << m_gcodegen.writer().set_pressure_advance(predicted_pa); // Use m_writer to set pressure advance output << m_gcodegen.writer().set_pressure_advance(predicted_pa); // Use m_writer to set pressure advance
m_last_predicted_pa = predicted_pa; // Update the last predicted PA value m_last_predicted_pa = predicted_pa; // Update the last predicted PA value
} }
+11 -7
View File
@@ -11,6 +11,7 @@
#include <regex> #include <regex>
#include <memory> #include <memory>
#include <map> #include <map>
#include <vector>
#include "AdaptivePAInterpolator.hpp" #include "AdaptivePAInterpolator.hpp"
namespace Slic3r { namespace Slic3r {
@@ -32,7 +33,7 @@ public:
* *
* @param gcodegen A reference to the GCode object that generates the G-code. * @param gcodegen A reference to the GCode object that generates the G-code.
*/ */
AdaptivePAProcessor(GCode &gcodegen); AdaptivePAProcessor(GCode &gcodegen, const std::vector<unsigned int> &tools_used);
/** /**
* @brief Processes a layer of G-code and applies adaptive pressure advance. * @brief Processes a layer of G-code and applies adaptive pressure advance.
@@ -69,25 +70,28 @@ public:
private: private:
GCode &m_gcodegen; ///< Reference to the GCode object. GCode &m_gcodegen; ///< Reference to the GCode object.
std::unordered_map<unsigned int, std::unique_ptr<AdaptivePAInterpolator>> m_AdaptivePAInterpolators; ///< Map between Interpolator objects and filament config indices (null when adaptive PA is off) std::unordered_map<unsigned int, std::unique_ptr<AdaptivePAInterpolator>> m_AdaptivePAInterpolators; ///< Map between Interpolator objects and tool ID's
const PrintConfig &m_config; ///< Reference to the print configuration. const PrintConfig &m_config; ///< Reference to the print configuration.
double m_last_predicted_pa; ///< Last predicted pressure advance value. double m_last_predicted_pa; ///< Last predicted pressure advance value.
double m_max_next_feedrate; ///< Maximum feed rate (speed) for the upcomming island. If no speed is found, the previous island speed is used. double m_max_next_feedrate; ///< Maximum feed rate (speed) for the upcomming island. If no speed is found, the previous island speed is used.
double m_next_feedrate; ///< First feed rate (speed) for the upcomming island. double m_next_feedrate; ///< First feed rate (speed) for the upcomming island.
double m_current_feedrate; ///< Current, latest feedrate. double m_current_feedrate; ///< Current, latest feedrate.
int m_last_config_index; ///< Filament config index of the last PA_CHANGE tag. int m_last_extruder_id; ///< Last used extruder ID.
std::regex m_pa_change_pattern; ///< Regular expression to detect PA_CHANGE pattern. std::regex m_pa_change_pattern; ///< Regular expression to detect PA_CHANGE pattern.
std::regex m_g1_f_pattern; ///< Regular expression to detect G1 F pattern. std::regex m_g1_f_pattern; ///< Regular expression to detect G1 F pattern.
std::smatch m_match; ///< Match results for regular expressions. std::smatch m_match; ///< Match results for regular expressions.
/** /**
* @brief Get the PA interpolator attached to the specified filament config index. * @brief Get the PA interpolator attached to the specified tool ID.
* *
* @param config_index The filament config index (one per filament and extruder variant) for which the PA interpolation model is to be returned. * This method manually sets the adaptive PA internally held value.
* @return The Adaptive PA Interpolator object corresponding to that index, or nullptr when adaptive PA is off for it. * Call this when changing tools or in any other case where the internally assumed last PA value may be incorrect
*
* @param An integer with the tool ID for which the PA interpolation model is to be returned.
* @return The Adaptive PA Interpolator object corresponding to that tool.
*/ */
AdaptivePAInterpolator* getInterpolator(unsigned int config_index); AdaptivePAInterpolator* getInterpolator(unsigned int tool_id);
}; };
} // namespace Slic3r } // namespace Slic3r
-16
View File
@@ -13,19 +13,8 @@
namespace Slic3r { namespace Slic3r {
// How many setters this thread holds, so the ones nested in another can skip
// setlocale, which takes a lock the whole process shares on Windows.
static thread_local int s_numeric_locale_depth = 0;
CNumericLocalesSetter::CNumericLocalesSetter() CNumericLocalesSetter::CNumericLocalesSetter()
{ {
// Nested in another setter on this thread, whose "C" the separator check
// confirms is still set.
if (s_numeric_locale_depth > 0 && is_decimal_separator_point()) {
m_nested = true;
++ s_numeric_locale_depth;
return;
}
#ifdef _WIN32 #ifdef _WIN32
_configthreadlocale(_ENABLE_PER_THREAD_LOCALE); _configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
m_orig_numeric_locale = std::setlocale(LC_NUMERIC, nullptr); m_orig_numeric_locale = std::setlocale(LC_NUMERIC, nullptr);
@@ -40,17 +29,12 @@ CNumericLocalesSetter::CNumericLocalesSetter()
m_new_locale = newlocale(LC_NUMERIC_MASK, "C", m_new_locale); m_new_locale = newlocale(LC_NUMERIC_MASK, "C", m_new_locale);
uselocale(m_new_locale); uselocale(m_new_locale);
#endif #endif
// Counted last, since the destructor does not run for a constructor that throws.
++ s_numeric_locale_depth;
} }
CNumericLocalesSetter::~CNumericLocalesSetter() CNumericLocalesSetter::~CNumericLocalesSetter()
{ {
-- s_numeric_locale_depth;
if (m_nested)
return;
#ifdef _WIN32 #ifdef _WIN32
std::setlocale(LC_NUMERIC, m_orig_numeric_locale.data()); std::setlocale(LC_NUMERIC, m_orig_numeric_locale.data());
#else #else
-5
View File
@@ -19,13 +19,8 @@ class CNumericLocalesSetter {
public: public:
CNumericLocalesSetter(); CNumericLocalesSetter();
~CNumericLocalesSetter(); ~CNumericLocalesSetter();
// A copy would restore the locale twice, and count down once more than up.
CNumericLocalesSetter(const CNumericLocalesSetter&) = delete;
CNumericLocalesSetter& operator=(const CNumericLocalesSetter&) = delete;
private: private:
// Inside another setter on this thread, which does the setting and restoring.
bool m_nested { false };
#ifdef _WIN32 #ifdef _WIN32
std::string m_orig_numeric_locale; std::string m_orig_numeric_locale;
#else #else
+24 -48
View File
@@ -54,43 +54,9 @@ static void png_read_callback(png_struct *png_ptr,
// Retrieve our input buffer through the png_ptr // Retrieve our input buffer through the png_ptr
auto reader = static_cast<IStream *>(png_get_io_ptr(png_ptr)); auto reader = static_cast<IStream *>(png_get_io_ptr(png_ptr));
// libpng expects a short read to be reported through png_error(); returning quietly would leave if (!reader || !reader->is_ok()) return;
// it decoding whatever happened to be in outBytes.
if (!reader || !reader->is_ok() ||
reader->read(static_cast<std::uint8_t *>(outBytes), byteCountToRead) != byteCountToRead)
png_error(png_ptr, "PNG data is truncated");
}
// libpng reports a corrupt or truncated image by longjmp()ing back to the jump buffer set with reader->read(static_cast<std::uint8_t *>(outBytes), byteCountToRead);
// setjmp(). The frame it lands in must own nothing that needs destroying: with exceptions enabled
// MSVC unwinds the stack as part of longjmp, and returning from a frame unwound that way crashes -
// which is what a truncated texture did on Windows while working everywhere else. So the calls that
// can fail live in these two helpers, which hold nothing but pointers, and every C++ object the
// decoders need stays in their own frames.
static bool png_read_header_guarded(png_struct *png, png_info *info, IStream *in_buf, int sig_bytes)
{
if (setjmp(png_jmpbuf(png)))
return false;
png_set_read_fn(png, static_cast<void *>(in_buf), png_read_callback);
// Tell that we have already read the first bytes to check the signature
png_set_sig_bytes(png, sig_bytes);
png_read_info(png, info);
return true;
}
// `bottom_up` fills the buffer last row first, which is the order the colour decoder hands back.
static bool png_read_rows_guarded(png_struct *png, png_info *info, png_bytep dst, size_t rows, size_t rowbytes,
bool bottom_up, bool read_end)
{
if (setjmp(png_jmpbuf(png)))
return false;
for (size_t i = 0; i < rows; ++i)
png_read_row(png, dst + (bottom_up ? rows - 1 - i : i) * rowbytes, nullptr);
if (read_end)
png_read_end(png, info);
return true;
} }
bool decode_png(IStream &in_buf, ImageGreyscale &out_img) bool decode_png(IStream &in_buf, ImageGreyscale &out_img)
@@ -111,8 +77,12 @@ bool decode_png(IStream &in_buf, ImageGreyscale &out_img)
dsc.info = png_create_info_struct(dsc.png); dsc.info = png_create_info_struct(dsc.png);
if(!dsc.info) return false; if(!dsc.info) return false;
if (!png_read_header_guarded(dsc.png, dsc.info, &in_buf, PNG_SIG_BYTES)) png_set_read_fn(dsc.png, static_cast<void *>(&in_buf), png_read_callback);
return false;
// Tell that we have already read the first bytes to check the signature
png_set_sig_bytes(dsc.png, PNG_SIG_BYTES);
png_read_info(dsc.png, dsc.info);
out_img.cols = png_get_image_width(dsc.png, dsc.info); out_img.cols = png_get_image_width(dsc.png, dsc.info);
out_img.rows = png_get_image_height(dsc.png, dsc.info); out_img.rows = png_get_image_height(dsc.png, dsc.info);
@@ -124,8 +94,11 @@ bool decode_png(IStream &in_buf, ImageGreyscale &out_img)
out_img.buf.resize(out_img.rows * out_img.cols); out_img.buf.resize(out_img.rows * out_img.cols);
return png_read_rows_guarded(dsc.png, dsc.info, static_cast<png_bytep>(out_img.buf.data()), out_img.rows, auto readbuf = static_cast<png_bytep>(out_img.buf.data());
out_img.cols, /* bottom_up */ false, /* read_end */ false); for (size_t r = 0; r < out_img.rows; ++r)
png_read_row(dsc.png, readbuf + r * out_img.cols, nullptr);
return true;
} }
bool decode_colored_png(IStream &in_buf, ImageColorscale &out_img) bool decode_colored_png(IStream &in_buf, ImageColorscale &out_img)
@@ -155,10 +128,12 @@ bool decode_colored_png(IStream &in_buf, ImageColorscale &out_img)
return false; return false;
} }
if (!png_read_header_guarded(dsc.png, dsc.info, &in_buf, PNG_SIG_BYTES)) { png_set_read_fn(dsc.png, static_cast<void *>(&in_buf), png_read_callback);
BOOST_LOG_TRIVIAL(error) << "decode_colored_png: corrupt or truncated PNG data";
return false; // Tell that we have already read the first bytes to check the signature
} png_set_sig_bytes(dsc.png, PNG_SIG_BYTES);
png_read_info(dsc.png, dsc.info);
out_img.cols = png_get_image_width(dsc.png, dsc.info); out_img.cols = png_get_image_width(dsc.png, dsc.info);
out_img.rows = png_get_image_height(dsc.png, dsc.info); out_img.rows = png_get_image_height(dsc.png, dsc.info);
@@ -187,12 +162,13 @@ bool decode_colored_png(IStream &in_buf, ImageColorscale &out_img)
int interlace_type = png_get_interlace_type(dsc.png, dsc.info); int interlace_type = png_get_interlace_type(dsc.png, dsc.info);
BOOST_LOG_TRIVIAL(info) << boost::format("filter_type %1%, compression_type %2%, interlace_type %3%, rowbytes %4%")%filter_type %compression_type %interlace_type %rowbytes; BOOST_LOG_TRIVIAL(info) << boost::format("filter_type %1%, compression_type %2%, interlace_type %3%, rowbytes %4%")%filter_type %compression_type %interlace_type %rowbytes;
if (!png_read_rows_guarded(dsc.png, dsc.info, static_cast<png_bytep>(out_img.buf.data()), out_img.rows, rowbytes, auto readbuf = static_cast<png_bytep>(out_img.buf.data());
/* bottom_up */ true, /* read_end */ true)) { for (size_t r = out_img.rows; r > 0; r--)
BOOST_LOG_TRIVIAL(error) << "decode_colored_png: corrupt or truncated PNG data"; {
return false; png_read_row(dsc.png, readbuf + (r - 1) * rowbytes, nullptr);
} }
png_read_end(dsc.png, dsc.info);
png_destroy_read_struct(&dsc.png, &dsc.info, NULL); png_destroy_read_struct(&dsc.png, &dsc.info, NULL);
return true; return true;
-65
View File
@@ -1,65 +0,0 @@
#pragma once
#include <algorithm>
#include <cstddef>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>
#include <tbb/blocked_range.h>
#include <tbb/parallel_for.h>
#include <tbb/task_arena.h>
#include <tbb/task_group.h>
#include "Exception.hpp"
namespace Slic3r {
// A batch of resolved presets, each a whole config, is what resolve_then_commit adds
// to peak memory, so it stays far below a vendor's preset count and above any core count.
inline constexpr size_t resolve_batch_size = 64;
// Resolve `count` items that do not depend on each other and install them one at
// a time.
//
// `resolve(i)` runs on any thread and must read only, since the items are
// resolved side by side. `commit(i, resolved)` is called for every item in index
// order, on the calling thread, and is where shared state is written.
//
// The items are worked through in batches, so what is resolved and held at once
// does not grow with `count`. An exception from either callable propagates after
// the batches before it have been committed. A cancellation of the caller's task
// group, which stops a batch partway without an exception, throws RuntimeError
// before that batch commits.
//
// `ChunkSetup`, when given, is constructed once for each piece of a batch TBB hands
// out, for per-thread state a resolve would otherwise set up per item, such as the
// C numeric locale, whose setting takes a lock the whole process shares.
template<class ChunkSetup = std::monostate, class Resolve, class Commit>
void resolve_then_commit(size_t count, Resolve resolve, Commit commit)
{
using Resolved = std::invoke_result_t<Resolve&, size_t>;
std::vector<Resolved> resolved(std::min(count, resolve_batch_size));
for (size_t first = 0; first < count; first += resolve_batch_size) {
const size_t last = std::min(first + resolve_batch_size, count);
// Isolated, so a thread waiting on the batch runs none of the caller's other
// tasks before finishing it.
tbb::this_task_arena::isolate([&] {
tbb::parallel_for(tbb::blocked_range<size_t>(first, last),
[&](const tbb::blocked_range<size_t>& range) {
ChunkSetup setup;
(void) setup;
for (size_t i = range.begin(); i < range.end(); ++ i)
resolved[i - first] = resolve(i);
});
});
if (tbb::is_current_task_group_canceling())
throw RuntimeError("resolve_then_commit: canceled before the batch was resolved");
for (size_t i = first; i < last; ++ i)
commit(i, std::move(resolved[i - first]));
}
}
} // namespace Slic3r
+155 -236
View File
@@ -5,8 +5,6 @@
#include "Preset.hpp" #include "Preset.hpp"
#include "PresetBundle.hpp" #include "PresetBundle.hpp"
#include "AppConfig.hpp" #include "AppConfig.hpp"
#include "LocalesUtils.hpp"
#include "ParallelResolve.hpp"
#ifdef _MSC_VER #ifdef _MSC_VER
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
@@ -508,11 +506,10 @@ void Preset::normalize(DynamicPrintConfig &config)
handle_legacy_sla(config); handle_legacy_sla(config);
} }
std::string Preset::remove_invalid_keys(DynamicPrintConfig &config, const DynamicPrintConfig &default_config, std::string Preset::remove_invalid_keys(DynamicPrintConfig &config, const DynamicPrintConfig &default_config)
const DynamicPrintConfig *added)
{ {
std::string incorrect_keys; std::string incorrect_keys;
for (const std::string &key : (added != nullptr ? *added : config).keys()) for (const std::string &key : config.keys())
if (! default_config.has(key)) { if (! default_config.has(key)) {
if (incorrect_keys.empty()) if (incorrect_keys.empty())
incorrect_keys = key; incorrect_keys = key;
@@ -624,6 +621,7 @@ void Preset::load_info(const std::string& file)
} }
else if (v.first.compare("base_id") == 0) { else if (v.first.compare("base_id") == 0) {
this->base_id = v.second.get_value<std::string>(); this->base_id = v.second.get_value<std::string>();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " load info from: " << file << " and base_id: " << this->base_id;
if (this->base_id.compare("null") == 0) if (this->base_id.compare("null") == 0)
this->base_id.clear(); this->base_id.clear();
} }
@@ -1671,169 +1669,6 @@ std::string PresetCollection::canonical_preset_name(const std::string &name, con
return get_preset_canonical_name(parsed.bare, origin); return get_preset_canonical_name(parsed.bare, origin);
} }
PresetCollection::UserPresetLoad PresetCollection::resolve_user_preset(
const boost::filesystem::path &file, const std::string &canonical_name,
const PresetOrigin &load_origin, ForwardCompatibilitySubstitutionRule substitution_rule,
const std::string &extruder_id_name, const std::string &extruder_variant_name,
std::set<std::string> *key_set1, std::set<std::string> *key_set2) const
{
UserPresetLoad out;
out.preset = Preset(m_type, canonical_name, false);
Preset &preset = out.preset;
preset.bundle_id = load_origin.bundle_id;
preset.file = file.string();
// Load the preset file, apply preset values on top of defaults.
try {
fs::path idx_path(preset.file);
idx_path.replace_extension(".info");
if (fs::exists(idx_path)) {
out.info_file = idx_path.string();
preset.load_info(out.info_file);
}
DynamicPrintConfig config;
//BBS: change to json format
//ConfigSubstitutions config_substitutions = config.load_from_ini(preset.file, substitution_rule);
std::map<std::string, std::string> key_values;
std::string reason;
ConfigSubstitutions config_substitutions = config.load_from_json(preset.file, substitution_rule, key_values, reason);
if (! config_substitutions.empty())
out.substitutions.push_back({ preset.name, m_type, PresetConfigSubstitutions::Source::UserFile, preset.file, std::move(config_substitutions) });
if (!reason.empty()) {
out.discard_file = true;
out.errors.push_back((boost::format("parse config %1% failed") % preset.file).str());
return out;
}
std::string version_str = key_values[BBL_JSON_KEY_VERSION];
boost::optional<Semver> version = Semver::parse(version_str);
if (!version) return out;
preset.version = *version;
if (key_values.find(BBL_JSON_KEY_FILAMENT_ID) != key_values.end())
preset.filament_id = key_values[BBL_JSON_KEY_FILAMENT_ID];
if (key_values.find(BBL_JSON_KEY_DESCRIPTION) != key_values.end())
preset.description = key_values[BBL_JSON_KEY_DESCRIPTION];
if (key_values.find(BBL_JSON_KEY_INSTANTIATION) != key_values.end())
preset.is_visible = key_values[BBL_JSON_KEY_INSTANTIATION] != "false";
//Orca: find and use the inherit config as the base
const Preset* inherit_preset = nullptr;
ConfigOption* inherits_config = config.option(BBL_JSON_KEY_INHERITS);
// check inherits_config
if (inherits_config) {
ConfigOptionString * option_str = dynamic_cast<ConfigOptionString *> (inherits_config);
std::string inherits_value = option_str->value;
// Orca: try to find if the parent preset has been renamed
inherit_preset = this->find_preset2(inherits_value);
Preset::normalize_inherits(config, inherit_preset);
}
const Preset& default_preset = this->default_preset_for(config);
if (inherit_preset) {
preset.config = inherit_preset->config;
preset.filament_id = inherit_preset->filament_id;
extend_default_config_length(config, false, {});
preset.config.update_diff_values_to_child_config(config, extruder_id_name, extruder_variant_name, *key_set1, *key_set2);
}
else {
auto inherits_config2 = dynamic_cast<ConfigOptionString *>(inherits_config);
if ((inherits_config2 && !inherits_config2->value.empty())) {
out.errors.push_back((boost::format("can not find parent %1% for config %2%!") % inherits_config2->value % preset.file).str());
return out;
}
// We support custom root preset now
// Find a default preset for the config. The PrintPresetCollection provides different default preset based on the "printer_technology" field.
preset.config = default_preset.config;
preset.config.apply(std::move(config));
extend_default_config_length(preset.config, true, default_preset.config);
}
Preset::normalize(preset.config);
// Report configuration fields, which are misplaced into a wrong group.
std::string incorrect_keys = Preset::remove_invalid_keys(preset.config, default_preset.config);
if (!incorrect_keys.empty())
out.errors.push_back("Error in a preset file: The preset \"" + preset.file +
"\" contains the following incorrect keys: " + incorrect_keys + ", which were removed");
if (preset.type == Preset::TYPE_FILAMENT && preset.is_user() && preset.inherits().empty()) {
auto compatible_printers = dynamic_cast<ConfigOptionStrings *>(preset.config.option("compatible_printers", true));
if (compatible_printers && compatible_printers->values.empty()) {
size_t at_pos = canonical_name.find('@');
if (at_pos != std::string::npos && at_pos + 1 < canonical_name.length()) {
compatible_printers->values.push_back(canonical_name.substr(at_pos + 1));
out.save_compatible_printers = true;
}
}
}
preset.loaded = true;
out.complete = true;
} catch (const std::ifstream::failure &err) {
out.discard_file = true;
out.errors.push_back((boost::format("The user-config cannot be loaded: %1%. Reason: %2%") % preset.file % err.what()).str());
//throw Slic3r::RuntimeError(std::string("The selected preset cannot be loaded: ") + preset.file + "\n\tReason: " + err.what());
} catch (const std::runtime_error &err) {
out.discard_file = true;
out.errors.push_back((boost::format("Failed loading the user-config file: %1%. Reason: %2%") % preset.file % err.what()).str());
//throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + preset.file + "\n\tReason: " + err.what());
}
out.install = true;
return out;
}
void PresetCollection::commit_user_preset(UserPresetLoad &&loaded, std::deque<Preset> &presets_loaded,
PresetsConfigSubstitutions &substitutions,
const std::function<void(Preset&)> &preset_loaded_fn,
bool read_only)
{
Preset &preset = loaded.preset;
if (! loaded.info_file.empty())
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " load info from: " << loaded.info_file << " and base_id: " << preset.base_id;
append(substitutions, std::move(loaded.substitutions));
for (const std::string &error : loaded.errors) {
++m_errors;
BOOST_LOG_TRIVIAL(error) << error;
}
if (loaded.discard_file && !read_only) {
fs::path file_path(loaded.preset.file);
if (fs::exists(file_path))
fs::remove(file_path);
file_path.replace_extension(".info");
if (fs::exists(file_path))
fs::remove(file_path);
}
if (!loaded.install)
return;
if (loaded.complete) {
if (loaded.save_compatible_printers) {
// A filesystem error from the rewrite is counted, and the preset still loads.
try {
if (!read_only)
preset.save(nullptr);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " added compatible_printers for preset: " << preset.name;
} catch (const std::runtime_error &err) {
++m_errors;
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " can not write compatible_printers back to " << preset.file << ": " << err.what();
}
}
//BBS: add some workaround for previous incorrect settings
if ((!preset.setting_id.empty())&&(preset.setting_id == preset.base_id))
preset.setting_id.clear();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " load preset: " << preset.name << " and filament_id: " << preset.filament_id << " and base_id: " << preset.base_id;
//BBS: add config related logs
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(", preset type %1%, name %2%, path %3%, is_system %4%, is_default %5% is_visible %6%")%Preset::get_type_string(m_type) %preset.name %preset.file %preset.is_system %preset.is_default %preset.is_visible;
// add alias for custom filament preset
set_custom_preset_alias(preset);
}
if (preset_loaded_fn != nullptr)
preset_loaded_fn(preset);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " load config successful and preset name is:" << preset.name;
presets_loaded.emplace_back(std::move(preset));
}
// Load all presets found in dir_path. // Load all presets found in dir_path.
// Throws an exception on error. // Throws an exception on error.
void PresetCollection::load_presets( void PresetCollection::load_presets(
@@ -1871,8 +1706,6 @@ void PresetCollection::load_presets(
std::set<std::string> *key_set1 = nullptr, *key_set2 = nullptr; std::set<std::string> *key_set1 = nullptr, *key_set2 = nullptr;
Preset::get_extruder_names_and_keysets(m_type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2); Preset::get_extruder_names_and_keysets(m_type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2);
struct UserPresetFile { fs::path path; std::string canonical_name; };
std::vector<UserPresetFile> files;
//BBS: change to json format //BBS: change to json format
for (auto &dir_entry : boost::filesystem::directory_iterator(dir)) for (auto &dir_entry : boost::filesystem::directory_iterator(dir))
{ {
@@ -1888,26 +1721,149 @@ void PresetCollection::load_presets(
BOOST_LOG_TRIVIAL(warning) << "Preset already present, not loading: " << canonical_name; BOOST_LOG_TRIVIAL(warning) << "Preset already present, not loading: " << canonical_name;
continue; continue;
} }
files.push_back({ dir_entry.path(), std::move(canonical_name) });
}
}
resolve_then_commit<CNumericLocalesSetter>(files.size(),
[&](size_t i) {
return this->resolve_user_preset(files[i].path, files[i].canonical_name, resolved_origin, substitution_rule,
extruder_id_name, extruder_variant_name, key_set1, key_set2);
},
[&](size_t, UserPresetLoad &&loaded) {
// Committing can remove an unreadable preset's file, and a filesystem error
// there is reported without stopping the rest of the directory.
try { try {
this->commit_user_preset(std::move(loaded), presets_loaded, substitutions, preset_loaded_fn, read_only); Preset preset(m_type, canonical_name, false);
preset.bundle_id = resolved_origin.bundle_id;
preset.file = dir_entry.path().string();
// Load the preset file, apply preset values on top of defaults.
try {
fs::path idx_path(preset.file);
idx_path.replace_extension(".info");
if (fs::exists(idx_path)) {
preset.load_info(idx_path.string());
}
DynamicPrintConfig config;
//BBS: change to json format
//ConfigSubstitutions config_substitutions = config.load_from_ini(preset.file, substitution_rule);
std::map<std::string, std::string> key_values;
std::string reason;
ConfigSubstitutions config_substitutions = config.load_from_json(preset.file, substitution_rule, key_values, reason);
if (! config_substitutions.empty())
substitutions.push_back({ preset.name, m_type, PresetConfigSubstitutions::Source::UserFile, preset.file, std::move(config_substitutions) });
if (!reason.empty()) {
fs::path file_path(preset.file);
if (!read_only && fs::exists(file_path))
fs::remove(file_path);
file_path.replace_extension(".info");
if (!read_only && fs::exists(file_path))
fs::remove(file_path);
BOOST_LOG_TRIVIAL(error) << boost::format("parse config %1% failed")%preset.file;
++m_errors;
continue;
}
std::string version_str = key_values[BBL_JSON_KEY_VERSION];
boost::optional<Semver> version = Semver::parse(version_str);
if (!version) continue;
preset.version = *version;
if (key_values.find(BBL_JSON_KEY_FILAMENT_ID) != key_values.end())
preset.filament_id = key_values[BBL_JSON_KEY_FILAMENT_ID];
if (key_values.find(BBL_JSON_KEY_DESCRIPTION) != key_values.end())
preset.description = key_values[BBL_JSON_KEY_DESCRIPTION];
if (key_values.find(BBL_JSON_KEY_INSTANTIATION) != key_values.end())
preset.is_visible = key_values[BBL_JSON_KEY_INSTANTIATION] != "false";
//Orca: find and use the inherit config as the base
Preset* inherit_preset = nullptr;
ConfigOption* inherits_config = config.option(BBL_JSON_KEY_INHERITS);
// check inherits_config
if (inherits_config) {
ConfigOptionString * option_str = dynamic_cast<ConfigOptionString *> (inherits_config);
std::string inherits_value = option_str->value;
// Orca: try to find if the parent preset has been renamed
inherit_preset = this->find_preset2(inherits_value);
Preset::normalize_inherits(config, inherit_preset);
} else {
;
}
const Preset& default_preset = this->default_preset_for(config);
if (inherit_preset) {
preset.config = inherit_preset->config;
preset.filament_id = inherit_preset->filament_id;
extend_default_config_length(config, false, {});
preset.config.update_diff_values_to_child_config(config, extruder_id_name, extruder_variant_name, *key_set1, *key_set2);
}
else {
auto inherits_config2 = dynamic_cast<ConfigOptionString *>(inherits_config);
if ((inherits_config2 && !inherits_config2->value.empty())) {
BOOST_LOG_TRIVIAL(error) << boost::format("can not find parent %1% for config %2%!")%inherits_config2->value %preset.file;
++m_errors;
continue;
}
// We support custom root preset now
// Find a default preset for the config. The PrintPresetCollection provides different default preset based on the "printer_technology" field.
preset.config = default_preset.config;
preset.config.apply(std::move(config));
extend_default_config_length(preset.config, true, default_preset.config);
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " load preset: " << name << " and filament_id: " << preset.filament_id << " and base_id: " << preset.base_id;
Preset::normalize(preset.config);
// Report configuration fields, which are misplaced into a wrong group.
std::string incorrect_keys = Preset::remove_invalid_keys(preset.config, default_preset.config);
if (!incorrect_keys.empty()) {
++m_errors;
BOOST_LOG_TRIVIAL(error)
<< "Error in a preset file: The preset \"" << preset.file
<< "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed";
}
if (preset.type == Preset::TYPE_FILAMENT && preset.is_user() && preset.inherits().empty()) {
auto compatible_printers = dynamic_cast<ConfigOptionStrings *>(preset.config.option("compatible_printers", true));
if (compatible_printers && compatible_printers->values.empty()) {
size_t at_pos = name.find('@');
if (at_pos != std::string::npos && at_pos + 1 < name.length()) {
compatible_printers->values.push_back(name.substr(at_pos + 1));
if (!read_only)
preset.save(nullptr);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " added compatible_printers for preset: " << name;
}
}
}
preset.loaded = true;
//BBS: add some workaround for previous incorrect settings
if ((!preset.setting_id.empty())&&(preset.setting_id == preset.base_id))
preset.setting_id.clear();
//BBS: add config related logs
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(", preset type %1%, name %2%, path %3%, is_system %4%, is_default %5% is_visible %6%")%Preset::get_type_string(m_type) %preset.name %preset.file %preset.is_system %preset.is_default %preset.is_visible;
// add alias for custom filament preset
set_custom_preset_alias(preset);
} catch (const std::ifstream::failure &err) {
++m_errors;
BOOST_LOG_TRIVIAL(error) << boost::format("The user-config cannot be loaded: %1%. Reason: %2%")%preset.file %err.what();
fs::path file_path(preset.file);
if (!read_only && fs::exists(file_path))
fs::remove(file_path);
file_path.replace_extension(".info");
if (!read_only && fs::exists(file_path))
fs::remove(file_path);
//throw Slic3r::RuntimeError(std::string("The selected preset cannot be loaded: ") + preset.file + "\n\tReason: " + err.what());
} catch (const std::runtime_error &err) {
++m_errors;
BOOST_LOG_TRIVIAL(error) << boost::format("Failed loading the user-config file: %1%. Reason: %2%")%preset.file %err.what();
//throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + preset.file + "\n\tReason: " + err.what());
fs::path file_path(preset.file);
if (!read_only && fs::exists(file_path))
fs::remove(file_path);
file_path.replace_extension(".info");
if (!read_only && fs::exists(file_path))
fs::remove(file_path);
}
if (preset_loaded_fn != nullptr)
preset_loaded_fn(preset);
presets_loaded.emplace_back(preset);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " load config successful and preset name is:" << preset.name;
} catch (const std::runtime_error &err) { } catch (const std::runtime_error &err) {
errors_cummulative += err.what(); errors_cummulative += err.what();
errors_cummulative += "\n"; errors_cummulative += "\n";
} }
}); }
}
if (presets_loaded.size() > 0) if (presets_loaded.size() > 0)
m_presets.insert(m_presets.end(), std::make_move_iterator(presets_loaded.begin()), std::make_move_iterator(presets_loaded.end())); m_presets.insert(m_presets.end(), std::make_move_iterator(presets_loaded.begin()), std::make_move_iterator(presets_loaded.end()));
sort_presets(); sort_presets();
@@ -2871,18 +2827,6 @@ std::pair<Preset*, bool> PresetCollection::load_external_preset(
return std::make_pair(&preset, false); return std::make_pair(&preset, false);
} }
Preset& PresetCollection::append_preset(std::string &&path, const std::string &name, DynamicPrintConfig &&config)
{
lock();
Preset &preset = m_presets.emplace_back(m_type, name, false);
preset.file = std::move(path);
preset.config = std::move(config);
preset.loaded = true;
preset.is_dirty = false;
unlock();
return preset;
}
Preset& PresetCollection::load_preset(const std::string &path, const std::string &name, DynamicPrintConfig &&config, bool select, Semver file_version) Preset& PresetCollection::load_preset(const std::string &path, const std::string &name, DynamicPrintConfig &&config, bool select, Semver file_version)
{ {
lock(); lock();
@@ -3902,53 +3846,28 @@ bool PresetCollection::select_preset_by_name_strict(const std::string &name)
return false; return false;
} }
std::vector<std::vector<std::string>> PresetCollection::merge_presets(const std::vector<PresetCollection*> &others, const VendorMap &new_vendors) // Merge one vendor's presets with the other vendor's presets, report duplicates.
std::vector<std::string> PresetCollection::merge_presets(PresetCollection &&other, const VendorMap &new_vendors)
{ {
auto less = [this](const Preset &a, const Preset &b) { std::vector<std::string> duplicates;
return m_type == Preset::TYPE_FILAMENT ? filament_preset_less(a, b) : a < b; for (Preset &preset : other.m_presets) {
}; if (preset.is_default || preset.is_external)
struct Incoming { Preset *preset; size_t source; }; continue;
auto incoming_less = [&less](const Incoming &a, const Incoming &b) { return less(*a.preset, *b.preset); }; Preset key(m_type, preset.name);
// Each of `others` is sorted, so its presets form one sorted run. auto it = (m_type == Preset::TYPE_FILAMENT)
std::vector<Incoming> incoming; ? std::lower_bound(m_presets.begin() + m_num_default_presets, m_presets.end(), key, filament_preset_less)
std::vector<size_t> run_ends { 0 }; : std::lower_bound(m_presets.begin() + m_num_default_presets, m_presets.end(), key);
for (size_t source = 0; source < others.size(); ++ source) { if (it == m_presets.end() || it->name != preset.name) {
for (Preset &preset : others[source]->m_presets)
if (! preset.is_default && ! preset.is_external)
incoming.push_back({ &preset, source });
assert(std::is_sorted(incoming.begin() + run_ends.back(), incoming.end(), incoming_less));
run_ends.push_back(incoming.size());
}
// Merged pairwise and stably, so equal names stay in the order of `others`.
const size_t runs = others.size();
for (size_t width = 1; width < runs; width *= 2)
for (size_t i = 0; i + width < runs; i += 2 * width)
std::inplace_merge(incoming.begin() + run_ends[i], incoming.begin() + run_ends[i + width],
incoming.begin() + run_ends[std::min(i + 2 * width, runs)], incoming_less);
std::vector<std::vector<std::string>> duplicates(others.size());
std::deque<Preset> merged;
auto own = m_presets.begin() + m_num_default_presets;
std::move(m_presets.begin(), own, std::back_inserter(merged));
// On equal names this collection's preset is kept, else the earliest of `others`,
// and each repeat is listed under the collection it came from.
for (auto next = incoming.begin(); own != m_presets.end() || next != incoming.end();) {
if (next == incoming.end() || (own != m_presets.end() && ! less(*next->preset, *own)))
merged.emplace_back(std::move(*own ++));
else {
Preset &preset = *(next ++)->preset;
if (preset.vendor != nullptr) { if (preset.vendor != nullptr) {
// Re-assign a pointer to the vendor structure in the new PresetBundle. // Re-assign a pointer to the vendor structure in the new PresetBundle.
auto it = new_vendors.find(preset.vendor->id); auto it = new_vendors.find(preset.vendor->id);
assert(it != new_vendors.end()); assert(it != new_vendors.end());
preset.vendor = &it->second; preset.vendor = &it->second;
} }
merged.emplace_back(std::move(preset)); m_presets.emplace(it, std::move(preset));
} } else
for (; next != incoming.end() && next->preset->name == merged.back().name; ++ next) duplicates.emplace_back(std::move(preset.name));
duplicates[next->source].emplace_back(next->preset->name);
} }
m_presets = std::move(merged);
return duplicates; return duplicates;
} }
+3 -51
View File
@@ -445,10 +445,7 @@ public:
static std::string remove_suffix_modified(const std::string& name); static std::string remove_suffix_modified(const std::string& name);
static void normalize(DynamicPrintConfig &config); static void normalize(DynamicPrintConfig &config);
// Report configuration fields, which are misplaced into a wrong group, remove them from the config. // Report configuration fields, which are misplaced into a wrong group, remove them from the config.
// `added`, when given, is the diff applied over a copy of default_config, and only static std::string remove_invalid_keys(DynamicPrintConfig &config, const DynamicPrintConfig &default_config);
// its keys are checked, since no other key can be missing from default_config.
static std::string remove_invalid_keys(DynamicPrintConfig &config, const DynamicPrintConfig &default_config,
const DynamicPrintConfig *added = nullptr);
// BBS: move constructor to public // BBS: move constructor to public
Preset(Type type, const std::string &name, bool is_default = false) : type(type), is_default(is_default), name(name) {} Preset(Type type, const std::string &name, bool is_default = false) : type(type), is_default(is_default), name(name) {}
@@ -804,8 +801,6 @@ public:
// Return number of presets including the "- default -" preset. // Return number of presets including the "- default -" preset.
size_t size() const { return m_presets.size(); } size_t size() const { return m_presets.size(); }
bool has_defaults_only() const { return m_presets.size() <= m_num_default_presets; } bool has_defaults_only() const { return m_presets.size() <= m_num_default_presets; }
// How many presets this collection refused or repaired while loading.
int error_count() const { return m_errors; }
// For Print / Filament presets, disable those, which are not compatible with the printer. // For Print / Filament presets, disable those, which are not compatible with the printer.
template<typename PreferedCondition> template<typename PreferedCondition>
@@ -879,10 +874,8 @@ protected:
// This is a temporary state, which shall be fixed immediately by the following step. // This is a temporary state, which shall be fixed immediately by the following step.
bool select_preset_by_name_strict(const std::string &name); bool select_preset_by_name_strict(const std::string &name);
// Move the presets of `others` into this collection in one pass. A name this // Merge one vendor's presets with the other vendor's presets, report duplicates.
// collection or an earlier one of `others` already has is left out, and reported std::vector<std::string> merge_presets(PresetCollection &&other, const VendorMap &new_vendors);
// in the list of the collection that repeats it.
std::vector<std::vector<std::string>> merge_presets(const std::vector<PresetCollection*> &others, const VendorMap &new_vendors);
// Update m_map_alias_to_profile_name from loaded system profiles. // Update m_map_alias_to_profile_name from loaded system profiles.
void update_map_alias_to_profile_name(); void update_map_alias_to_profile_name();
@@ -897,43 +890,6 @@ protected:
void set_custom_preset_alias(Preset &preset); void set_custom_preset_alias(Preset &preset);
private: private:
// One preset file read and flattened against the presets already in this
// collection, before anything the collection shares has been touched.
struct UserPresetLoad
{
Preset preset;
// Joins the collection. A file that threw partway still joins it, without
// the steps that did not run.
bool install { false };
// The whole of the load ran, so the preset is ready to be aliased.
bool complete { false };
// A filament preset that named no compatible printer and was given one from
// its name, which commit writes back to its file.
bool save_compatible_printers { false };
// Unreadable, so commit removes it and its .info file.
bool discard_file { false };
// The .info file read beside the preset, which commit logs.
std::string info_file;
// Counted and logged by commit, in the order the directory listed the files.
std::vector<std::string> errors;
PresetsConfigSubstitutions substitutions;
};
// Read and flatten one preset file. It reads only, and resolves against the presets
// loaded before this pass, never another file of the same pass, so the files of a
// pass are independent of each other.
UserPresetLoad resolve_user_preset(const boost::filesystem::path &file, const std::string &canonical_name,
const PresetOrigin &load_origin, ForwardCompatibilitySubstitutionRule substitution_rule,
const std::string &extruder_id_name, const std::string &extruder_variant_name,
std::set<std::string> *key_set1, std::set<std::string> *key_set2) const;
// Install one resolved preset. The collection, its alias maps, the error count
// and the preset files on disk are touched here and only here.
void commit_user_preset(UserPresetLoad &&loaded, std::deque<Preset> &presets_loaded,
PresetsConfigSubstitutions &substitutions,
const std::function<void(Preset&)> &preset_loaded_fn,
bool read_only);
std::string canonical_preset_name(const std::string &name, const PresetOrigin &load_origin = PresetOrigin()) const; std::string canonical_preset_name(const std::string &name, const PresetOrigin &load_origin = PresetOrigin()) const;
// Comparator that sorts "Generic " prefixed presets before others, then alphabetically within each group. // Comparator that sorts "Generic " prefixed presets before others, then alphabetically within each group.
@@ -945,10 +901,6 @@ private:
return a.name < b.name; return a.name < b.name;
} }
// Append a preset without keeping the collection sorted, for a caller installing
// many at once; find_preset() is unusable until sort_presets() runs.
Preset& append_preset(std::string &&path, const std::string &name, DynamicPrintConfig &&config);
// Sort presets: filament presets use generic-first ordering, others sort alphabetically. // Sort presets: filament presets use generic-first ordering, others sort alphabetically.
void sort_presets() { void sort_presets() {
if (m_type == Preset::TYPE_FILAMENT) if (m_type == Preset::TYPE_FILAMENT)
File diff suppressed because it is too large Load Diff
+27 -172
View File
@@ -15,7 +15,6 @@
#include <unordered_map> #include <unordered_map>
#include <optional> #include <optional>
#include <array> #include <array>
#include <atomic>
#include <boost/filesystem/path.hpp> #include <boost/filesystem/path.hpp>
#include <unordered_set> #include <unordered_set>
@@ -623,180 +622,36 @@ public:
// default_filament_profile must resolve to a system filament. // default_filament_profile must resolve to a system filament.
bool check_printer_default_materials() const; bool check_printer_default_materials() const;
// One vendor to load, and the directory it is installed in. // Merge one vendor's presets with the other vendor's presets, report duplicates.
struct VendorSource // Public so per-vendor-cache consumers (e.g. the setup wizard) can assemble a
{ // bundle out of several per-vendor caches loaded into separate PresetBundle instances.
std::string name; std::vector<std::string> merge_presets(PresetBundle &&other);
boost::filesystem::path dir;
};
// Load `vendors` into this bundle, the Orca filament library directly and every
// other vendor in parallel into a bundle of its own that inherits from it, merged
// in the order given. A vendor that cannot be loaded has its error added to the
// returned text, or thrown in validation mode, and its name to `failed`; it is
// left out, except for the library, which keeps what it installed before the
// failure. Once `cancel` is set, no further vendor starts loading.
std::pair<PresetsConfigSubstitutions, std::string> load_vendors(const std::vector<VendorSource>& vendors,
ForwardCompatibilitySubstitutionRule compatibility_rule, bool allow_cache,
const std::atomic<bool>* cancel = nullptr, std::vector<std::string>* failed = nullptr);
private: private:
// Move the presets and vendor profiles of `others` into this bundle, in one pass // Load one vendor from the preset cache installed in `dir`, judged against
// over each collection. A preset whose name this bundle or an earlier one of // the vendor profile there. False, with this bundle left clean, when there
// `others` already has is left out and listed under the bundle that repeats it. // is no usable cache and the vendor has to be parsed. This is how
std::vector<std::vector<std::string>> merge_presets(const std::vector<PresetBundle*> &others); // load_vendor_configs_from_json reads a cache.
bool load_vendor_cache(const boost::filesystem::path& dir, const std::string& vendor_name, const PresetBundle* base_bundle);
// What parsing one entry's JSON sub-file reported. Its errors and warnings are // Load one source-form preset entry into this bundle: resolve `inherits`
// logged when the entry installs, so they come out in listing order with the // and `include`, flatten, validate and register the preset. Returns the
// entry's install errors, as parsing and installing one entry at a time leaves them. // reason loading failed, empty on success. See the definition for the
struct EntryParse // sharing contract between the JSON parse and the cache load.
{ // retain_configs / retain_includes, when non-null, name the only presets
ConfigSubstitutions substitutions; // registered into config_maps / include_maps (a config copy each). The
// Counted in the bundle's error count. // cache load passes the names its entries inherit / include — the only
std::vector<std::string> errors; // ones ever looked up again; the JSON parse retains all, not knowing what
std::vector<std::string> warnings; // later subfiles name.
}; std::string load_vendor_preset(const CachedPreset& entry,
const std::string& path, const std::string& vendor_name,
// An EntryParse for each entry of the VendorCacheData list of the same name. const PresetBundle* base_bundle,
struct VendorParse LoadConfigBundleAttributes flags,
{ ConfigSubstitutionContext& substitution_context, PresetsConfigSubstitutions& substitutions,
std::vector<EntryParse> process_entries; std::map<std::string, DynamicPrintConfig>& config_maps, std::map<std::string, DynamicPrintConfig>& include_maps,
std::vector<EntryParse> filament_entries; std::map<std::string, std::string>& filament_id_maps,
std::vector<EntryParse> machine_entries; PresetCollection* presets_collection, size_t& count, bool is_from_lib,
}; const std::set<std::string>* retain_configs = nullptr, const std::set<std::string>* retain_includes = nullptr);
// One vendor read from its cache or its JSONs by read_vendor, for
// install_vendor_read to install.
struct VendorRead
{
std::string dir;
std::string vendor_name;
LoadConfigBundleAttributes flags;
ForwardCompatibilitySubstitutionRule compatibility_rule;
// The errors this bundle had counted before the read, which the cache stamp leaves out.
int errors_at_entry { 0 };
// A whole-vendor load, which can read a cache and write one.
bool cacheable { false };
// Read from the cache at cache_path, which install can still reject.
bool from_cache { false };
std::string cache_path;
// Only the vendor profile was asked for.
bool vendor_only { false };
VendorCacheData data;
// What each entry's JSON parse reported, and whether and with which version
// the cache is written once the entries install.
VendorParse parsed;
bool will_cache { false };
std::string version;
// The sub-file the parse stopped at, its kind, why, and the errors it reported.
std::string reason;
std::string failed_subfile;
const char* failed_kind { nullptr };
std::vector<std::string> failed_errors;
};
// Read a vendor into this bundle's vendor profiles and `data`, from its cache
// when one covers it, else from its JSONs; nothing is installed. Throws
// ConfigurationError when the vendor's own JSON cannot be parsed.
VendorRead read_vendor(const std::string& dir, const std::string& vendor_name, LoadConfigBundleAttributes flags,
ForwardCompatibilitySubstitutionRule compatibility_rule, bool allow_cache);
// Parse the vendor's JSONs into `read`, up to the first sub-file that fails.
void parse_vendor_json(VendorRead& read);
// Install what read_vendor read, against base_bundle's filament library. A cache
// that cannot be installed is replaced by a parse of the JSONs. Throws
// ConfigurationError at the first entry that cannot be installed, or after
// installing the entries before a sub-file that could not be parsed.
std::pair<PresetsConfigSubstitutions, size_t> install_vendor_read(VendorRead&& read, const PresetBundle* base_bundle);
// Install a cache's entries. False, with this bundle left clean, when one of them
// cannot be installed.
bool install_vendor_cache(const std::string& cache_path, const std::string& vendor_name, VendorCacheData&& data,
const PresetBundle* base_bundle);
// Log and count errors reported by a resolve or a parse.
void log_errors(const std::vector<std::string>& errors);
// The state of installing one collection of one vendor, which
// resolve_vendor_preset reads through a const reference and only
// commit_vendor_preset writes.
struct VendorInstall
{
// The directory holding <vendor_name>/, whose sub-paths the entries name.
std::string path;
std::string vendor_name;
const VendorProfile* vendor_profile;
const PresetBundle* base_bundle;
LoadConfigBundleAttributes flags;
PresetCollection* presets;
// The Orca filament library, which keeps every config for other vendors to
// resolve against.
bool is_from_lib;
PresetsConfigSubstitutions* substitutions;
// The names some entry inherits / includes, the only ones whose configs /
// include diffs are looked up again.
std::set<std::string> inherited;
std::set<std::string> included;
std::map<std::string, DynamicPrintConfig> config_maps;
std::map<std::string, DynamicPrintConfig> include_maps;
std::map<std::string, std::string> filament_id_maps;
std::unordered_set<std::string> installed_names;
size_t count { 0 };
};
// Install a vendor's source-form entries, parsed from its JSON or read from its
// cache: processes, then filaments, then printers. Both loads go through here,
// so a cache-loaded bundle cannot come out different from a JSON-loaded one.
// `parsed` is given for entries parsed just now. `complete` says the entries
// are the vendor's whole lists; only then are the filament library's configs
// and filament ids left in m_config_maps and m_filament_id_maps. Returns the
// number of presets installed, and throws ConfigurationError at the first
// entry that cannot be installed.
size_t install_vendor(const std::string& path, const std::string& vendor_name, const PresetBundle* base_bundle,
LoadConfigBundleAttributes flags, const VendorCacheData& entries,
VendorParse* parsed, bool complete, PresetsConfigSubstitutions& substitutions);
// Install one collection's entries in the order they are listed.
void install_vendor_entries(VendorInstall& install, const std::vector<CachedPreset>& entries,
std::vector<EntryParse>* parsed);
// One entry flattened against the preset it inherits, before anything this
// bundle shares has been touched.
struct PresetInstall
{
DynamicPrintConfig config;
std::string file_path;
// Empty when the preset is its own alias.
std::string alias;
std::string filament_id;
std::vector<std::string> renamed_from;
// Reported by commit, so resolving entries together leaves the log and
// the error count as one entry at a time produces them.
std::vector<std::string> errors;
// What a base states for the presets that include it, when it is retained.
std::optional<DynamicPrintConfig> included;
// The config kept for the entries that inherit this one, or for other
// vendors when this is the filament library.
std::optional<DynamicPrintConfig> retained;
// Not instantiated, so it contributes a config and no preset.
bool config_only { false };
// Non-empty when the entry is rejected, and says why.
std::string reason;
};
// Flatten one entry against the config it inherits, from this collection's
// config_maps or base_bundle's filament library, with the include diffs it
// names layered in. It looks up nothing but the names the entry inherits and
// includes, and writes nothing.
PresetInstall resolve_vendor_preset(const CachedPreset& entry, const VendorInstall& install) const;
// Install a resolved entry. The collections, the maps in `install` and the
// error count are touched here and only here, one entry at a time.
// Presets are appended, so a repeated name is caught with `installed_names`,
// and the collection is sorted once every entry is in.
std::string commit_vendor_preset(const CachedPreset& entry, PresetInstall&& resolved,
ConfigSubstitutions&& substitutions, VendorInstall& install);
// Clear every collection's m_printer_hold_alias, which reset() leaves alone. // Clear every collection's m_printer_hold_alias, which reset() leaves alone.
void clear_printer_hold_aliases(); void clear_printer_hold_aliases();
+1 -1
View File
@@ -109,7 +109,7 @@ void skip_config(cereal::BinaryInputArchive& ar, const CacheDictionary& dict);
// One preset as its JSON subfile states it: the config diff, the names of the // One preset as its JSON subfile states it: the config diff, the names of the
// preset it inherits and the presets it includes, and the parse metadata — // preset it inherits and the presets it includes, and the parse metadata —
// everything PresetBundle::parse_vendor_json extracts and // everything the parse phase of load_vendor_configs_from_json extracts and
// nothing it derives. Inheritance and includes are resolved when the entry is // nothing it derives. Inheritance and includes are resolved when the entry is
// installed, against whatever filament library is loaded then, so a cache // installed, against whatever filament library is loaded then, so a cache
// carries no other vendor's values and no other vendor's update can make it // carries no other vendor's values and no other vendor's update can make it
+6 -12
View File
@@ -2439,18 +2439,12 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
if (m_default_region_config.precise_outer_wall && m_default_region_config.wall_sequence != WallSequence::InnerOuter) if (m_default_region_config.precise_outer_wall && m_default_region_config.wall_sequence != WallSequence::InnerOuter)
warn(L("The precise wall option will be ignored for outer-inner or inner-outer-inner wall sequences."), "precise_outer_wall"); warn(L("The precise wall option will be ignored for outer-inner or inner-outer-inner wall sequences."), "precise_outer_wall");
// check adaptive pressure advance model of every extruder variant column of the used filaments // check adaptive pressure advance model
const std::vector<int> &self_index = m_config.filament_self_index.values; for (unsigned int extruder_id : extruders) {
const size_t pa_columns = std::max(m_config.adaptive_pressure_advance_model.size(), size_t(extruders.back()) + 1); if (m_config.adaptive_pressure_advance.get_at(extruder_id) &&
for (size_t column = 0; column < pa_columns; ++column) { m_config.enable_pressure_advance.get_at(extruder_id)) {
// filament_self_index maps a column to its filament once the filament arrays hold one column per variant
const unsigned int filament_id = self_index.size() == pa_columns ? self_index[column] - 1 : column; const std::string pa_model = m_config.adaptive_pressure_advance_model.get_at(extruder_id);
if (!std::binary_search(extruders.begin(), extruders.end(), filament_id))
continue;
if (m_config.adaptive_pressure_advance.get_at(column) &&
m_config.enable_pressure_advance.get_at(column)) {
const std::string pa_model = m_config.adaptive_pressure_advance_model.get_at(column);
if (!pa_model.empty()) { if (!pa_model.empty()) {
std::string validation_error = AdaptivePAProcessor::validate_adaptive_pa_model(pa_model); std::string validation_error = AdaptivePAProcessor::validate_adaptive_pa_model(pa_model);
if (!validation_error.empty()) { if (!validation_error.empty()) {
-30
View File
@@ -9403,10 +9403,6 @@ void PrintConfigDef::handle_legacy_composite(DynamicPrintConfig &config)
} }
config.set_key_value("wiping_volumes_use_custom_matrix", new ConfigOptionBool(custom)); config.set_key_value("wiping_volumes_use_custom_matrix", new ConfigOptionBool(custom));
} }
// Orca: a config saved before a key joined filament_options_with_variant stores it once per filament
// rather than once per filament variant, and one exported by an older CLI may store a single value.
normalize_filament_values_to_variants(config);
} }
const PrintConfigDef print_config_def; const PrintConfigDef print_config_def;
@@ -9509,13 +9505,6 @@ std::set<std::string> filament_options_with_variant = {
"filament_ironing_spacing", "filament_ironing_spacing",
"filament_ironing_inset", "filament_ironing_inset",
"filament_ironing_speed", "filament_ironing_speed",
// Orca: pressure advance
"enable_pressure_advance",
"pressure_advance",
"adaptive_pressure_advance",
"adaptive_pressure_advance_model",
"adaptive_pressure_advance_overhangs",
"adaptive_pressure_advance_bridges",
"activate_air_filtration", "activate_air_filtration",
"activate_air_filtration_during_print", "activate_air_filtration_during_print",
"activate_air_filtration_on_completion", "activate_air_filtration_on_completion",
@@ -10723,25 +10712,6 @@ void set_variant_override(ConfigOptionVectorBase &target, const ConfigOptionVect
target.set_to_index(&source, indices, stride); target.set_to_index(&source, indices, stride);
} }
void normalize_filament_values_to_variants(DynamicPrintConfig &config)
{
const auto *self_index = config.option<ConfigOptionInts>("filament_self_index");
if (self_index == nullptr || self_index->empty())
return;
const int filament_count = *std::max_element(self_index->values.begin(), self_index->values.end());
if (filament_count <= 0 || size_t(filament_count) >= self_index->size())
return;
for (const std::string &key : filament_options_with_variant) {
auto *opt = dynamic_cast<ConfigOptionVectorBase *>(config.option(key));
if (opt == nullptr || (opt->size() != size_t(filament_count) && opt->size() != 1))
continue;
std::unique_ptr<ConfigOption> per_filament(opt->clone());
// set_at() takes the first value for a filament past the end of a single-value vector
for (size_t variant = 0; variant < self_index->size(); ++variant)
opt->set_at(per_filament.get(), variant, self_index->values[variant] - 1);
}
}
//used for object/region config //used for object/region config
//use the smallest of multiple to single //use the smallest of multiple to single
-5
View File
@@ -884,11 +884,6 @@ extern std::set<std::string> empty_options;
void set_variant_override(ConfigOptionVectorBase &target, const ConfigOptionVectorBase &source, void set_variant_override(ConfigOptionVectorBase &target, const ConfigOptionVectorBase &source,
const std::vector<int> &variant_index, int stride = 1); const std::vector<int> &variant_index, int stride = 1);
// Orca: lays every filament_options_with_variant option out one value per filament variant, as
// filament_self_index maps the variants to filaments. An option holding one value per filament, or a
// single value, gives every variant of a filament that filament's value; other lengths are left alone.
void normalize_filament_values_to_variants(DynamicPrintConfig &config);
extern std::set<std::string> filament_dev_options; extern std::set<std::string> filament_dev_options;
extern void update_static_print_config_from_dynamic(ConfigBase& config, const DynamicPrintConfig& dest_config, std::vector<int> variant_index, std::set<std::string>& key_set1, int stride = 1); extern void update_static_print_config_from_dynamic(ConfigBase& config, const DynamicPrintConfig& dest_config, std::vector<int> variant_index, std::set<std::string>& key_set1, int stride = 1);
+1 -10
View File
@@ -256,19 +256,10 @@ extern bool is_gallery_file(const std::string& path, char const* type);
extern bool is_shapes_dir(const std::string& dir); extern bool is_shapes_dir(const std::string& dir);
//BBS: add json support //BBS: add json support
extern bool is_json_file(const std::string& path); extern bool is_json_file(const std::string& path);
// True if rel_path is relative, has no ".." component or embedded NUL and, joined to root, still resolves inside it. // True if rel_path is relative, has no ".." component and, joined to root, still resolves inside it.
// Both '/' and '\\' are treated as separators on every platform, so an archive rejected on one OS // Both '/' and '\\' are treated as separators on every platform, so an archive rejected on one OS
// is rejected on all of them. // is rejected on all of them.
extern bool is_path_within_root(const std::string &rel_path, const boost::filesystem::path &root); extern bool is_path_within_root(const std::string &rel_path, const boost::filesystem::path &root);
// True if a symlink stored at link_rel_path (relative to root) with this target stays inside root: the target
// must be relative, and joined to the link's directory it must pass is_path_within_root.
extern bool is_symlink_target_within_root(const std::string &link_rel_path, const std::string &target, const boost::filesystem::path &root);
// True if path names an entry strictly inside root: it must be spelled with root as its prefix,
// and must still resolve inside root once symlinks are followed.
extern bool is_absolute_path_within_root(const boost::filesystem::path &path, const boost::filesystem::path &root);
// True if a file with this name is of a type that the desktop opens as plain content, so it cannot run code.
// Anything unknown is not safe.
extern bool is_safe_to_open_file_name(const std::string &file_name);
// Orca: custom protocal support utils // Orca: custom protocal support utils
inline bool is_orca_open(const std::string& url) { return boost::starts_with(url, "orcaslicer://open"); } inline bool is_orca_open(const std::string& url) { return boost::starts_with(url, "orcaslicer://open"); }
-63
View File
@@ -4,9 +4,6 @@
#include "miniz_extension.hpp" #include "miniz_extension.hpp"
#include "Utils.hpp" #include "Utils.hpp"
#include <boost/filesystem.hpp>
#include <boost/log/trivial.hpp>
#if defined(_MSC_VER) || defined(__MINGW64__) #if defined(_MSC_VER) || defined(__MINGW64__)
#include "boost/nowide/cstdio.hpp" #include "boost/nowide/cstdio.hpp"
#endif #endif
@@ -118,66 +115,6 @@ std::string decode_archive_entry_path(mz_zip_archive *zip, const mz_zip_archive_
return decode_zip_unicode_path_extra_field(extra.substr(0, extra_size > 0 ? extra_size - 1 : 0), stat.m_filename); return decode_zip_unicode_path_extra_field(extra.substr(0, extra_size > 0 ? extra_size - 1 : 0), stat.m_filename);
} }
bool extract_archive_confined(const std::string &zip_path_utf8, const std::string &dest_dir)
{
mz_zip_archive archive;
mz_zip_zero_struct(&archive);
if (!open_zip_reader(&archive, zip_path_utf8)) {
BOOST_LOG_TRIVIAL(error) << "Unable to open zip reader for " << zip_path_utf8;
return false;
}
const mz_uint num_entries = mz_zip_reader_get_num_files(&archive);
mz_zip_archive_file_stat stat;
// Validate every entry first so an archive with a single escaping entry leaves no partial output behind.
const boost::filesystem::path root(dest_dir);
for (mz_uint i = 0; i < num_entries; ++i) {
if (mz_zip_reader_file_stat(&archive, i, &stat) && !is_path_within_root(stat.m_filename, root)) {
BOOST_LOG_TRIVIAL(error) << "Unzip: rejecting " << zip_path_utf8 << ", entry " << stat.m_filename << " resolves outside " << dest_dir;
close_zip_reader(&archive);
return false;
}
}
for (mz_uint i = 0; i < num_entries; ++i) {
if (!mz_zip_reader_file_stat(&archive, i, &stat)) {
BOOST_LOG_TRIVIAL(warning) << "Unzip: read file stat failed";
continue;
}
const std::string dest_file = dest_dir + "/" + stat.m_filename;
try {
if (stat.m_is_directory) {
const boost::filesystem::path dest_path(dest_file);
if (!boost::filesystem::exists(dest_path))
boost::filesystem::create_directories(dest_path);
continue;
}
if (stat.m_uncomp_size == 0) {
BOOST_LOG_TRIVIAL(warning) << "Unzip: invalid size for file " << stat.m_filename;
continue;
}
// Replace a symlink at the destination rather than writing through it.
const boost::filesystem::path dest_path(dest_file);
if (boost::filesystem::is_symlink(boost::filesystem::symlink_status(dest_path)))
boost::filesystem::remove(dest_path);
if (!mz_zip_reader_extract_to_file(&archive, stat.m_file_index, dest_file.c_str(), 0)) {
BOOST_LOG_TRIVIAL(error) << "Unzip: extract file " << stat.m_filename << " to dest " << dest_file << " failed";
close_zip_reader(&archive);
return false;
}
BOOST_LOG_TRIVIAL(info) << "Unzip: successfully extract file " << stat.m_file_index << " to " << dest_file;
} catch (const std::exception &e) {
close_zip_reader(&archive);
BOOST_LOG_TRIVIAL(error) << "Unzip: archive read exception: " << e.what();
return false;
}
}
close_zip_reader(&archive);
return true;
}
MZ_Archive::MZ_Archive() MZ_Archive::MZ_Archive()
{ {
mz_zip_zero_struct(&arch); mz_zip_zero_struct(&arch);
-2
View File
@@ -11,8 +11,6 @@ bool open_zip_writer(mz_zip_archive *zip, const std::string &fname_utf8);
bool close_zip_reader(mz_zip_archive *zip); bool close_zip_reader(mz_zip_archive *zip);
bool close_zip_writer(mz_zip_archive *zip); bool close_zip_writer(mz_zip_archive *zip);
std::string decode_archive_entry_path(mz_zip_archive *zip, const mz_zip_archive_file_stat &stat); std::string decode_archive_entry_path(mz_zip_archive *zip, const mz_zip_archive_file_stat &stat);
// Extracts every entry of the archive under dest_dir. Nothing is written if any entry would resolve outside dest_dir.
bool extract_archive_confined(const std::string &zip_path_utf8, const std::string &dest_dir);
class MZ_Archive { class MZ_Archive {
public: public:
+1 -39
View File
@@ -70,7 +70,6 @@
#include <boost/shared_ptr.hpp> #include <boost/shared_ptr.hpp>
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <boost/filesystem/path.hpp> #include <boost/filesystem/path.hpp>
#include <boost/nowide/fstream.hpp> #include <boost/nowide/fstream.hpp>
@@ -1094,9 +1093,6 @@ bool is_path_within_root(const std::string &rel_path, const boost::filesystem::p
auto is_separator = [](char c) { return c == '/' || c == '\\'; }; auto is_separator = [](char c) { return c == '/' || c == '\\'; };
if (rel_path.empty() || is_separator(rel_path.front()) || (rel_path.size() > 1 && rel_path[1] == ':')) if (rel_path.empty() || is_separator(rel_path.front()) || (rel_path.size() > 1 && rel_path[1] == ':'))
return false; return false;
// The filesystem calls stop at a NUL, so they would act on a shorter path than the one checked here.
if (rel_path.find('\0') != std::string::npos)
return false;
for (size_t start = 0; start <= rel_path.size();) { for (size_t start = 0; start <= rel_path.size();) {
size_t end = start; size_t end = start;
while (end < rel_path.size() && !is_separator(rel_path[end])) while (end < rel_path.size() && !is_separator(rel_path[end]))
@@ -1107,10 +1103,7 @@ bool is_path_within_root(const std::string &rel_path, const boost::filesystem::p
} }
// Resolve against the canonical root so a symlink inside it cannot lead back out. // Resolve against the canonical root so a symlink inside it cannot lead back out.
try { try {
std::string root_str = boost::filesystem::weakly_canonical(root).string(); const std::string root_str = boost::filesystem::weakly_canonical(root).string();
// A trailing separator on root would otherwise fail the prefix match below for every path.
while (!root_str.empty() && (root_str.back() == '/' || root_str.back() == boost::filesystem::path::preferred_separator))
root_str.pop_back();
const std::string full_str = boost::filesystem::weakly_canonical(root / rel_path).string(); const std::string full_str = boost::filesystem::weakly_canonical(root / rel_path).string();
return full_str.compare(0, root_str.size(), root_str) == 0 && return full_str.compare(0, root_str.size(), root_str) == 0 &&
(full_str.size() == root_str.size() || full_str[root_str.size()] == boost::filesystem::path::preferred_separator); (full_str.size() == root_str.size() || full_str[root_str.size()] == boost::filesystem::path::preferred_separator);
@@ -1119,37 +1112,6 @@ bool is_path_within_root(const std::string &rel_path, const boost::filesystem::p
} }
} }
bool is_symlink_target_within_root(const std::string &link_rel_path, const std::string &target, const boost::filesystem::path &root)
{
if (target.empty() || target.front() == '/' || target.front() == '\\' || (target.size() > 1 && target[1] == ':'))
return false;
// A relative target without ".." only descends from the link's directory, so no chain of such links can leave root.
const size_t sep = link_rel_path.find_last_of("/\\");
return is_path_within_root((sep == std::string::npos ? std::string() : link_rel_path.substr(0, sep + 1)) + target, root);
}
bool is_absolute_path_within_root(const boost::filesystem::path &path, const boost::filesystem::path &root)
{
const boost::filesystem::path rel = path.lexically_relative(root);
return !rel.empty() && rel != "." && is_path_within_root(rel.string(), root);
}
bool is_safe_to_open_file_name(const std::string &file_name)
{
// Formats that cannot carry macros or scripts. Legacy and OpenDocument office files, HTML and SVG are left out on purpose.
static const std::vector<std::string> safe_extensions = {
"jpg", "jpeg", "jfif", "pjpeg", "pjp", "png", "gif", "bmp", "webp", "tif", "tiff",
"pdf", "txt", "md", "csv", "docx", "xlsx", "pptx",
"stl", "obj", "3mf", "amf", "ply", "step", "stp", "iges", "igs", "dxf",
"mp4", "mov", "webm"};
// The name must end in the extension itself: Windows drops trailing dots and spaces and reads ':' as a stream separator.
const size_t dot = file_name.find_last_of('.');
if (dot == std::string::npos || file_name.find_first_of("/\\:") != std::string::npos)
return false;
const std::string extension = boost::algorithm::to_lower_copy(file_name.substr(dot + 1));
return std::find(safe_extensions.begin(), safe_extensions.end(), extension) != safe_extensions.end();
}
bool is_img_file(const std::string &path) bool is_img_file(const std::string &path)
{ {
return boost::iends_with(path, ".png") || boost::iends_with(path, ".svg"); return boost::iends_with(path, ".png") || boost::iends_with(path, ".svg");
+15
View File
@@ -158,6 +158,21 @@ enum class EGCodeExtrusionRole : uint8_t
static constexpr std::size_t GCODE_EXTRUSION_ROLES_COUNT = static_cast<std::size_t>(EGCodeExtrusionRole::COUNT); static constexpr std::size_t GCODE_EXTRUSION_ROLES_COUNT = static_cast<std::size_t>(EGCodeExtrusionRole::COUNT);
//
// What the reduced set, drawn while the user is dragging, holds in place of the full toolpaths
//
enum class EReducedDetailMode : uint8_t
{
// nothing: no reduced set is built
Off,
// only the bottom and top layers of the visible range, for a caller that draws the print
// itself some other way
EndLayersOnly,
// every role but the infill under the skins and the gap fill: what can be seen from outside
ShellOnly,
COUNT
};
// //
// Option types // Option types
// //
+13
View File
@@ -114,6 +114,19 @@ public:
// //
bool is_dim_previous_layers() const; bool is_dim_previous_layers() const;
void set_dim_previous_layers(bool value); void set_dim_previous_layers(bool value);
//
// The reduced set drawn while the user drags: what the mode keeps, and always the bottom and
// top layers of the visible range. While a mode is set it is built alongside the full set, so
// set_reduced_detail() rebuilds nothing. Ignored on the OpenGL ES path.
//
EReducedDetailMode get_reduced_detail_mode() const;
void set_reduced_detail_mode(EReducedDetailMode mode);
// Whether the shell mode hides the infill roles; off for a profile that leaves them on the
// surface, with no top or bottom shell or no walls.
bool get_reduced_detail_hide_infill() const;
void set_reduced_detail_hide_infill(bool value);
void set_reduced_detail(bool value);
bool is_reduced_detail() const;
float get_dim_previous_layers_brightness() const; float get_dim_previous_layers_brightness() const;
void set_dim_previous_layers_brightness(float value); void set_dim_previous_layers_brightness(float value);
// //
+5
View File
@@ -25,6 +25,11 @@ struct Settings
// ORCA: how bright those darkened layers are rendered, 1.0 = unchanged, 0.0 = black // ORCA: how bright those darkened layers are rendered, 1.0 = unchanged, 0.0 = black
float dim_previous_layers_brightness{ 0.4f }; float dim_previous_layers_brightness{ 0.4f };
bool spiral_vase_mode{ false }; bool spiral_vase_mode{ false };
// what the reduced set holds and whether it is drawn. Ignored on the OpenGL ES path.
EReducedDetailMode reduced_detail_mode{ EReducedDetailMode::Off };
// whether the shell mode may hide the infill roles: false when the profile leaves them on the surface
bool reduced_detail_hide_infill{ true };
bool reduced_detail{ false };
// //
// Required update flags // Required update flags
// //
+30
View File
@@ -92,6 +92,36 @@ bool Viewer::is_dim_previous_layers() const
return m_impl->is_dim_previous_layers(); return m_impl->is_dim_previous_layers();
} }
void Viewer::set_reduced_detail(bool value)
{
m_impl->set_reduced_detail(value);
}
bool Viewer::is_reduced_detail() const
{
return m_impl->is_reduced_detail();
}
EReducedDetailMode Viewer::get_reduced_detail_mode() const
{
return m_impl->get_reduced_detail_mode();
}
void Viewer::set_reduced_detail_mode(EReducedDetailMode mode)
{
m_impl->set_reduced_detail_mode(mode);
}
bool Viewer::get_reduced_detail_hide_infill() const
{
return m_impl->get_reduced_detail_hide_infill();
}
void Viewer::set_reduced_detail_hide_infill(bool value)
{
m_impl->set_reduced_detail_hide_infill(value);
}
void Viewer::set_dim_previous_layers(bool value) void Viewer::set_dim_previous_layers(bool value)
{ {
m_impl->set_dim_previous_layers(value); m_impl->set_dim_previous_layers(value);
+104 -9
View File
@@ -17,6 +17,7 @@
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <numeric> #include <numeric>
#include <cfloat>
namespace libvgcode { namespace libvgcode {
@@ -901,9 +902,17 @@ void ViewerImpl::reset()
#else #else
m_enabled_segments_count = 0; m_enabled_segments_count = 0;
m_enabled_options_count = 0; m_enabled_options_count = 0;
m_enabled_segments_reduced_count = 0;
m_enabled_options_reduced_count = 0;
m_enabled_segments_reduced_tex_size = 0;
m_enabled_options_reduced_tex_size = 0;
m_settings_used_for_ranges = std::nullopt; m_settings_used_for_ranges = std::nullopt;
delete_textures(m_enabled_options_reduced_tex_id);
delete_buffers(m_enabled_options_reduced_buf_id);
delete_textures(m_enabled_segments_reduced_tex_id);
delete_buffers(m_enabled_segments_reduced_buf_id);
delete_textures(m_enabled_options_tex_id); delete_textures(m_enabled_options_tex_id);
delete_buffers(m_enabled_options_buf_id); delete_buffers(m_enabled_options_buf_id);
delete_textures(m_enabled_segments_tex_id); delete_textures(m_enabled_segments_tex_id);
@@ -1163,6 +1172,17 @@ void ViewerImpl::load(GCodeInputData&& gcode_data)
glsafe(glGenTextures(1, &m_enabled_options_tex_id)); glsafe(glGenTextures(1, &m_enabled_options_tex_id));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_options_tex_id)); glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_options_tex_id));
// create (but do not fill) the reduced counterparts of the two buffers above
glsafe(glGenBuffers(1, &m_enabled_segments_reduced_buf_id));
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_enabled_segments_reduced_buf_id));
glsafe(glGenTextures(1, &m_enabled_segments_reduced_tex_id));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_segments_reduced_tex_id));
glsafe(glGenBuffers(1, &m_enabled_options_reduced_buf_id));
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_enabled_options_reduced_buf_id));
glsafe(glGenTextures(1, &m_enabled_options_reduced_tex_id));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_options_reduced_tex_id));
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, 0)); glsafe(glBindBuffer(GL_TEXTURE_BUFFER, 0));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, old_bound_texture)); glsafe(glBindTexture(GL_TEXTURE_BUFFER, old_bound_texture));
#endif // ENABLE_OPENGL_ES #endif // ENABLE_OPENGL_ES
@@ -1174,6 +1194,18 @@ void ViewerImpl::load(GCodeInputData&& gcode_data)
update_colors(); update_colors();
} }
#ifndef ENABLE_OPENGL_ES
// the roles that lie under a skin or between walls, never seen from outside the print
static bool is_hidden_in_shell(EGCodeExtrusionRole role)
{
return role == EGCodeExtrusionRole::InternalInfill ||
role == EGCodeExtrusionRole::SolidInfill ||
role == EGCodeExtrusionRole::InternalBridgeInfill ||
role == EGCodeExtrusionRole::GapFill;
}
#endif // ENABLE_OPENGL_ES
void ViewerImpl::update_enabled_entities() void ViewerImpl::update_enabled_entities()
{ {
if (m_vertices.empty()) if (m_vertices.empty())
@@ -1181,6 +1213,17 @@ void ViewerImpl::update_enabled_entities()
std::vector<uint32_t> enabled_segments; std::vector<uint32_t> enabled_segments;
std::vector<uint32_t> enabled_options; std::vector<uint32_t> enabled_options;
#ifndef ENABLE_OPENGL_ES
// the reduced set is filled by the same walk, so switching to it costs no rebuild. Whatever the
// mode leaves out, the bottom and top layers of the visible range are kept whole: they are the
// surfaces the range cuts open
const EReducedDetailMode reduced_mode = m_settings.reduced_detail_mode;
const bool build_reduced = reduced_mode != EReducedDetailMode::Off;
const bool hide_infill = m_settings.reduced_detail_hide_infill;
std::vector<uint32_t> enabled_segments_reduced;
std::vector<uint32_t> enabled_options_reduced;
const Interval& layers_range = m_layers.get_view_range();
#endif // ENABLE_OPENGL_ES
Interval range = m_view_range.get_visible(); Interval range = m_view_range.get_visible();
// when top layer only visualization is enabled, we need to render // when top layer only visualization is enabled, we need to render
@@ -1228,6 +1271,20 @@ void ViewerImpl::update_enabled_entities()
enabled_options.push_back(static_cast<uint32_t>(i)); enabled_options.push_back(static_cast<uint32_t>(i));
else else
enabled_segments.push_back(static_cast<uint32_t>(i)); enabled_segments.push_back(static_cast<uint32_t>(i));
#ifndef ENABLE_OPENGL_ES
if (build_reduced) {
const bool end_layer = v.layer_id == layers_range[0] || v.layer_id == layers_range[1];
if (end_layer)
(v.is_option() ? enabled_options_reduced : enabled_segments_reduced).push_back(static_cast<uint32_t>(i));
else if (reduced_mode == EReducedDetailMode::ShellOnly) {
if (v.is_option())
enabled_options_reduced.push_back(static_cast<uint32_t>(i));
else if (!v.is_extrusion() || !hide_infill || !is_hidden_in_shell(v.role))
enabled_segments_reduced.push_back(static_cast<uint32_t>(i));
}
}
#endif // ENABLE_OPENGL_ES
} }
#ifdef ENABLE_OPENGL_ES #ifdef ENABLE_OPENGL_ES
@@ -1256,6 +1313,23 @@ void ViewerImpl::update_enabled_entities()
else else
glsafe(glBufferData(GL_TEXTURE_BUFFER, 0, nullptr, GL_STATIC_DRAW)); glsafe(glBufferData(GL_TEXTURE_BUFFER, 0, nullptr, GL_STATIC_DRAW));
m_enabled_segments_reduced_count = enabled_segments_reduced.size();
m_enabled_options_reduced_count = enabled_options_reduced.size();
m_enabled_segments_reduced_tex_size = enabled_segments_reduced.size() * sizeof(uint32_t);
m_enabled_options_reduced_tex_size = enabled_options_reduced.size() * sizeof(uint32_t);
// uploaded even when nothing was built, so that the last reduced set is released as soon as
// the preference is switched off
assert(m_enabled_segments_reduced_buf_id > 0);
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_enabled_segments_reduced_buf_id));
glsafe(glBufferData(GL_TEXTURE_BUFFER, m_enabled_segments_reduced_tex_size,
enabled_segments_reduced.empty() ? nullptr : enabled_segments_reduced.data(), GL_STATIC_DRAW));
assert(m_enabled_options_reduced_buf_id > 0);
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_enabled_options_reduced_buf_id));
glsafe(glBufferData(GL_TEXTURE_BUFFER, m_enabled_options_reduced_tex_size,
enabled_options_reduced.empty() ? nullptr : enabled_options_reduced.data(), GL_STATIC_DRAW));
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, 0)); glsafe(glBindBuffer(GL_TEXTURE_BUFFER, 0));
#endif // ENABLE_OPENGL_ES #endif // ENABLE_OPENGL_ES
@@ -1463,6 +1537,23 @@ void ViewerImpl::toggle_top_layer_only_view_range()
update_colors_texture(); update_colors_texture();
} }
// Both decide which vertices land in the reduced set, so the sets are rebuilt.
void ViewerImpl::set_reduced_detail_mode(EReducedDetailMode mode)
{
if (m_settings.reduced_detail_mode == mode)
return;
m_settings.reduced_detail_mode = mode;
m_settings.update_enabled_entities = true;
}
void ViewerImpl::set_reduced_detail_hide_infill(bool value)
{
if (m_settings.reduced_detail_hide_infill == value)
return;
m_settings.reduced_detail_hide_infill = value;
m_settings.update_enabled_entities = true;
}
// ORCA: enable/disable darkening of the layers the layer slider is not scrubbed to // ORCA: enable/disable darkening of the layers the layer slider is not scrubbed to
void ViewerImpl::set_dim_previous_layers(bool value) void ViewerImpl::set_dim_previous_layers(bool value)
{ {
@@ -1856,6 +1947,8 @@ size_t ViewerImpl::get_used_gpu_memory() const
ret += m_colors_tex_size; ret += m_colors_tex_size;
ret += m_enabled_segments_tex_size; ret += m_enabled_segments_tex_size;
ret += m_enabled_options_tex_size; ret += m_enabled_options_tex_size;
ret += m_enabled_segments_reduced_tex_size;
ret += m_enabled_options_reduced_tex_size;
#endif // ENABLE_OPENGL_ES #endif // ENABLE_OPENGL_ES
return ret; return ret;
} }
@@ -2072,7 +2165,8 @@ void ViewerImpl::render_segments(const Mat4x4& view_matrix, const Mat4x4& projec
#ifdef ENABLE_OPENGL_ES #ifdef ENABLE_OPENGL_ES
if (m_texture_data.get_enabled_segments_count() == 0) if (m_texture_data.get_enabled_segments_count() == 0)
#else #else
if (m_enabled_segments_count == 0) const ActiveSet segments = active_segments();
if (segments.count == 0)
#endif // ENABLE_OPENGL_ES #endif // ENABLE_OPENGL_ES
return; return;
@@ -2099,7 +2193,7 @@ void ViewerImpl::render_segments(const Mat4x4& view_matrix, const Mat4x4& projec
const bool top_down = !m_rendering_shadow_casters && view_matrix[10] > 0.0f; const bool top_down = !m_rendering_shadow_casters && view_matrix[10] > 0.0f;
glsafe(glUniform1i(m_uni_segments_reverse_order_id, top_down ? 1 : 0)); glsafe(glUniform1i(m_uni_segments_reverse_order_id, top_down ? 1 : 0));
#ifndef ENABLE_OPENGL_ES #ifndef ENABLE_OPENGL_ES
glsafe(glUniform1i(m_uni_segments_instance_count_id, static_cast<int>(m_enabled_segments_count))); glsafe(glUniform1i(m_uni_segments_instance_count_id, static_cast<int>(segments.count)));
#endif // ENABLE_OPENGL_ES #endif // ENABLE_OPENGL_ES
// ORCA: realistic view. The depth pass writes the map it would otherwise read, so it shades // ORCA: realistic view. The depth pass writes the map it would otherwise read, so it shades
// with the lookup off. // with the lookup off.
@@ -2149,10 +2243,10 @@ void ViewerImpl::render_segments(const Mat4x4& view_matrix, const Mat4x4& projec
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_colors_tex_id)); glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_colors_tex_id));
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32F, m_colors_buf_id)); glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32F, m_colors_buf_id));
glsafe(glActiveTexture(GL_TEXTURE3)); glsafe(glActiveTexture(GL_TEXTURE3));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_segments_tex_id)); glsafe(glBindTexture(GL_TEXTURE_BUFFER, segments.tex_id));
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32UI, m_enabled_segments_buf_id)); glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32UI, segments.buf_id));
m_segment_template.render(m_enabled_segments_count); m_segment_template.render(segments.count);
#endif // ENABLE_OPENGL_ES #endif // ENABLE_OPENGL_ES
if (curr_cull_face) if (curr_cull_face)
@@ -2178,7 +2272,8 @@ void ViewerImpl::render_options(const Mat4x4& view_matrix, const Mat4x4& project
#ifdef ENABLE_OPENGL_ES #ifdef ENABLE_OPENGL_ES
if (m_texture_data.get_enabled_options_count() == 0) if (m_texture_data.get_enabled_options_count() == 0)
#else #else
if (m_enabled_options_count == 0) const ActiveSet options = active_options();
if (options.count == 0)
#endif // ENABLE_OPENGL_ES #endif // ENABLE_OPENGL_ES
return; return;
@@ -2236,10 +2331,10 @@ void ViewerImpl::render_options(const Mat4x4& view_matrix, const Mat4x4& project
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_colors_tex_id)); glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_colors_tex_id));
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32F, m_colors_buf_id)); glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32F, m_colors_buf_id));
glsafe(glActiveTexture(GL_TEXTURE3)); glsafe(glActiveTexture(GL_TEXTURE3));
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_options_tex_id)); glsafe(glBindTexture(GL_TEXTURE_BUFFER, options.tex_id));
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32UI, m_enabled_options_buf_id)); glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32UI, options.buf_id));
m_option_template.render(m_enabled_options_count); m_option_template.render(options.count);
#endif // ENABLE_OPENGL_ES #endif // ENABLE_OPENGL_ES
if (!curr_cull_face) if (!curr_cull_face)
+45
View File
@@ -109,6 +109,21 @@ public:
// 0.0 = black // 0.0 = black
bool is_dim_previous_layers() const { return m_settings.dim_previous_layers; } bool is_dim_previous_layers() const { return m_settings.dim_previous_layers; }
void set_dim_previous_layers(bool value); void set_dim_previous_layers(bool value);
//
// Draw from the reduced set; it is already built, so this is just a buffer binding.
//
void set_reduced_detail(bool value) {
#ifdef ENABLE_OPENGL_ES
// no reduced set is built on OpenGL ES
value = false;
#endif // ENABLE_OPENGL_ES
m_settings.reduced_detail = value;
}
bool is_reduced_detail() const { return m_settings.reduced_detail; }
EReducedDetailMode get_reduced_detail_mode() const { return m_settings.reduced_detail_mode; }
void set_reduced_detail_mode(EReducedDetailMode mode);
bool get_reduced_detail_hide_infill() const { return m_settings.reduced_detail_hide_infill; }
void set_reduced_detail_hide_infill(bool value);
float get_dim_previous_layers_brightness() const { return m_settings.dim_previous_layers_brightness; } float get_dim_previous_layers_brightness() const { return m_settings.dim_previous_layers_brightness; }
void set_dim_previous_layers_brightness(float value); void set_dim_previous_layers_brightness(float value);
@@ -501,6 +516,15 @@ private:
unsigned int m_enabled_options_tex_id{ 0 }; unsigned int m_enabled_options_tex_id{ 0 };
size_t m_enabled_options_count{ 0 }; size_t m_enabled_options_count{ 0 };
// //
// OpenGL buffers to store the reduced set drawn while Settings::reduced_detail is set
//
unsigned int m_enabled_segments_reduced_buf_id{ 0 };
unsigned int m_enabled_segments_reduced_tex_id{ 0 };
size_t m_enabled_segments_reduced_count{ 0 };
unsigned int m_enabled_options_reduced_buf_id{ 0 };
unsigned int m_enabled_options_reduced_tex_id{ 0 };
size_t m_enabled_options_reduced_count{ 0 };
//
// Caches for size of data sent to gpu, in bytes // Caches for size of data sent to gpu, in bytes
// //
size_t m_positions_tex_size{ 0 }; size_t m_positions_tex_size{ 0 };
@@ -508,6 +532,27 @@ private:
size_t m_colors_tex_size{ 0 }; size_t m_colors_tex_size{ 0 };
size_t m_enabled_segments_tex_size{ 0 }; size_t m_enabled_segments_tex_size{ 0 };
size_t m_enabled_options_tex_size{ 0 }; size_t m_enabled_options_tex_size{ 0 };
size_t m_enabled_segments_reduced_tex_size{ 0 };
size_t m_enabled_options_reduced_tex_size{ 0 };
// The set the next draw reads from: the reduced one while dragging, if one is built.
bool use_reduced_set() const { return m_settings.reduced_detail && m_settings.reduced_detail_mode != EReducedDetailMode::Off; }
struct ActiveSet
{
size_t count{ 0 };
unsigned int buf_id{ 0 };
unsigned int tex_id{ 0 };
};
ActiveSet active_segments() const {
if (use_reduced_set())
return { m_enabled_segments_reduced_count, m_enabled_segments_reduced_buf_id, m_enabled_segments_reduced_tex_id };
return { m_enabled_segments_count, m_enabled_segments_buf_id, m_enabled_segments_tex_id };
}
ActiveSet active_options() const {
if (use_reduced_set())
return { m_enabled_options_reduced_count, m_enabled_options_reduced_buf_id, m_enabled_options_reduced_tex_id };
return { m_enabled_options_count, m_enabled_options_buf_id, m_enabled_options_tex_id };
}
#endif // ENABLE_OPENGL_ES #endif // ENABLE_OPENGL_ES
// //
+7 -2
View File
@@ -1019,9 +1019,14 @@ if (UNIX AND NOT APPLE)
find_package(GTK${SLIC3R_GTK} REQUIRED) find_package(GTK${SLIC3R_GTK} REQUIRED)
pkg_check_modules(LIBSECRET REQUIRED libsecret-1) pkg_check_modules(LIBSECRET REQUIRED libsecret-1)
pkg_check_modules(webkit2gtk REQUIRED webkit2gtk-4.1) pkg_check_modules(webkit2gtk REQUIRED webkit2gtk-4.1)
find_package(X11 REQUIRED) if (FLATPAK)
# I don't know why this is needed, but for whatever reason slic3r isn't
# linking to X11 and webkit2gtk. force it.
find_package(X11 REQUIRED)
target_link_libraries(libslic3r_gui ${X11_LIBRARIES} ${webkit2gtk_LIBRARIES})
endif()
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${GTK${SLIC3R_GTK}_INCLUDE_DIRS} ${LIBSECRET_INCLUDE_DIRS} ${webkit2gtk_INCLUDE_DIRS}) target_include_directories(libslic3r_gui SYSTEM PRIVATE ${GTK${SLIC3R_GTK}_INCLUDE_DIRS} ${LIBSECRET_INCLUDE_DIRS} ${webkit2gtk_INCLUDE_DIRS})
target_link_libraries(libslic3r_gui ${GTK${SLIC3R_GTK}_LIBRARIES} fontconfig ${LIBSECRET_LIBRARIES} ${webkit2gtk_LIBRARIES} ${X11_LIBRARIES}) target_link_libraries(libslic3r_gui ${GTK${SLIC3R_GTK}_LIBRARIES} fontconfig ${LIBSECRET_LIBRARIES})
# Propagate GDK backend detection results as compile definitions so that # Propagate GDK backend detection results as compile definitions so that
# LinuxDisplayBackend.cpp can include the right GDK headers. # LinuxDisplayBackend.cpp can include the right GDK headers.
+1 -1
View File
@@ -428,7 +428,7 @@ void AuFile::on_dclick(wxMouseEvent &evt)
if (m_type == AddFileButton) if (m_type == AddFileButton)
return; return;
else else
desktop_open_project_attachment(this, m_file_path); wxLaunchDefaultApplication(m_file_path.wstring(), 0);
} }
void AuFile::on_mouse_left_up(wxMouseEvent &evt) void AuFile::on_mouse_left_up(wxMouseEvent &evt)
+6 -8
View File
@@ -171,16 +171,14 @@ void ConfigManipulation::check_adaptive_pressure_advance_model(DynamicPrintConfi
return; return;
const auto* model = config->option<ConfigOptionStrings>("adaptive_pressure_advance_model"); const auto* model = config->option<ConfigOptionStrings>("adaptive_pressure_advance_model");
if (model == nullptr) if (model == nullptr || model->values.empty())
return; return;
// Each extruder variant holds its own model. std::string raw_model;
std::string error; for (const std::string& chunk : model->values)
for (const std::string& variant_model : model->values) { raw_model += chunk;
error = AdaptivePAProcessor::validate_adaptive_pa_model(variant_model);
if (!error.empty()) std::string error = AdaptivePAProcessor::validate_adaptive_pa_model(raw_model);
break;
}
if (!error.empty()) { if (!error.empty()) {
wxString msg_text = _L("Adaptive Pressure Advance model validation failed:\n"); wxString msg_text = _L("Adaptive Pressure Advance model validation failed:\n");
msg_text += from_u8(error); msg_text += from_u8(error);
+146 -1
View File
@@ -1267,6 +1267,8 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
if (current_top_layer_only != required_top_layer_only) if (current_top_layer_only != required_top_layer_only)
m_viewer.toggle_top_layer_only_view_range(); m_viewer.toggle_top_layer_only_view_range();
read_reduced_detail_preferences();
// ORCA: darken the layers the preview layer slider is not scrubbed to // ORCA: darken the layers the preview layer slider is not scrubbed to
m_viewer.set_dim_previous_layers(get_app_config()->get_bool("preview_dim_previous_layers")); m_viewer.set_dim_previous_layers(get_app_config()->get_bool("preview_dim_previous_layers"));
m_viewer.set_dim_previous_layers_brightness(0.01f * std::stoi(get_app_config()->get("preview_dim_previous_layers_brightness"))); m_viewer.set_dim_previous_layers_brightness(0.01f * std::stoi(get_app_config()->get("preview_dim_previous_layers_brightness")));
@@ -1491,6 +1493,10 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
// BBS: data for rendering color arrangement recommendation // BBS: data for rendering color arrangement recommendation
m_nozzle_nums = print.config().option<ConfigOptionFloats>("nozzle_diameter")->values.size(); m_nozzle_nums = print.config().option<ConfigOptionFloats>("nozzle_diameter")->values.size();
// the shell drag mode hides the infill only where the profile covers it: with no top or bottom
// shell, or no walls, the infill is the surface. Per-object settings are not consulted.
const PrintRegionConfig& region = print.default_region_config();
m_viewer.set_reduced_detail_hide_infill(region.top_shell_layers.value > 0 && region.bottom_shell_layers.value > 0 && region.wall_loops.value > 0);
// Orca hack: Hide filament group for non-bbl printers // Orca hack: Hide filament group for non-bbl printers
if (!print.is_BBL_printer()) m_nozzle_nums = 1; if (!print.is_BBL_printer()) m_nozzle_nums = 1;
std::vector<int> filament_maps = print.get_filament_maps(); std::vector<int> filament_maps = print.get_filament_maps();
@@ -1661,6 +1667,7 @@ void GCodeViewer::reset_shell()
{ {
m_shells.volumes.clear(); m_shells.volumes.clear();
m_shells.print_id = -1; m_shells.print_id = -1;
m_shells.with_wipe_tower = false;
m_shell_bounding_box = BoundingBoxf3(); m_shell_bounding_box = BoundingBoxf3();
} }
@@ -1697,7 +1704,12 @@ void GCodeViewer::reset()
void GCodeViewer::render_scene(int canvas_width, int canvas_height) void GCodeViewer::render_scene(int canvas_width, int canvas_height)
{ {
glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glEnable(GL_DEPTH_TEST));
render_shells(canvas_width, canvas_height); // while dragging in the solid model mode, the objects stand in for their toolpaths, cut to the
// visible layer range; the toolpath set then holds only the range's bottom and top layers
if (m_viewer.is_reduced_detail() && solid_model_enabled())
render_solid_model(canvas_width, canvas_height);
else
render_shells(canvas_width, canvas_height);
if (m_viewer.get_extrusion_roles_count() == 0) if (m_viewer.get_extrusion_roles_count() == 0)
return; return;
@@ -2020,6 +2032,56 @@ void GCodeViewer::update_layers_slider_mode()
// TODO m_layers_slider->SetModeAndOnlyExtruder(one_extruder_printed_model, only_extruder); // TODO m_layers_slider->SetModeAndOnlyExtruder(one_extruder_printed_model, only_extruder);
} }
void GCodeViewer::set_interacting(bool interacting)
{
// with no shells to stand in for the toolpaths, the solid model would leave only the end layers
const bool usable = !solid_model_enabled() || !m_shells.volumes.empty();
m_viewer.set_reduced_detail(interacting && usable);
}
void GCodeViewer::read_reduced_detail_preferences()
{
m_reduced_detail_mode = reduced_detail_mode_from_string(get_app_config()->get("preview_reduced_detail_mode"));
apply_reduced_detail_settings();
}
void GCodeViewer::apply_reduced_detail_settings()
{
m_viewer.set_reduced_detail_mode(m_reduced_detail_mode);
}
void GCodeViewer::set_reduced_detail_mode(const std::string& mode)
{
const bool was_solid = solid_model_enabled();
m_reduced_detail_mode = reduced_detail_mode_from_string(mode);
apply_reduced_detail_settings();
reload_shells_if_solid_model_changed(was_solid);
}
libvgcode::EReducedDetailMode GCodeViewer::reduced_detail_mode_from_string(const std::string& mode)
{
if (mode == "solid")
return libvgcode::EReducedDetailMode::EndLayersOnly;
if (mode == "shell")
return libvgcode::EReducedDetailMode::ShellOnly;
return libvgcode::EReducedDetailMode::Off;
}
void GCodeViewer::reload_shells_if_solid_model_changed(bool was_enabled)
{
if (was_enabled == solid_model_enabled() || m_shells.print_id == -1)
return;
// only the prime tower comes and goes with the mode: a full reload would drop the shells
// whenever the print has moved on since they were loaded, leaving the solid model nothing to draw
if (wxGetApp().plater() == nullptr)
return;
// the shells are loaded from the current plate's print, which is not the plater's own
const Print& print = wxGetApp().plater()->get_partplate_list().get_current_fff_print();
if (static_cast<int>(print.id().id) != m_shells.print_id)
return;
update_shell_wipe_tower(print, m_gl_data_initialized);
}
void GCodeViewer::set_layers_z_range(const std::array<unsigned int, 2>& layers_z_range) void GCodeViewer::set_layers_z_range(const std::array<unsigned int, 2>& layers_z_range)
{ {
m_viewer.set_layers_view_range(static_cast<uint32_t>(layers_z_range[0]), static_cast<uint32_t>(layers_z_range[1])); m_viewer.set_layers_view_range(static_cast<uint32_t>(layers_z_range[0]), static_cast<uint32_t>(layers_z_range[1]));
@@ -2344,7 +2406,11 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const
void GCodeViewer::load_shells(const Print& print, bool initialized, bool force_previewing) void GCodeViewer::load_shells(const Print& print, bool initialized, bool force_previewing)
{ {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": initialized=%1%, force_previewing=%2%")%initialized %force_previewing; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": initialized=%1%, force_previewing=%2%")%initialized %force_previewing;
// the shells can load before the first G-code does, so the preferences are read here as well
read_reduced_detail_preferences();
if ((print.id().id == m_shells.print_id)&&(print.get_modified_count() == m_shells.print_modify_count)) { if ((print.id().id == m_shells.print_id)&&(print.get_modified_count() == m_shells.print_modify_count)) {
// the prime tower comes and goes on its own, without reloading the objects
update_shell_wipe_tower(print, initialized);
//BBS: update force previewing logic //BBS: update force previewing logic
if (force_previewing) if (force_previewing)
m_shells.previewing = force_previewing; m_shells.previewing = force_previewing;
@@ -2453,10 +2519,45 @@ void GCodeViewer::load_shells(const Print& print, bool initialized, bool force_p
m_shells.print_id = print.id().id; m_shells.print_id = print.id().id;
m_shells.print_modify_count = print.get_modified_count(); m_shells.print_modify_count = print.get_modified_count();
m_shells.previewing = true; m_shells.previewing = true;
update_shell_wipe_tower(print, initialized);
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": shell loaded, id change to %1%, modify_count %2%, object count %3%, glvolume count %4%") BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": shell loaded, id change to %1%, modify_count %2%, object count %3%, glvolume count %4%")
% m_shells.print_id % m_shells.print_modify_count % object_count %m_shells.volumes.volumes.size(); % m_shells.print_id % m_shells.print_modify_count % object_count %m_shells.volumes.volumes.size();
} }
// The prime tower as it was sliced, so that the solid model shows what the print shows. It keeps its
// opaque colour, so it never appears among the translucent shells, and stays out of their bounding box.
void GCodeViewer::update_shell_wipe_tower(const Print& print, bool initialized)
{
const bool with_wipe_tower = solid_model_enabled() && print.is_step_done(psWipeTower) && print.wipe_tower_data().wipe_tower_mesh_data;
if (with_wipe_tower == m_shells.with_wipe_tower)
return;
m_shells.with_wipe_tower = with_wipe_tower;
GLVolumePtrs& volumes = m_shells.volumes.volumes;
if (!with_wipe_tower) {
volumes.erase(std::remove_if(volumes.begin(), volumes.end(), [](GLVolume* volume) {
if (!volume->is_wipe_tower)
return false;
delete volume;
return true;
}), volumes.end());
return;
}
const PrintConfig& config = print.config();
const int plate_idx = print.get_plate_index();
const Vec3d plate_origin = print.get_plate_origin();
const float x = static_cast<float>(config.wipe_tower_x.get_at(plate_idx) + plate_origin.x());
const float y = static_cast<float>(config.wipe_tower_y.get_at(plate_idx) + plate_origin.y());
const size_t first_new = volumes.size();
m_shells.volumes.load_real_wipe_tower_preview(1000 + plate_idx, x, y, print.wipe_tower_data().wipe_tower_mesh_data->real_wipe_tower_mesh,
print.wipe_tower_data().wipe_tower_mesh_data->real_brim_mesh, true,
static_cast<float>(config.wipe_tower_rotation_angle), false, initialized);
for (size_t i = first_new; i < volumes.size(); ++i) {
volumes[i]->zoom_to_volumes = false;
volumes[i]->force_native_color = true;
volumes[i]->set_render_color();
}
}
void GCodeViewer::render_toolpaths() void GCodeViewer::render_toolpaths()
{ {
const Camera& camera = wxGetApp().plater()->get_camera(); const Camera& camera = wxGetApp().plater()->get_camera();
@@ -2657,6 +2758,50 @@ void GCodeViewer::render_shells(int canvas_width, int canvas_height)
glsafe(::glDepthMask(GL_TRUE)); glsafe(::glDepthMask(GL_TRUE));
} }
// The sliced objects and the prime tower drawn opaque, in their filament colours, cut to the
// visible layer range by the shader's z range. The toolpaths of the range's bottom and top layers
// are drawn afterwards and cap the cut.
void GCodeViewer::render_solid_model(int canvas_width, int canvas_height)
{
if (m_shells.volumes.empty())
return;
// gouraud_light has no z range, so it could not cut the model
GLShaderProgram* shader = wxGetApp().get_shader("gouraud");
if (shader == nullptr)
return;
const libvgcode::Interval& layers = m_viewer.get_layers_view_range();
const float z_top = m_viewer.get_layer_z(layers[1]) - m_z_offset + 0.001f;
const float z_bottom = (layers[0] > 0) ? m_viewer.get_layer_z(layers[0] - 1) - m_z_offset - 0.001f : -FLT_MAX;
std::vector<float> alphas;
alphas.reserve(m_shells.volumes.volumes.size());
for (GLVolume* volume : m_shells.volumes.volumes) {
alphas.push_back(volume->color.a());
volume->color.a(1.0f);
volume->set_render_color();
}
m_shells.volumes.set_z_range(z_bottom, z_top);
// gouraud also clips by this plane, which nothing else sets on the shells
m_shells.volumes.set_clipping_plane(ClippingPlane::ClipsNothing().get_data());
shader->start_using();
// the 3D view leaves its shadow settings on the shared program
shader->set_uniform("shadow_intensity", 0.0f);
const Camera& camera = wxGetApp().plater()->get_camera();
shader->set_uniform("z_far", camera.get_far_z());
shader->set_uniform("z_near", camera.get_near_z());
m_shells.volumes.render(GLVolumeCollection::ERenderType::Opaque, false, camera.get_view_matrix(), camera.get_projection_matrix(), {canvas_width, canvas_height});
shader->stop_using();
m_shells.volumes.set_z_range(-FLT_MAX, FLT_MAX);
size_t k = 0;
for (GLVolume* volume : m_shells.volumes.volumes) {
volume->color.a(alphas[k++]);
volume->set_render_color();
}
}
//BBS //BBS
void GCodeViewer::render_all_plates_stats(const std::vector<const GCodeProcessorResult*>& gcode_result_list, bool show /*= true*/) const { void GCodeViewer::render_all_plates_stats(const std::vector<const GCodeProcessorResult*>& gcode_result_list, bool show /*= true*/) const {
if (!show) if (!show)
+22 -1
View File
@@ -174,6 +174,8 @@ public:
int print_id{-1}; int print_id{-1};
int print_modify_count{-1}; int print_modify_count{-1};
bool previewing{false}; bool previewing{false};
// the prime tower was loaded with the objects, for the solid model
bool with_wipe_tower{false};
}; };
//BBS //BBS
ConflictResultOpt m_conflict_result; ConflictResultOpt m_conflict_result;
@@ -234,6 +236,17 @@ private:
bool m_legend_visible{ true }; bool m_legend_visible{ true };
bool m_legend_enabled{ true }; bool m_legend_enabled{ true };
// the reduced-detail preferences, pushed to libvgcode by apply_reduced_detail_settings()
libvgcode::EReducedDetailMode m_reduced_detail_mode{ libvgcode::EReducedDetailMode::Off };
void read_reduced_detail_preferences();
void apply_reduced_detail_settings();
static libvgcode::EReducedDetailMode reduced_detail_mode_from_string(const std::string& mode);
// in the solid model mode, the sliced objects are drawn as solid shapes instead of toolpaths
bool solid_model_enabled() const { return m_reduced_detail_mode == libvgcode::EReducedDetailMode::EndLayersOnly; }
void render_solid_model(int canvas_width, int canvas_height);
// the prime tower is only among the shells for the solid model, so it is added or removed when that changes
void reload_shells_if_solid_model_changed(bool was_enabled);
void update_shell_wipe_tower(const Print& print, bool initialized);
float m_legend_height; float m_legend_height;
PrintEstimatedStatistics m_print_statistics; PrintEstimatedStatistics m_print_statistics;
@@ -291,7 +304,7 @@ public:
// void _render_calibration_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager); // void _render_calibration_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager);
// void _render_calibration_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager); // void _render_calibration_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager);
// void render_calibration_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager); // void render_calibration_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager);
bool has_data() const { return !m_viewer.get_extrusion_roles().empty(); } bool has_data() const { return m_viewer.get_extrusion_roles_count() != 0; }
bool can_export_toolpaths() const; bool can_export_toolpaths() const;
std::vector<int> get_plater_extruder(); std::vector<int> get_plater_extruder();
@@ -363,6 +376,14 @@ public:
void set_dim_previous_layers_brightness(float value) { m_viewer.set_dim_previous_layers_brightness(value); } void set_dim_previous_layers_brightness(float value) { m_viewer.set_dim_previous_layers_brightness(value); }
float get_dim_previous_layers_brightness() const { return m_viewer.get_dim_previous_layers_brightness(); } float get_dim_previous_layers_brightness() const { return m_viewer.get_dim_previous_layers_brightness(); }
// whether the mouse is holding either slider's handle
bool is_slider_dragging() const { return m_layers_slider->is_dragging() || m_moves_slider->is_dragging(); }
// while the user drags the camera or a slider, draw the reduced set, if the preference asks for one
void set_interacting(bool interacting);
bool is_reduced_detail() const { return m_viewer.is_reduced_detail(); }
// the preference's string value: "off", "solid" or "shell"
void set_reduced_detail_mode(const std::string& mode);
void set_layers_z_range(const std::array<unsigned int, 2>& layers_z_range); void set_layers_z_range(const std::array<unsigned int, 2>& layers_z_range);
bool is_legend_shown() const { return m_legend_visible && m_legend_enabled; } bool is_legend_shown() const { return m_legend_visible && m_legend_enabled; }
+66 -9
View File
@@ -2084,6 +2084,11 @@ void GLCanvas3D::_render_frame(bool scene_dirty, bool only_init)
const bool overlay_tick = m_fps_overlay_tick; const bool overlay_tick = m_fps_overlay_tick;
m_fps_overlay_tick = false; m_fps_overlay_tick = false;
// Whether the preview draws its reduced set is decided before the cached scene is consulted,
// since switching changes what the scene pass draws.
if (m_canvas_type == ECanvasType::CanvasPreview && m_render_preview && m_gcode_viewer.has_data() && _update_preview_interaction())
scene_dirty = true;
// An overlay-only frame reuses the last scene pass. The overlay is rebuilt either way, and drawn // An overlay-only frame reuses the last scene pass. The overlay is rebuilt either way, and drawn
// below once it is known whether the frame differs from the one on screen. // below once it is known whether the frame differs from the one on screen.
const bool reuse_scene = !scene_dirty && _can_reuse_cached_scene(camera); const bool reuse_scene = !scene_dirty && _can_reuse_cached_scene(camera);
@@ -3277,9 +3282,16 @@ void GLCanvas3D::bind_event_handlers()
if (m_selection_edit.kind != SelectionEdit::None) if (m_selection_edit.kind != SelectionEdit::None)
finish_selection_edit(); finish_selection_edit();
ImGui::SetWindowFocus(nullptr); ImGui::SetWindowFocus(nullptr);
// a drag cut short never sees its button release, which would leave the reduced set drawn
if (m_canvas_type == CanvasPreview && m_mouse.dragging && m_gcode_viewer.is_reduced_detail())
mouse_up_cleanup();
render(); render();
evt.Skip(); evt.Skip();
}); });
m_canvas->Bind(wxEVT_MOUSE_CAPTURE_LOST, [this](wxMouseCaptureLostEvent&) {
if (m_canvas_type == CanvasPreview && m_mouse.dragging && m_gcode_viewer.is_reduced_detail())
mouse_up_cleanup();
});
m_event_handlers_bound = true; m_event_handlers_bound = true;
m_canvas->Bind(wxEVT_GESTURE_PAN, &GLCanvas3D::on_gesture, this); m_canvas->Bind(wxEVT_GESTURE_PAN, &GLCanvas3D::on_gesture, this);
@@ -3355,6 +3367,17 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
m_overlay_dirty |= imgui_requires_extra_frame; m_overlay_dirty |= imgui_requires_extra_frame;
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT #endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
m_dirty |= GLTexture::Compressor::has_compressed_texture_to_refresh(); m_dirty |= GLTexture::Compressor::has_compressed_texture_to_refresh();
// the render timer only wakes the idle loop; the frame that puts the preview's toolpaths back
// after a wheel burst has to be asked for here, once the settle time is really up
if (m_preview_settle_pending) {
const auto now = std::chrono::steady_clock::now();
if (now >= m_preview_interaction_until) {
m_preview_settle_pending = false;
m_dirty = true;
}
else // the timer fired early
schedule_extra_frame(static_cast<int>(std::chrono::duration_cast<std::chrono::milliseconds>(m_preview_interaction_until - now).count()) + 1);
}
if (!m_dirty && !m_overlay_dirty) if (!m_dirty && !m_overlay_dirty)
return; return;
@@ -3885,6 +3908,10 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt)
return; return;
} }
// only a wheel the panels did not take moves the camera
if (m_canvas_type == CanvasPreview)
note_preview_interaction();
#ifdef __WXMSW__ #ifdef __WXMSW__
// For some reason the Idle event is not being generated after the mouse scroll event in case of scrolling with the two fingers on the touch pad, // For some reason the Idle event is not being generated after the mouse scroll event in case of scrolling with the two fingers on the touch pad,
// if the event is not allowed to be passed further. // if the event is not allowed to be passed further.
@@ -3985,6 +4012,11 @@ void GLCanvas3D::on_fps_overlay_timer(wxTimerEvent& evt)
wxWakeUpIdle(); wxWakeUpIdle();
} }
void GLCanvas3D::note_preview_interaction()
{
m_preview_interaction_until = std::chrono::steady_clock::now() + std::chrono::milliseconds(150);
}
void GLCanvas3D::schedule_extra_frame(int milliseconds) void GLCanvas3D::schedule_extra_frame(int milliseconds)
{ {
// Schedule idle event right now // Schedule idle event right now
@@ -5631,6 +5663,9 @@ void GLCanvas3D::mouse_up_cleanup()
m_mouse.ignore_left_up = false; m_mouse.ignore_left_up = false;
m_mouse.ignore_right_up = false; m_mouse.ignore_right_up = false;
m_dirty = true; m_dirty = true;
// the frame that follows a release puts the preview's toolpaths back, and on some platforms
// no idle event follows a button release until the next input
wxWakeUpIdle();
if (m_canvas->HasCapture()) if (m_canvas->HasCapture())
m_canvas->ReleaseMouse(); m_canvas->ReleaseMouse();
@@ -6546,11 +6581,8 @@ void GLCanvas3D::render_thumbnail_internal(ThumbnailData& thumbnail_data, const
// glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 1.0f)); // glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 1.0f));
BOOST_LOG_TRIVIAL(info) << boost::format("render_thumbnail: finished"); BOOST_LOG_TRIVIAL(info) << boost::format("render_thumbnail: finished");
// Puts the canvas viewport back in place of the thumbnail one set above. The CLI renders // Puts the canvas viewport back in place of the thumbnail one set above.
// thumbnails with no application and no plater, so there is no canvas viewport to restore. wxGetApp().plater()->get_camera().apply_viewport();
if (wxTheApp != nullptr)
if (Plater *plater = wxGetApp().plater(); plater != nullptr)
plater->get_camera().apply_viewport();
} }
void GLCanvas3D::render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, void GLCanvas3D::render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params,
@@ -7782,13 +7814,20 @@ bool GLCanvas3D::_is_scene_cacheable() const
return false; return false;
#endif #endif
// The scene follows the cursor during a drag, under a gizmo that draws at the cursor, and while // The scene follows the cursor while the user drags, under a gizmo that draws at the cursor, and
// the cursor is on the layer height bar, where the object shader draws a band at its height. // while the cursor is on the layer height bar, where the object shader draws a band at its height.
const GLGizmoBase* gizmo = m_gizmos.get_current(); const GLGizmoBase* gizmo = m_gizmos.get_current();
const bool cursor_on_layers_bar = is_layers_editing_enabled() && const bool cursor_on_layers_bar = is_layers_editing_enabled() &&
m_layers_editing.bar_rect_contains(*this, (float)m_mouse.position.x(), (float)m_mouse.position.y()); m_layers_editing.bar_rect_contains(*this, (float)m_mouse.position.x(), (float)m_mouse.position.y());
return !m_mouse.dragging && !m_gizmos.is_dragging() && !m_rectangle_selection.is_dragging() && return !is_user_interacting() && (gizmo == nullptr || !gizmo->render_follows_cursor()) && !cursor_on_layers_bar;
(gizmo == nullptr || !gizmo->render_follows_cursor()) && !cursor_on_layers_bar; }
// Whether the user is holding something that moves the scene: the camera, the navigator, a gizmo,
// the rectangle selection or a preview slider.
bool GLCanvas3D::is_user_interacting() const
{
return m_mouse.dragging || m_navigator_dragging || m_gizmos.is_dragging() || m_rectangle_selection.is_dragging() ||
m_gcode_viewer.is_slider_dragging();
} }
bool GLCanvas3D::_is_frame_skipping_enabled() const bool GLCanvas3D::_is_frame_skipping_enabled() const
@@ -8779,6 +8818,24 @@ void GLCanvas3D::_render_wireframe_overlay()
shader->stop_using(); shader->stop_using();
} }
// The reduced set is drawn while the camera, the navigator or either slider is dragged. A wheel
// step has no duration, so it holds the reduced set for a settle time instead, and the frame that
// restores the full toolpaths is scheduled for when that time runs out. Returns whether what the scene
// pass draws changed, since a frame that reuses the cached scene would hide the change.
bool GLCanvas3D::_update_preview_interaction()
{
const auto now = std::chrono::steady_clock::now();
const bool settling = now < m_preview_interaction_until;
const bool dragging = is_user_interacting();
const bool was_reduced = m_gcode_viewer.is_reduced_detail();
m_gcode_viewer.set_interacting(dragging || settling);
if (settling && !dragging && m_gcode_viewer.is_reduced_detail()) {
m_preview_settle_pending = true;
schedule_extra_frame(static_cast<int>(std::chrono::duration_cast<std::chrono::milliseconds>(m_preview_interaction_until - now).count()) + 1);
}
return m_gcode_viewer.is_reduced_detail() != was_reduced;
}
//BBS: GUI refactor: add canvas size as parameters //BBS: GUI refactor: add canvas size as parameters
void GLCanvas3D::_render_gcode(int canvas_width, int canvas_height) void GLCanvas3D::_render_gcode(int canvas_width, int canvas_height)
{ {
+11
View File
@@ -656,6 +656,10 @@ private:
ECursorType m_cursor_type; ECursorType m_cursor_type;
GLSelectionRectangle m_rectangle_selection; GLSelectionRectangle m_rectangle_selection;
bool m_navigator_dragging{ false }; bool m_navigator_dragging{ false };
// until when a wheel step keeps the preview's reduced set drawn
std::chrono::time_point<std::chrono::steady_clock> m_preview_interaction_until{};
// whether the frame that restores the toolpaths once that time is up is still owed
bool m_preview_settle_pending{ false };
//BBS:add plate related logic //BBS:add plate related logic
mutable std::vector<int> m_hover_volume_idxs; mutable std::vector<int> m_hover_volume_idxs;
@@ -1222,6 +1226,10 @@ public:
void msw_rescale() { m_gcode_viewer.invalidate_legend(); } void msw_rescale() { m_gcode_viewer.invalidate_legend(); }
void request_extra_frame() { m_extra_frame_requested = true; } void request_extra_frame() { m_extra_frame_requested = true; }
// whether the user is holding the camera, the navigator, a gizmo, the rectangle selection or a preview slider
bool is_user_interacting() const;
// a wheel step is over before the next frame, so it holds the preview's reduced set for a settle time
void note_preview_interaction();
void schedule_extra_frame(int milliseconds); void schedule_extra_frame(int milliseconds);
@@ -1372,6 +1380,9 @@ private:
//BBS: GUI refactor: add canvas size as parameters //BBS: GUI refactor: add canvas size as parameters
void _render_gcode(int canvas_width, int canvas_height); void _render_gcode(int canvas_width, int canvas_height);
void _render_gcode_overlay(int canvas_width, int canvas_height); void _render_gcode_overlay(int canvas_width, int canvas_height);
// decides whether the preview draws its reduced set this frame and returns whether what the scene
// pass draws changed; runs before the cached scene is consulted
bool _update_preview_interaction();
//BBS: render a plane for assemble //BBS: render a plane for assemble
void _render_plane() const; void _render_plane() const;
void _render_selection(); void _render_selection();
-32
View File
@@ -14,9 +14,6 @@
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <boost/any.hpp> #include <boost/any.hpp>
#include <wx/filename.h>
#include <wx/filesys.h>
#if __APPLE__ #if __APPLE__
#import <IOKit/pwr_mgt/IOPMLib.h> #import <IOKit/pwr_mgt/IOPMLib.h>
#elif _WIN32 #elif _WIN32
@@ -30,13 +27,11 @@
#include "AboutDialog.hpp" #include "AboutDialog.hpp"
#include "MsgDialog.hpp" #include "MsgDialog.hpp"
#include "Plater.hpp"
#include "format.hpp" #include "format.hpp"
#include "WebUserLoginDialog.hpp" #include "WebUserLoginDialog.hpp"
#include "libslic3r/Print.hpp" #include "libslic3r/Print.hpp"
#include "libslic3r/Utils.hpp"
namespace Slic3r { namespace Slic3r {
@@ -536,11 +531,6 @@ boost::filesystem::path into_path(const wxString &str)
return boost::filesystem::path(str.wx_str()); return boost::filesystem::path(str.wx_str());
} }
wxString file_url_from_path(const boost::filesystem::path &path)
{
return wxFileSystem::FileNameToURL(wxFileName(from_path(path)));
}
void about() void about()
{ {
AboutDialog dlg; AboutDialog dlg;
@@ -645,26 +635,4 @@ void desktop_open_any_folder( const std::string& path )
} }
bool desktop_open_project_attachment(wxWindow *parent, const boost::filesystem::path &path)
{
// The auxiliary path is UTF-8, which is what boost::filesystem reads a narrow string as.
const boost::filesystem::path aux_root(wxGetApp().plater()->model().get_auxiliary_file_temp_path());
boost::system::error_code ec;
if (!is_absolute_path_within_root(path, aux_root) || !boost::filesystem::is_regular_file(path, ec))
return false;
// Attachments come with the project and carry no download mark, so the desktop would open them without a warning.
if (!is_safe_to_open_file_name(path.filename().string())) {
MessageDialog dlg(parent,
wxString::Format(_L("\"%s\" is not a plain document, image or model file. Opening it may run it as a "
"program or script on this computer.\n\n"
"Only open attachments from projects you trust. Open it anyway?"),
from_path(path.filename())),
_L("Open attachment"), wxICON_WARNING | wxYES_NO);
if (dlg.ShowModal() != wxID_YES)
return false;
}
return wxLaunchDefaultApplication(from_path(path), 0);
}
} } } }
-6
View File
@@ -76,9 +76,6 @@ std::string into_u8(const wxString &str);
wxString from_path(const boost::filesystem::path &path); wxString from_path(const boost::filesystem::path &path);
// boost path from wxString // boost path from wxString
boost::filesystem::path into_path(const wxString &str); boost::filesystem::path into_path(const wxString &str);
// file:// URL of a local path, percent-encoded so characters such as '#', '%' and '?' stay part of the path.
// Append any query or fragment to the result.
wxString file_url_from_path(const boost::filesystem::path &path);
// Display an About dialog // Display an About dialog
extern void about(); extern void about();
@@ -86,9 +83,6 @@ extern void about();
extern void desktop_open_datadir_folder(); extern void desktop_open_datadir_folder();
// Ask the destop to open one folder // Ask the destop to open one folder
extern void desktop_open_any_folder(const std::string& path); extern void desktop_open_any_folder(const std::string& path);
// Ask the desktop to open a file from the project's auxiliary directory, after a confirmation
// unless its type is known to be plain content. Returns false if the file was not opened.
extern bool desktop_open_project_attachment(wxWindow *parent, const boost::filesystem::path &path);
} // namespace GUI } // namespace GUI
} // namespace Slic3r } // namespace Slic3r
+5 -26
View File
@@ -1512,33 +1512,11 @@ int GUI_App::install_plugin(std::string name, std::string package_name, InstallP
size_t n = mz_zip_reader_get_extra(&archive, stat.m_file_index, extra.data(), extra.size()); size_t n = mz_zip_reader_get_extra(&archive, stat.m_file_index, extra.data(), extra.size());
dest_file = decode(extra.substr(0, n), stat.m_filename); dest_file = decode(extra.substr(0, n), stat.m_filename);
} }
if (!is_path_within_root(dest_file, plugin_folder)) {
BOOST_LOG_TRIVIAL(error) << "[install_plugin] entry " << dest_file << " resolves outside " << plugin_folder.string();
close_zip_reader(&archive);
if (pro_fn) { pro_fn(InstallStatusUnzipFailed, 0, cancel); }
return InstallStatusUnzipFailed;
}
auto dest_path = plugin_folder / dest_file; auto dest_path = plugin_folder / dest_file;
boost::filesystem::create_directories(dest_path.parent_path());
std::string dest_zip_file = encode_path(dest_path.string().c_str()); std::string dest_zip_file = encode_path(dest_path.string().c_str());
#ifndef WIN32
// Validate a symlink's target before anything at the destination is replaced.
const bool is_link = S_ISLNK(stat.m_external_attr >> 16);
std::string link;
if (is_link) {
link.assign(stat.m_uncomp_size, 0);
if (!mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, link.data(), stat.m_uncomp_size, 0) ||
!is_symlink_target_within_root(dest_file, link, plugin_folder)) {
BOOST_LOG_TRIVIAL(error) << "[install_plugin] link " << dest_file << " -> " << link << " is unreadable or resolves outside " << plugin_folder.string();
close_zip_reader(&archive);
if (pro_fn) { pro_fn(InstallStatusUnzipFailed, 0, cancel); }
return InstallStatusUnzipFailed;
}
}
#endif
try { try {
boost::filesystem::create_directories(dest_path.parent_path()); if (fs::exists(dest_path)) {
// symlink_status so that an existing symlink, dangling or not, is replaced rather than written through.
if (fs::exists(fs::symlink_status(dest_path))) {
boost::system::error_code ec; boost::system::error_code ec;
fs::remove(dest_path, ec); fs::remove(dest_path, ec);
if (ec) { if (ec) {
@@ -1566,8 +1544,9 @@ int GUI_App::install_plugin(std::string name, std::string package_name, InstallP
} }
mz_bool res = 0; mz_bool res = 0;
#ifndef WIN32 #ifndef WIN32
if (is_link) { if (S_ISLNK(stat.m_external_attr >> 16)) {
res = 1; std::string link(stat.m_uncomp_size + 1, 0);
res = mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, link.data(), stat.m_uncomp_size, 0);
try { try {
boost::filesystem::create_symlink(link, dest_path); boost::filesystem::create_symlink(link, dest_path);
} catch (const std::exception &e) { } catch (const std::exception &e) {
+7
View File
@@ -483,6 +483,11 @@ void IMSlider::draw_background_and_groove(const ImRect& bg_rect, const ImRect& g
ImGui::RenderFrame(groove.Min, groove.Max, groove_col, false, 0.5 * groove.GetWidth()); ImGui::RenderFrame(groove.Min, groove.Max, groove_col, false, 0.5 * groove.GetWidth());
} }
bool IMSlider::is_dragging() const
{
return GImGui != nullptr && m_imgui_id != 0 && GImGui->ActiveId == m_imgui_id && GImGui->IO.MouseDown[0];
}
bool IMSlider::horizontal_slider(const char* str_id, int* value, int v_min, int v_max, const ImVec2& size, float scale) bool IMSlider::horizontal_slider(const char* str_id, int* value, int v_min, int v_max, const ImVec2& size, float scale)
{ {
ImGuiWindow* window = ImGui::GetCurrentWindow(); ImGuiWindow* window = ImGui::GetCurrentWindow();
@@ -491,6 +496,7 @@ bool IMSlider::horizontal_slider(const char* str_id, int* value, int v_min, int
ImGuiContext& context = *GImGui; ImGuiContext& context = *GImGui;
const ImGuiID id = window->GetID(str_id); const ImGuiID id = window->GetID(str_id);
m_imgui_id = id;
const ImVec2 pos = window->DC.CursorPos; const ImVec2 pos = window->DC.CursorPos;
const ImRect draw_region(pos, pos + size); const ImRect draw_region(pos, pos + size);
@@ -883,6 +889,7 @@ bool IMSlider::vertical_slider(const char* str_id, int* higher_value, int* lower
ImGuiContext& context = *GImGui; ImGuiContext& context = *GImGui;
const ImGuiID id = window->GetID(str_id); const ImGuiID id = window->GetID(str_id);
m_imgui_id = id;
const ImVec2 pos = window->DC.CursorPos; const ImVec2 pos = window->DC.CursorPos;
const ImRect draw_region(pos, pos + size); const ImRect draw_region(pos, pos + size);
+5
View File
@@ -118,6 +118,9 @@ public:
//BBS update scroll value changed //BBS update scroll value changed
bool is_dirty() { return m_dirty; } bool is_dirty() { return m_dirty; }
// whether the mouse is holding this slider's handle, read from ImGui's active id rather than
// from the dirty flag, which is raised and consumed inside a single frame
bool is_dragging() const;
void set_as_dirty(bool dirty = true) { m_dirty = dirty; } void set_as_dirty(bool dirty = true) { m_dirty = dirty; }
bool is_need_post_tick_event() { return m_is_need_post_tick_changed_event; } bool is_need_post_tick_event() { return m_is_need_post_tick_changed_event; }
void reset_post_tick_event(bool val = false) { void reset_post_tick_event(bool val = false) {
@@ -182,6 +185,8 @@ private:
int m_higher_value; int m_higher_value;
int m_one_layer_value; // ORCA int m_one_layer_value; // ORCA
bool m_dirty = false; bool m_dirty = false;
// the ImGui id of the slider widget, as of its last render
unsigned int m_imgui_id = 0;
bool m_render_as_disabled{ false }; bool m_render_as_disabled{ false };
+216 -150
View File
@@ -1997,126 +1997,6 @@ bool MainFrame::can_reslice() const
return (m_plater != nullptr) && !m_plater->model().objects.empty(); return (m_plater != nullptr) && !m_plater->model().objects.empty();
} }
namespace {
// Orca: stable key persisted by the "remember last print action" preference. Reordering PrintSelectType
// must not silently remap a saved preference, so the key never derives from the enum value.
const char* print_select_type_key(MainFrame::PrintSelectType type)
{
switch (type) {
case MainFrame::ePrintAll: return "print_all";
case MainFrame::ePrintPlate: return "print_plate";
case MainFrame::eExportSlicedFile: return "export_sliced_file";
case MainFrame::eExportAllSlicedFile: return "export_all_sliced_file";
case MainFrame::eExportGcode: return "export_gcode";
case MainFrame::eSendGcode: return "send_gcode";
case MainFrame::eSendToPrinter: return "send_to_printer";
case MainFrame::eSendToPrinterAll: return "send_to_printer_all";
case MainFrame::ePrintMultiMachine: return "print_multi_machine";
case MainFrame::eUploadGcode: break; // Orca: no dropdown entry, never selectable
}
return "";
}
// Orca: single source for the print button and print dropdown labels
wxString print_select_type_label(MainFrame::PrintSelectType type)
{
switch (type) {
case MainFrame::ePrintAll: return _L("Print all");
case MainFrame::ePrintPlate: return _L("Print plate");
case MainFrame::eExportSlicedFile: return _L("Export plate sliced file");
case MainFrame::eExportAllSlicedFile: return _L("Export all sliced file");
case MainFrame::eExportGcode: return _L("Export G-code file");
case MainFrame::eSendGcode: return _L_CONTEXT("Print", "Verb");
case MainFrame::eSendToPrinter: return _L("Send");
case MainFrame::eSendToPrinterAll: return _L("Send all");
case MainFrame::ePrintMultiMachine: return _L("Send to Multi-device");
case MainFrame::eUploadGcode: break; // Orca: no dropdown entry, never selectable
}
return _L("Print plate");
}
} // namespace
std::vector<MainFrame::PrintSelectType> MainFrame::available_print_actions() const
{
std::vector<PrintSelectType> actions;
const auto preset_bundle = wxGetApp().preset_bundle;
const bool use_printer_agents = wxGetApp().app_config->get_bool("use_printer_agents");
if (preset_bundle && !preset_bundle->is_bbl_vendor() && !use_printer_agents) {
// ThirdParty actions
actions.push_back(eSendGcode);
// Orca: when the printer accepts a .gcode.3mf (the "Support 3MF as gcode" option),
// also offer exporting the sliced .gcode.3mf bundle
const auto* use_3mf_opt = preset_bundle->printers.get_edited_preset().config.option<ConfigOptionBool>("use_3mf");
if (use_3mf_opt != nullptr && use_3mf_opt->value)
actions.push_back(eExportSlicedFile);
actions.push_back(eExportGcode);
return actions;
}
// Orca Slicer actions
bool support_send = true;
bool support_print_all = true;
if (preset_bundle && !preset_bundle->use_bbl_network() && !use_printer_agents) {
support_send = false; // All 3rd print hosts do not have the send options
const auto& cfg = preset_bundle->printers.get_edited_preset().config;
const auto* host_type_opt = cfg.option<ConfigOptionEnum<PrintHostType>>("host_type");
// Only simply print support uploading all plates
support_print_all = host_type_opt != nullptr && host_type_opt->value == PrintHostType::htSimplyPrint;
}
actions.push_back(ePrintPlate);
if (support_print_all)
actions.push_back(ePrintAll);
if (support_send) {
actions.push_back(eSendToPrinter);
actions.push_back(eSendToPrinterAll);
}
if (enable_multi_machine)
actions.push_back(ePrintMultiMachine);
actions.push_back(eExportSlicedFile);
actions.push_back(eExportAllSlicedFile);
actions.push_back(eExportGcode);
return actions;
}
void MainFrame::select_print_action(PrintSelectType select_type)
{
m_print_btn->SetLabel(print_select_type_label(select_type));
m_print_select = select_type;
remember_print_select(select_type);
m_print_enable = get_enable_print_status();
m_print_btn->Enable(m_print_enable);
this->Layout();
fit_tab_labels(); // ORCA on label change
}
void MainFrame::remember_print_select(PrintSelectType select_type)
{
if (!wxGetApp().app_config->get_bool("remember_print_action"))
return;
// AppConfig is marked dirty here and flushed by the regular autosave
wxGetApp().app_config->set("last_print_action", print_select_type_key(select_type));
}
bool MainFrame::get_remembered_print_select(PrintSelectType& out) const
{
if (!wxGetApp().app_config->get_bool("remember_print_action"))
return false;
const std::string saved = wxGetApp().app_config->get("last_print_action");
if (saved.empty())
return false;
// Orca: only restore an action the current printer actually offers in the dropdown
for (PrintSelectType type : available_print_actions()) {
if (saved == print_select_type_key(type)) {
out = type;
return true;
}
}
return false;
}
wxBoxSizer* MainFrame::create_side_tools() wxBoxSizer* MainFrame::create_side_tools()
{ {
enable_multi_machine = wxGetApp().is_enable_multi_machine(); enable_multi_machine = wxGetApp().is_enable_multi_machine();
@@ -2136,14 +2016,6 @@ wxBoxSizer* MainFrame::create_side_tools()
m_print_btn = new SideButton(print_panel, _L("Print plate"), ""); m_print_btn = new SideButton(print_panel, _L("Print plate"), "");
m_print_option_btn = new SideButton(print_panel, "", "sidebutton_dropdown", 0, 14); m_print_option_btn = new SideButton(print_panel, "", "sidebutton_dropdown", 0, 14);
// Orca: restore the last used print/export action if the user opted to remember it
PrintSelectType remembered_print_select;
if (get_remembered_print_select(remembered_print_select)) {
m_print_select = remembered_print_select;
m_print_btn->SetLabel(print_select_type_label(remembered_print_select));
fit_tab_labels(); // ORCA on label change
}
auto slice_sizer = new wxBoxSizer(wxHORIZONTAL); auto slice_sizer = new wxBoxSizer(wxHORIZONTAL);
slice_sizer->Add(m_slice_option_btn, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, FromDIP(1)); slice_sizer->Add(m_slice_option_btn, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, FromDIP(1));
slice_sizer->Add(m_slice_btn, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(1)); slice_sizer->Add(m_slice_btn, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(1));
@@ -2306,15 +2178,193 @@ wxBoxSizer* MainFrame::create_side_tools()
m_print_option_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) m_print_option_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event)
{ {
SidePopup* p = new SidePopup(this); SidePopup* p = new SidePopup(this);
for (PrintSelectType type : available_print_actions()) {
SideButton* btn = new SideButton(p, print_select_type_label(type), ""); if (wxGetApp().preset_bundle
btn->SetCornerRadius(0); && !wxGetApp().preset_bundle->is_bbl_vendor()
btn->Bind(wxEVT_BUTTON, [this, p, type](wxCommandEvent&) { && !wxGetApp().app_config->get_bool("use_printer_agents")) {
select_print_action(type); // ThirdParty Buttons
SideButton* export_gcode_btn = new SideButton(p, _L("Export G-code file"), "");
export_gcode_btn->SetCornerRadius(0);
export_gcode_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
m_print_btn->SetLabel(_L("Export G-code file"));
m_print_select = eExportGcode;
m_print_enable = get_enable_print_status();
m_print_btn->Enable(m_print_enable);
this->Layout();
fit_tab_labels(); // ORCA on label change
p->Dismiss(); p->Dismiss();
}); });
p->append_button(btn);
// upload and print
SideButton* send_gcode_btn = new SideButton(p, _L_CONTEXT("Print", "Verb"), "");
send_gcode_btn->SetCornerRadius(0);
send_gcode_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
m_print_btn->SetLabel(_L_CONTEXT("Print", "Verb"));
m_print_select = eSendGcode;
m_print_enable = get_enable_print_status();
m_print_btn->Enable(m_print_enable);
this->Layout();
fit_tab_labels(); // ORCA on label change
p->Dismiss();
});
p->append_button(send_gcode_btn);
// Orca: when the printer accepts a .gcode.3mf (the "Support 3MF as gcode" option),
// also offer exporting the sliced .gcode.3mf bundle
const auto& printer_config = wxGetApp().preset_bundle->printers.get_edited_preset().config;
const auto* use_3mf_opt = printer_config.option<ConfigOptionBool>("use_3mf");
if (use_3mf_opt != nullptr && use_3mf_opt->value) {
SideButton* export_sliced_file_btn = new SideButton(p, _L("Export plate sliced file"), "");
export_sliced_file_btn->SetCornerRadius(0);
export_sliced_file_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
m_print_btn->SetLabel(_L("Export plate sliced file"));
m_print_select = eExportSlicedFile;
m_print_enable = get_enable_print_status();
m_print_btn->Enable(m_print_enable);
this->Layout();
fit_tab_labels(); // ORCA on label change
p->Dismiss();
});
p->append_button(export_sliced_file_btn);
}
p->append_button(export_gcode_btn);
} }
else {
//Orca Slicer Buttons
SideButton* print_plate_btn = new SideButton(p, _L("Print plate"), "");
print_plate_btn->SetCornerRadius(0);
SideButton* send_to_printer_btn = new SideButton(p, _L("Send"), "");
send_to_printer_btn->SetCornerRadius(0);
SideButton* export_sliced_file_btn = new SideButton(p, _L("Export plate sliced file"), "");
export_sliced_file_btn->SetCornerRadius(0);
SideButton* export_all_sliced_file_btn = new SideButton(p, _L("Export all sliced file"), "");
export_all_sliced_file_btn->SetCornerRadius(0);
print_plate_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
m_print_btn->SetLabel(_L("Print plate"));
m_print_select = ePrintPlate;
m_print_enable = get_enable_print_status();
m_print_btn->Enable(m_print_enable);
this->Layout();
fit_tab_labels(); // ORCA on label change
p->Dismiss();
});
SideButton* print_all_btn = new SideButton(p, _L("Print all"), "");
print_all_btn->SetCornerRadius(0);
print_all_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
m_print_btn->SetLabel(_L("Print all"));
m_print_select = ePrintAll;
m_print_enable = get_enable_print_status();
m_print_btn->Enable(m_print_enable);
this->Layout();
fit_tab_labels(); // ORCA on label change
p->Dismiss();
});
send_to_printer_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
m_print_btn->SetLabel(_L("Send"));
m_print_select = eSendToPrinter;
m_print_enable = get_enable_print_status();
m_print_btn->Enable(m_print_enable);
this->Layout();
fit_tab_labels(); // ORCA on label change
p->Dismiss();
});
SideButton* send_to_printer_all_btn = new SideButton(p, _L("Send all"), "");
send_to_printer_all_btn->SetCornerRadius(0);
send_to_printer_all_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
m_print_btn->SetLabel(_L("Send all"));
m_print_select = eSendToPrinterAll;
m_print_enable = get_enable_print_status();
m_print_btn->Enable(m_print_enable);
this->Layout();
fit_tab_labels(); // ORCA on label change
p->Dismiss();
});
export_sliced_file_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
m_print_btn->SetLabel(_L("Export plate sliced file"));
m_print_select = eExportSlicedFile;
m_print_enable = get_enable_print_status();
m_print_btn->Enable(m_print_enable);
this->Layout();
fit_tab_labels(); // ORCA on label change
p->Dismiss();
});
export_all_sliced_file_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
m_print_btn->SetLabel(_L("Export all sliced file"));
m_print_select = eExportAllSlicedFile;
m_print_enable = get_enable_print_status();
m_print_btn->Enable(m_print_enable);
this->Layout();
fit_tab_labels(); // ORCA on label change
p->Dismiss();
});
bool support_send = true;
bool support_print_all = true;
const auto preset_bundle = wxGetApp().preset_bundle;
if (preset_bundle) {
if (preset_bundle->use_bbl_network() || wxGetApp().app_config->get_bool("use_printer_agents")) {
// BBL network support everything
} else {
support_send = false; // All 3rd print hosts do not have the send options
auto cfg = preset_bundle->printers.get_edited_preset().config;
const auto host_type = cfg.option<ConfigOptionEnum<PrintHostType>>("host_type")->value;
// Only simply print support uploading all plates
support_print_all = host_type == PrintHostType::htSimplyPrint;
}
}
p->append_button(print_plate_btn);
if (support_print_all) {
p->append_button(print_all_btn);
}
if (support_send) {
p->append_button(send_to_printer_btn);
p->append_button(send_to_printer_all_btn);
}
if (enable_multi_machine) {
SideButton* print_multi_machine_btn = new SideButton(p, _L("Send to Multi-device"), "");
print_multi_machine_btn->SetCornerRadius(0);
print_multi_machine_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
m_print_btn->SetLabel(_L("Send to Multi-device"));
m_print_select = ePrintMultiMachine;
m_print_enable = get_enable_print_status();
m_print_btn->Enable(m_print_enable);
this->Layout();
fit_tab_labels(); // ORCA on label change
p->Dismiss();
});
p->append_button(print_multi_machine_btn);
}
p->append_button(export_sliced_file_btn);
p->append_button(export_all_sliced_file_btn);
SideButton* export_gcode_btn = new SideButton(p, _L("Export G-code file"), "");
export_gcode_btn->SetCornerRadius(0);
export_gcode_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
m_print_btn->SetLabel(_L("Export G-code file"));
m_print_select = eExportGcode;
m_print_enable = get_enable_print_status();
m_print_btn->Enable(m_print_enable);
this->Layout();
fit_tab_labels(); // ORCA on label change
p->Dismiss();
});
p->append_button(export_gcode_btn);
}
p->Popup(m_print_btn); p->Popup(m_print_btn);
} }
); );
@@ -4174,22 +4224,38 @@ void MainFrame::on_config_changed(DynamicPrintConfig* config) const
void MainFrame::set_print_button_to_default(PrintSelectType select_type) void MainFrame::set_print_button_to_default(PrintSelectType select_type)
{ {
// Orca: keep the user's remembered print/export action instead of resetting it to the computed if (select_type == PrintSelectType::ePrintPlate) {
// default. get_remembered_print_select() already rejects anything this printer does not offer. m_print_btn->SetLabel(_L("Print plate"));
PrintSelectType remembered; m_print_select = ePrintPlate;
if (get_remembered_print_select(remembered)) if (m_print_enable)
select_type = remembered; m_print_enable = get_enable_print_status();
m_print_btn->Enable(m_print_enable);
if (select_type == eUploadGcode) this->Layout();
return; // unsupported: no dropdown entry exists for this action } else if (select_type == PrintSelectType::eSendGcode) {
m_print_btn->SetLabel(_L_CONTEXT("Print", "Verb"));
m_print_btn->SetLabel(print_select_type_label(select_type)); m_print_select = eSendGcode;
m_print_select = select_type; if (m_print_enable)
// get_enable_print_status() already applies can_send_gcode() to the actions that need it m_print_enable = get_enable_print_status() && can_send_gcode();
if (m_print_enable) m_print_btn->Enable(m_print_enable);
m_print_enable = get_enable_print_status(); this->Layout();
m_print_btn->Enable(m_print_enable); } else if (select_type == PrintSelectType::eExportGcode) {
this->Layout(); m_print_btn->SetLabel(_L("Export G-code file"));
m_print_select = eExportGcode;
if (m_print_enable)
m_print_enable = get_enable_print_status() && can_send_gcode();
m_print_btn->Enable(m_print_enable);
this->Layout();
} else if (select_type == PrintSelectType::eExportSlicedFile) {
m_print_btn->SetLabel(_L("Export plate sliced file"));
m_print_select = eExportSlicedFile;
if (m_print_enable)
m_print_enable = get_enable_print_status();
m_print_btn->Enable(m_print_enable);
this->Layout();
} else {
// unsupport
return;
}
} }
void MainFrame::add_to_recent_projects(const wxString& filename) void MainFrame::add_to_recent_projects(const wxString& filename)
-9
View File
@@ -13,7 +13,6 @@
#include <string> #include <string>
#include <map> #include <map>
#include <vector>
#include "GUI_Utils.hpp" #include "GUI_Utils.hpp"
#include "Event.hpp" #include "Event.hpp"
@@ -431,14 +430,6 @@ public:
// Propagate changed configuration from the Tab to the Plater and save changes to the AppConfig // Propagate changed configuration from the Tab to the Plater and save changes to the AppConfig
void on_config_changed(DynamicPrintConfig* cfg) const ; void on_config_changed(DynamicPrintConfig* cfg) const ;
void set_print_button_to_default(PrintSelectType select_type); void set_print_button_to_default(PrintSelectType select_type);
// Orca: the print/export actions the current printer offers, in the order the dropdown lists them.
// The dropdown is built from this, and a remembered action is only restored if it appears here.
std::vector<PrintSelectType> available_print_actions() const;
// Orca: apply an action picked from the print dropdown to the print button
void select_print_action(PrintSelectType select_type);
// Orca: remember the user's preferred print/export action across sessions (see "remember_print_action" preference)
void remember_print_select(PrintSelectType select_type);
bool get_remembered_print_select(PrintSelectType& out) const;
bool can_save() const; bool can_save() const;
bool can_save_as() const; bool can_save_as() const;
+4 -1
View File
@@ -98,7 +98,10 @@ void MarkdownTip::LoadStyle()
ph /= "tooltip/styled.html"; ph /= "tooltip/styled.html";
_data_dir = false; _data_dir = false;
} }
_tipView->LoadURL(file_url_from_path(ph)); auto url = ph.string();
std::replace(url.begin(), url.end(), '\\', '/');
url = "file:///" + url;
_tipView->LoadURL(from_u8(url));
_lastTip.clear(); _lastTip.clear();
} }
+16 -28
View File
@@ -286,30 +286,6 @@ static void set_config_values(DynamicPrintConfig *config, const std::string &key
} }
} }
// Orca: a calibration print sets pressure advance explicitly to 0 on each extruder variant that
// has it disabled, so the value stored in the printer does not skew the result. Variants with
// pressure advance enabled keep their own value.
static void zero_pressure_advance_where_disabled(DynamicPrintConfig *filament_config)
{
auto enable_pa = filament_config->option<ConfigOptionBools>("enable_pressure_advance");
auto pa = filament_config->option<ConfigOptionFloats>("pressure_advance");
auto adaptive_pa = filament_config->option<ConfigOptionBools>("adaptive_pressure_advance");
if (!enable_pa || !pa || !adaptive_pa || pa->empty() || adaptive_pa->empty())
return;
// All variant keys share the variant count; widen a short one rather than index past it.
if (pa->size() < enable_pa->size())
pa->resize(enable_pa->size());
if (adaptive_pa->size() < enable_pa->size())
adaptive_pa->resize(enable_pa->size());
for (size_t variant = 0; variant < enable_pa->size(); ++variant) {
if (enable_pa->get_at(variant))
continue;
enable_pa->values[variant] = true;
pa->values[variant] = 0.0;
adaptive_pa->values[variant] = false;
}
}
bool Plater::has_illegal_filename_characters(const wxString& wxs_name) bool Plater::has_illegal_filename_characters(const wxString& wxs_name)
{ {
std::string name = into_u8(wxs_name); std::string name = into_u8(wxs_name);
@@ -3696,7 +3672,7 @@ void Sidebar::update_all_preset_comboboxes()
wxString url = from_u8(PrintHost::get_print_host_webui(&cfg)); wxString url = from_u8(PrintHost::get_print_host_webui(&cfg));
wxString apikey; wxString apikey;
if(url.empty()) if(url.empty())
url = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/orca/missing_connection.html"); url = wxString::Format("file://%s/web/orca/missing_connection.html", from_u8(resources_dir()));
else { else {
const auto host_type = cfg.option<ConfigOptionEnum<PrintHostType>>("host_type")->value; const auto host_type = cfg.option<ConfigOptionEnum<PrintHostType>>("host_type")->value;
if (cfg.has("printhost_apikey") && (host_type != htSimplyPrint)) if (cfg.has("printhost_apikey") && (host_type != htSimplyPrint))
@@ -17088,7 +17064,11 @@ void Plater::calib_input_shaping_freq(const Calib_Params& params)
set_config_values<double, ConfigOptionFloatsNullable>(print_config, "default_jerk", 0); set_config_values<double, ConfigOptionFloatsNullable>(print_config, "default_jerk", 0);
} }
zero_pressure_advance_where_disabled(filament_config); if (!filament_config->option<ConfigOptionBools>("enable_pressure_advance")->get_at(0)) {
set_config_values<bool, ConfigOptionBools>(filament_config, "enable_pressure_advance", true);
set_config_values<double, ConfigOptionFloatsNullable>(filament_config, "pressure_advance", 0.0);
set_config_values<bool, ConfigOptionBools>(filament_config, "adaptive_pressure_advance", false);
}
printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false}); printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false});
printer_config->set_key_value("input_shaping_emit", new ConfigOptionBool{false}); printer_config->set_key_value("input_shaping_emit", new ConfigOptionBool{false});
@@ -17150,7 +17130,11 @@ void Plater::calib_input_shaping_damp(const Calib_Params& params)
set_config_values<double, ConfigOptionFloatsNullable>(print_config, "default_jerk", 0); set_config_values<double, ConfigOptionFloatsNullable>(print_config, "default_jerk", 0);
} }
zero_pressure_advance_where_disabled(filament_config); if (!filament_config->option<ConfigOptionBools>("enable_pressure_advance")->get_at(0)) {
set_config_values<bool, ConfigOptionBools>(filament_config, "enable_pressure_advance", true);
set_config_values<double, ConfigOptionFloatsNullable>(filament_config, "pressure_advance", 0.0);
set_config_values<bool, ConfigOptionBools>(filament_config, "adaptive_pressure_advance", false);
}
printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false}); printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false});
printer_config->set_key_value("input_shaping_emit", new ConfigOptionBool{false}); printer_config->set_key_value("input_shaping_emit", new ConfigOptionBool{false});
@@ -17212,7 +17196,11 @@ void Plater::Calib_Cornering(const Calib_Params& params)
set_config_values<double, ConfigOptionFloatsNullable>(print_config, "default_jerk", 0); set_config_values<double, ConfigOptionFloatsNullable>(print_config, "default_jerk", 0);
} }
zero_pressure_advance_where_disabled(filament_config); if (!filament_config->option<ConfigOptionBools>("enable_pressure_advance")->get_at(0)) {
set_config_values<bool, ConfigOptionBools>(filament_config, "enable_pressure_advance", true);
set_config_values<double, ConfigOptionFloatsNullable>(filament_config, "pressure_advance", 0.0);
set_config_values<bool, ConfigOptionBools>(filament_config, "adaptive_pressure_advance", false);
}
printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false}); printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false});
printer_config->set_key_value("input_shaping_emit", new ConfigOptionBool{true}); printer_config->set_key_value("input_shaping_emit", new ConfigOptionBool{true});
+30 -6
View File
@@ -322,7 +322,7 @@ wxBoxSizer* PreferencesDialog::create_item_combobox(wxString title, wxString too
return sizer; return sizer;
} }
wxBoxSizer *PreferencesDialog::create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector<wxString> vlist, std::vector<std::string> config_name_index, const wxString wiki_url) wxBoxSizer *PreferencesDialog::create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector<wxString> vlist, std::vector<std::string> config_name_index, std::function<void(std::string)> onchange, const wxString wiki_url)
{ {
assert(vlist.size() == config_name_index.size()); assert(vlist.size() == config_name_index.size());
unsigned int current_index = 0; unsigned int current_index = 0;
@@ -338,8 +338,9 @@ wxBoxSizer *PreferencesDialog::create_item_combobox(wxString title, wxString too
auto [sizer, combobox] = create_item_combobox_base(title, tooltip, param, vlist, current_index); auto [sizer, combobox] = create_item_combobox_base(title, tooltip, param, vlist, current_index);
//// save config //// save config
combobox->GetDropDown().Bind(wxEVT_COMBOBOX, [this, param, config_name_index](wxCommandEvent& e) { combobox->GetDropDown().Bind(wxEVT_COMBOBOX, [this, param, config_name_index, onchange](wxCommandEvent& e) {
app_config->set(param, config_name_index[e.GetSelection()]); app_config->set(param, config_name_index[e.GetSelection()]);
if (onchange != nullptr) onchange(config_name_index[e.GetSelection()]);
e.Skip(); e.Skip();
}); });
@@ -1830,10 +1831,7 @@ void PreferencesDialog::create_items()
auto item_mix_print_high_low_temperature = create_item_checkbox(_L("Remove mixed temperature restriction"), _L("With this option enabled, you can print materials with a large temperature difference together."), "enable_high_low_temp_mixed_printing"); auto item_mix_print_high_low_temperature = create_item_checkbox(_L("Remove mixed temperature restriction"), _L("With this option enabled, you can print materials with a large temperature difference together."), "enable_high_low_temp_mixed_printing");
g_sizer->Add(item_mix_print_high_low_temperature); g_sizer->Add(item_mix_print_high_low_temperature);
auto item_remember_print_action = create_item_checkbox(_L("Remember last print action"), _L("If enabled, OrcaSlicer will remember the last selected option in the print button's dropdown (e.g. Print, Export plate sliced file, Export G-code file) and use it as the default on next startup."), "remember_print_action");
g_sizer->Add(item_remember_print_action);
//// CONTROL > Camera //// CONTROL > Camera
g_sizer->Add(create_item_title(_L("Camera")), 1, wxEXPAND); g_sizer->Add(create_item_title(_L("Camera")), 1, wxEXPAND);
@@ -2047,6 +2045,32 @@ void PreferencesDialog::create_items()
"preview_default_view_type", PreviewViewTypeLabels, PreviewViewTypeValues); "preview_default_view_type", PreviewViewTypeLabels, PreviewViewTypeValues);
g_sizer->Add(item_preview_view_type); g_sizer->Add(item_preview_view_type);
auto item_reduced_detail_mode = create_item_combobox(
_L("Simplify preview while dragging"),
_L("What the sliced preview draws while you drag the camera or a preview slider, or zoom with the mouse wheel, so that large prints stay responsive. "
"The full toolpaths are restored as soon as you let go.\n"
"Off: the full toolpaths.\n"
"Solid model: the sliced objects and the prime tower as solid shapes in their filament colors, cut to the visible layer range, "
"with its bottom and top layers drawn as toolpaths. Supports are not shown, and negative volumes are not cut out.\n"
"Shell only: every layer without its sparse infill, internal solid infill and gap fill, which lie under the walls and skins. "
"Walls, top and bottom surfaces, bridges, supports and the prime tower are drawn whole, so the print looks the same from outside.\n"
"The bottom and top of the visible layer range are always drawn whole."),
"preview_reduced_detail_mode",
{_L("Off"), _L("Solid model"), _L("Shell only")},
{"off", "solid", "shell"},
// apply the new mode immediately to the currently loaded preview
[](std::string value) {
if (Plater* plater = wxGetApp().plater()) {
if (GLCanvas3D* canvas = plater->get_preview_canvas3D()) {
canvas->get_gcode_viewer().set_reduced_detail_mode(value);
canvas->set_as_dirty();
canvas->request_extra_frame();
}
}
}
);
g_sizer->Add(item_reduced_detail_mode);
auto item_dim_previous_layers = create_item_checkbox( auto item_dim_previous_layers = create_item_checkbox(
_L("Dim lower layers"), _L("Dim lower layers"),
_L("When scrubbing the layer slider in the sliced preview, render the layers below the current one darkened so that only the layer being viewed is shown at full brightness."), _L("When scrubbing the layer slider in the sliced preview, render the layers below the current one darkened so that only the layer being viewed is shown at full brightness."),
+1 -1
View File
@@ -93,7 +93,7 @@ public:
wxBoxSizer *create_item_title(wxString title); wxBoxSizer *create_item_title(wxString title);
wxBoxSizer *create_item_label(wxString label, const wxString tooltip = "", const wxString wiki_url = ""); wxBoxSizer *create_item_label(wxString label, const wxString tooltip = "", const wxString wiki_url = "");
wxBoxSizer *create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector<wxString> vlist, std::function<void(wxString)> onchange = {}, const wxString wiki_url = ""); wxBoxSizer *create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector<wxString> vlist, std::function<void(wxString)> onchange = {}, const wxString wiki_url = "");
wxBoxSizer *create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector<wxString> vlist, std::vector<std::string> config_name_index, const wxString wiki_url = ""); wxBoxSizer *create_item_combobox(wxString title, wxString tooltip, std::string param, std::vector<wxString> vlist, std::vector<std::string> config_name_index, std::function<void(std::string)> onchange = {}, const wxString wiki_url = "");
wxBoxSizer *create_item_region_combobox(wxString title, wxString tooltip); wxBoxSizer *create_item_region_combobox(wxString title, wxString tooltip);
wxBoxSizer *create_item_language_combobox(wxString title, wxString tooltip); wxBoxSizer *create_item_language_combobox(wxString title, wxString tooltip);
wxBoxSizer *create_item_loglevel_combobox(wxString title, wxString tooltip, std::vector<wxString> vlist); wxBoxSizer *create_item_loglevel_combobox(wxString title, wxString tooltip, std::vector<wxString> vlist);
+3 -1
View File
@@ -57,7 +57,9 @@ PrivacyUpdateDialog::PrivacyUpdateDialog(wxWindow* parent, wxWindowID id, const
fs::path ph(resources_dir()); fs::path ph(resources_dir());
ph /= "tooltip/privacyupdate.html"; ph /= "tooltip/privacyupdate.html";
m_host_url = into_u8(file_url_from_path(ph)); m_host_url = ph.string();
std::replace(m_host_url.begin(), m_host_url.end(), '\\', '/');
m_host_url = "file:///" + m_host_url;
m_vebview_release_note->LoadURL(from_u8(m_host_url)); m_vebview_release_note->LoadURL(from_u8(m_host_url));
m_sizer_right->Add(m_vebview_release_note, 0, wxEXPAND | wxRIGHT | wxLEFT, FromDIP(15)); m_sizer_right->Add(m_vebview_release_note, 0, wxEXPAND | wxRIGHT | wxLEFT, FromDIP(15));
+6 -4
View File
@@ -45,10 +45,10 @@ 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) ProjectPanel::ProjectPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style) : wxPanel(parent, id, pos, size, style)
{ {
SetBackgroundColour(*wxWHITE); SetBackgroundColour(*wxWHITE);
m_project_home_url = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/model/index.html"); m_project_home_url = wxString::Format("file://%s/web/model/index.html", from_u8(resources_dir()));
wxString strlang = wxGetApp().current_language_code_safe(); wxString strlang = wxGetApp().current_language_code_safe();
if (strlang != "") if (strlang != "")
m_project_home_url += "?lang=" + strlang; m_project_home_url = wxString::Format("file://%s/web/model/index.html?lang=%s", from_u8(resources_dir()), strlang);
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
@@ -293,8 +293,10 @@ void ProjectPanel::OnScriptMessage(wxWebViewEvent& evt)
if (!accessory_path.empty()) { if (!accessory_path.empty()) {
std::string decode_path = wxGetApp().url_decode(accessory_path.ToStdString()); std::string decode_path = wxGetApp().url_decode(accessory_path.ToStdString());
fs::path path(decode_path); fs::path path(decode_path);
if (!desktop_open_project_attachment(this, path))
BOOST_LOG_TRIVIAL(warning) << "open_3mf_accessory: not opening " << decode_path; if (fs::exists(path)) {
wxLaunchDefaultApplication(path.wstring(), 0);
}
} }
} }
else if (strCmd == "request_3mf_info") { else if (strCmd == "request_3mf_info") {
+1 -1
View File
@@ -510,7 +510,7 @@ void UpdateVersionDialog::update_version_info(wxString release_note, wxString ve
out_buf->append(text, size); out_buf->append(text, size);
}, (void*) &html_source, MD_DIALECT_GITHUB | MD_FLAG_STRIKETHROUGH | MD_FLAG_WIKILINKS, 0); }, (void*) &html_source, MD_DIALECT_GITHUB | MD_FLAG_STRIKETHROUGH | MD_FLAG_WIKILINKS, 0);
html_source.append("</body></html>"); html_source.append("</body></html>");
m_vebview_release_note->LoadURL(file_url_from_path(boost::filesystem::path(resources_dir()) / "web/guide/0/index.html")); m_vebview_release_note->LoadURL("file://" + (boost::filesystem::path (resources_dir()) / "web/guide/0/index.html").string());
SetMinSize(GetSize()); SetMinSize(GetSize());
SetMaxSize(GetSize()); SetMaxSize(GetSize());
+19 -20
View File
@@ -4415,15 +4415,15 @@ void TabFilament::build()
optgroup->append_single_option_line("pellet_flow_coefficient", "printer_basic_information_advanced#pellet-modded-printer"); optgroup->append_single_option_line("pellet_flow_coefficient", "printer_basic_information_advanced#pellet-modded-printer");
optgroup->append_single_option_line("filament_flow_ratio", "material_flow_ratio_and_pressure_advance#flow-ratio", 0); optgroup->append_single_option_line("filament_flow_ratio", "material_flow_ratio_and_pressure_advance#flow-ratio", 0);
optgroup->append_single_option_line("enable_pressure_advance", "material_flow_ratio_and_pressure_advance#pressure-advance", 0); optgroup->append_single_option_line("enable_pressure_advance", "material_flow_ratio_and_pressure_advance#pressure-advance");
optgroup->append_single_option_line("pressure_advance", "material_flow_ratio_and_pressure_advance#pressure-advance", 0); optgroup->append_single_option_line("pressure_advance", "material_flow_ratio_and_pressure_advance#pressure-advance");
// Orca: adaptive pressure advance and calibration model // Orca: adaptive pressure advance and calibration model
optgroup->append_single_option_line("adaptive_pressure_advance", "material_flow_ratio_and_pressure_advance#enable-adaptive-pressure-advance-beta", 0); optgroup->append_single_option_line("adaptive_pressure_advance", "material_flow_ratio_and_pressure_advance#enable-adaptive-pressure-advance-beta");
optgroup->append_single_option_line("adaptive_pressure_advance_overhangs", "material_flow_ratio_and_pressure_advance#enable-adaptive-pressure-advance-for-overhangs-beta", 0); optgroup->append_single_option_line("adaptive_pressure_advance_overhangs", "material_flow_ratio_and_pressure_advance#enable-adaptive-pressure-advance-for-overhangs-beta");
optgroup->append_single_option_line("adaptive_pressure_advance_bridges", "material_flow_ratio_and_pressure_advance#pressure-advance-for-bridges", 0); optgroup->append_single_option_line("adaptive_pressure_advance_bridges", "material_flow_ratio_and_pressure_advance#pressure-advance-for-bridges");
Option option = optgroup->get_option("adaptive_pressure_advance_model", 0); Option option = optgroup->get_option("adaptive_pressure_advance_model");
option.opt.full_width = true; option.opt.full_width = true;
option.opt.is_code = true; option.opt.is_code = true;
option.opt.height = 15; option.opt.height = 15;
@@ -4432,7 +4432,7 @@ void TabFilament::build()
DynamicPrintConfig& filament_config = m_preset_bundle->filaments.get_edited_preset().config; DynamicPrintConfig& filament_config = m_preset_bundle->filaments.get_edited_preset().config;
update_dirty(); update_dirty();
if (opt_key.substr(0, opt_key.find('#')) == "adaptive_pressure_advance_model") if (opt_key == "adaptive_pressure_advance_model")
m_config_manipulation.check_adaptive_pressure_advance_model(&filament_config); m_config_manipulation.check_adaptive_pressure_advance_model(&filament_config);
on_value_change(opt_key, value); on_value_change(opt_key, value);
@@ -4829,11 +4829,8 @@ void TabFilament::toggle_options()
} }
if (m_active_page->title() == L("Filament")) if (m_active_page->title() == L("Filament"))
{ {
const int selection = m_variant_combo ? m_variant_combo->GetSelection() : 0; bool pa = m_config->opt_bool("enable_pressure_advance", 0);
const unsigned int variant_idx = (unsigned int) std::max(selection, 0); toggle_option("pressure_advance", pa);
bool pa = m_config->opt_bool("enable_pressure_advance", variant_idx);
toggle_option("pressure_advance", pa, 0);
//Orca: Enable the plates that should be visible when multi bed support is enabled or a BBL printer is selected; otherwise, enable only the plate visible for the selected bed type. //Orca: Enable the plates that should be visible when multi bed support is enabled or a BBL printer is selected; otherwise, enable only the plate visible for the selected bed type.
DynamicConfig& proj_cfg = m_preset_bundle->project_config; DynamicConfig& proj_cfg = m_preset_bundle->project_config;
@@ -4861,12 +4858,12 @@ void TabFilament::toggle_options()
// Orca: adaptive pressure advance and calibration model // Orca: adaptive pressure advance and calibration model
// If PA is not enabled, disable adaptive pressure advance and hide the model section // If PA is not enabled, disable adaptive pressure advance and hide the model section
// If adaptive PA is not enabled, hide the adaptive PA model section // If adaptive PA is not enabled, hide the adaptive PA model section
toggle_option("adaptive_pressure_advance", pa, 0); toggle_option("adaptive_pressure_advance", pa);
toggle_option("adaptive_pressure_advance_overhangs", pa, 0); toggle_option("adaptive_pressure_advance_overhangs", pa);
bool has_adaptive_pa = m_config->opt_bool("adaptive_pressure_advance", variant_idx); bool has_adaptive_pa = m_config->opt_bool("adaptive_pressure_advance", 0);
toggle_line("adaptive_pressure_advance_overhangs", has_adaptive_pa && pa, 0); toggle_line("adaptive_pressure_advance_overhangs", has_adaptive_pa && pa);
toggle_line("adaptive_pressure_advance_model", has_adaptive_pa && pa, 0); toggle_line("adaptive_pressure_advance_model", has_adaptive_pa && pa);
toggle_line("adaptive_pressure_advance_bridges", has_adaptive_pa && pa, 0); toggle_line("adaptive_pressure_advance_bridges", has_adaptive_pa && pa);
bool is_pellet_printer = printer_cfg.opt_bool("pellet_modded_printer"); bool is_pellet_printer = printer_cfg.opt_bool("pellet_modded_printer");
toggle_line("pellet_flow_coefficient", is_pellet_printer); toggle_line("pellet_flow_coefficient", is_pellet_printer);
@@ -4874,6 +4871,8 @@ void TabFilament::toggle_options()
toggle_line("activate_chamber_temp_control", printer_cfg.opt_bool("support_chamber_temp_control")); toggle_line("activate_chamber_temp_control", printer_cfg.opt_bool("support_chamber_temp_control"));
const int selection = m_variant_combo ? m_variant_combo->GetSelection() : 0;
const unsigned int variant_idx = (unsigned int) std::max(selection, 0);
std::string volumetric_speed_cos = m_config->opt_string("volumetric_speed_coefficients", variant_idx); std::string volumetric_speed_cos = m_config->opt_string("volumetric_speed_coefficients", variant_idx);
bool enable_fit = volumetric_speed_cos != "0 0 0 0 0 0"; bool enable_fit = volumetric_speed_cos != "0 0 0 0 0 0";
toggle_option("filament_adaptive_volumetric_speed", enable_fit, 256 + variant_idx); toggle_option("filament_adaptive_volumetric_speed", enable_fit, 256 + variant_idx);
@@ -5206,6 +5205,7 @@ void TabPrinter::build_fff()
optgroup->append_single_option_line("adaptive_bed_mesh_margin", "printer_basic_information_adaptive_bed_mesh#mesh-margin"); optgroup->append_single_option_line("adaptive_bed_mesh_margin", "printer_basic_information_adaptive_bed_mesh#mesh-margin");
optgroup = page->new_optgroup(L("Accessory"), "param_accessory"); optgroup = page->new_optgroup(L("Accessory"), "param_accessory");
optgroup->append_single_option_line("nozzle_type", "printer_basic_information_accessory#nozzle-type", 0);
optgroup->append_single_option_line("nozzle_hrc", "printer_basic_information_accessory#nozzle-hrc"); optgroup->append_single_option_line("nozzle_hrc", "printer_basic_information_accessory#nozzle-hrc");
optgroup->append_single_option_line("auxiliary_fan", "printer_basic_information_accessory#auxiliary-part-cooling-fan"); optgroup->append_single_option_line("auxiliary_fan", "printer_basic_information_accessory#auxiliary-part-cooling-fan");
optgroup->append_single_option_line("fan_direction"); optgroup->append_single_option_line("fan_direction");
@@ -5720,7 +5720,6 @@ if (is_marlin_flavor)
auto optgroup = page->new_optgroup(L("Basic information"), L"param_information", -1, true); auto optgroup = page->new_optgroup(L("Basic information"), L"param_information", -1, true);
optgroup->append_single_option_line("nozzle_diameter", "printer_extruder_basic_information#nozzle-diameter", extruder_idx); optgroup->append_single_option_line("nozzle_diameter", "printer_extruder_basic_information#nozzle-diameter", extruder_idx);
optgroup->append_single_option_line("nozzle_type", "printer_basic_information_accessory#nozzle-type", extruder_idx);
//optgroup->append_single_option_line("nozzle_volume_type", "", extruder_idx); //optgroup->append_single_option_line("nozzle_volume_type", "", extruder_idx);
optgroup->append_single_option_line("nozzle_volume", "printer_extruder_basic_information#nozzle-volume", extruder_idx); optgroup->append_single_option_line("nozzle_volume", "printer_extruder_basic_information#nozzle-volume", extruder_idx);
@@ -5732,7 +5731,7 @@ if (is_marlin_flavor)
optgroup->m_on_change = [this, extruder_idx](const t_config_option_key& opt_key, boost::any value) optgroup->m_on_change = [this, extruder_idx](const t_config_option_key& opt_key, boost::any value)
{ {
bool is_SEMM = m_config->opt_bool("single_extruder_multi_material"); bool is_SEMM = m_config->opt_bool("single_extruder_multi_material");
if (is_SEMM && m_extruders_count > 1 && boost::starts_with(opt_key, "nozzle_diameter")) if (is_SEMM && m_extruders_count > 1 && opt_key.find_first_of("nozzle_diameter") != std::string::npos)
{ {
SuppressBackgroundProcessingUpdate sbpu; SuppressBackgroundProcessingUpdate sbpu;
const double new_nd = boost::any_cast<double>(value); const double new_nd = boost::any_cast<double>(value);
+4 -5
View File
@@ -31,15 +31,14 @@ int UserManager::parse_json(std::string payload)
{ {
bool restored_json = false; bool restored_json = false;
json j; json j;
json j_pre = json::parse(payload);
if (j_pre.empty()) {
return -1;
}
//bind/unbind //bind/unbind
try { try {
json j_pre = json::parse(payload);
if (j_pre.empty()) {
return -1;
}
if (j_pre.contains("bind")) { if (j_pre.contains("bind")) {
if (j_pre["bind"].contains("command")) { if (j_pre["bind"].contains("command")) {
+3 -1
View File
@@ -38,7 +38,9 @@ DownPluginFrame::DownPluginFrame(GUI_App *pGUI) : wxDialog((wxWindow *) (pGUI->m
// set the frame icon // set the frame icon
wxBoxSizer *topsizer = new wxBoxSizer(wxVERTICAL); wxBoxSizer *topsizer = new wxBoxSizer(wxVERTICAL);
wxString TargetUrl = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/guide/6/index.html"); wxString TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/6/index.html").make_preferred().string());
TargetUrl = "file://" + TargetUrl;
// Create the webview // Create the webview
m_browser = WebView::CreateWebView(this, TargetUrl); m_browser = WebView::CreateWebView(this, TargetUrl);
+42 -34
View File
@@ -218,38 +218,37 @@ wxString GuideFrame::SetStartPage(GuidePage startpage, bool load)
m_page = startpage; m_page = startpage;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(" enter, load=%1%, start_page=%2%")%load%int(startpage); BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(" enter, load=%1%, start_page=%2%")%load%int(startpage);
//wxLogMessage("GUIDE: webpage_1 %s", (boost::filesystem::path(resources_dir()) / "web\\guide\\1\\index.html").make_preferred().string().c_str() ); //wxLogMessage("GUIDE: webpage_1 %s", (boost::filesystem::path(resources_dir()) / "web\\guide\\1\\index.html").make_preferred().string().c_str() );
const wxString guide_url = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/guide/0/index.html"); wxString TargetUrl = from_u8( (boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=1").make_preferred().string() );
wxString TargetUrl = guide_url + "?target=1";
//wxLogMessage("GUIDE: webpage_2 %s", TargetUrl.mb_str()); //wxLogMessage("GUIDE: webpage_2 %s", TargetUrl.mb_str());
if (startpage == BBL_WELCOME){ if (startpage == BBL_WELCOME){
SetTitle(_L("Setup Wizard")); SetTitle(_L("Setup Wizard"));
TargetUrl = guide_url + "?target=1"; TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=1").make_preferred().string());
} else if (startpage == BBL_REGION) { } else if (startpage == BBL_REGION) {
SetTitle(_L("Setup Wizard")); SetTitle(_L("Setup Wizard"));
TargetUrl = guide_url + "?target=11"; TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=11").make_preferred().string());
} else if (startpage == BBL_MODELS) { } else if (startpage == BBL_MODELS) {
SetTitle(_L("Setup Wizard")); SetTitle(_L("Setup Wizard"));
TargetUrl = guide_url + "?target=21"; TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=21").make_preferred().string());
} else if (startpage == BBL_FILAMENTS) { } else if (startpage == BBL_FILAMENTS) {
SetTitle(_L("Setup Wizard")); SetTitle(_L("Setup Wizard"));
int nSize = m_ProfileJson["model"].size(); int nSize = m_ProfileJson["model"].size();
if (nSize>0) if (nSize>0)
TargetUrl = guide_url + "?target=22"; TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=22").make_preferred().string());
else else
TargetUrl = guide_url + "?target=21"; TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=21").make_preferred().string());
} else if (startpage == BBL_FILAMENT_ONLY) { } else if (startpage == BBL_FILAMENT_ONLY) {
SetTitle(""); SetTitle("");
TargetUrl = guide_url + "?target=23"; TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=23").make_preferred().string());
} else if (startpage == BBL_MODELS_ONLY) { } else if (startpage == BBL_MODELS_ONLY) {
SetTitle(""); SetTitle("");
TargetUrl = guide_url + "?target=24"; TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=24").make_preferred().string());
} }
else { else {
SetTitle(_L("Setup Wizard")); SetTitle(_L("Setup Wizard"));
TargetUrl = guide_url + "?target=21"; TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=21").make_preferred().string());
} }
wxString strlang = wxGetApp().current_language_code_safe(); wxString strlang = wxGetApp().current_language_code_safe();
@@ -257,6 +256,7 @@ wxString GuideFrame::SetStartPage(GuidePage startpage, bool load)
if (strlang != "") if (strlang != "")
TargetUrl = wxString::Format("%s&lang=%s", w2s(TargetUrl), strlang); TargetUrl = wxString::Format("%s&lang=%s", w2s(TargetUrl), strlang);
TargetUrl = "file://" + TargetUrl;
if (load) if (load)
load_url(TargetUrl); load_url(TargetUrl);
@@ -1284,7 +1284,7 @@ bool GuideFrame::BuildProfileJson(const PresetBundle& bundle, bool require_all_r
entry["vendor"] = vp.id; entry["vendor"] = vp.id;
entry["nozzle_diameter"] = nozzle_str; entry["nozzle_diameter"] = nozzle_str;
entry["materials"] = materials_str; entry["materials"] = materials_str;
entry["cover"] = into_u8(file_url_from_path(cover_path)); entry["cover"] = cover_path.string();
entry["nozzle_selected"] = ""; entry["nozzle_selected"] = "";
entry["sub_path"] = ""; entry["sub_path"] = "";
m_ProfileJson["model"].push_back(entry); m_ProfileJson["model"].push_back(entry);
@@ -1405,26 +1405,23 @@ bool GuideFrame::BuildProfileDataFromVendors()
// is served from the shipped profiles. Each is stamped by name and // is served from the shipped profiles. Each is stamped by name and
// version alone: a profile change requires a version bump, so those two // version alone: a profile change requires a version bump, so those two
// determine content wherever the vendor's copy sits. // determine content wherever the vendor's copy sits.
std::vector<PresetBundle::VendorSource> ordered; struct VendorSource { std::string name; boost::filesystem::path dir; std::string version; };
json stamps = json::array(); std::vector<VendorSource> ordered;
auto add_vendor = [&ordered, &stamps](const std::string& name, const boost::filesystem::path& dir) { auto add_vendor = [&ordered](const std::string& name, const boost::filesystem::path& dir) {
// The version a load from `dir` would serve: the profile's where one // The version a load from `dir` would serve: the profile's where one
// exists (a cache is only served while it covers the profile beside // exists (a cache is only served while it covers the profile beside
// it), the cache's own stamp where the cache is the whole vendor. // it), the cache's own stamp where the cache is the whole vendor.
// A profile without a version (blacklist.json) carries no presets // A profile without a version (blacklist.json) carries no presets
// and is passed over. // and is passed over.
const boost::filesystem::path profile = dir / (name + ".json"); const boost::filesystem::path profile = dir / (name + ".json");
std::string version;
if (boost::filesystem::exists(profile)) { if (boost::filesystem::exists(profile)) {
const Semver v = get_version_from_json(profile.string()); const Semver v = get_version_from_json(profile.string());
if (! v.valid()) if (v.valid())
return; ordered.push_back({name, dir, v.to_string()});
version = v.to_string();
} else { } else {
version = VendorCacheFile::peek_version((dir / (name + ".opc")).string(), name); ordered.push_back({name, dir,
VendorCacheFile::peek_version((dir / (name + ".opc")).string(), name)});
} }
ordered.push_back({name, dir});
stamps.push_back({name, version});
}; };
const std::string filament_library(PresetBundle::ORCA_FILAMENT_LIBRARY); const std::string filament_library(PresetBundle::ORCA_FILAMENT_LIBRARY);
if (auto it = vendor_sources.find(filament_library); it != vendor_sources.end()) if (auto it = vendor_sources.find(filament_library); it != vendor_sources.end())
@@ -1434,6 +1431,9 @@ bool GuideFrame::BuildProfileDataFromVendors()
add_vendor(name, dir); add_vendor(name, dir);
if (ordered.empty()) if (ordered.empty())
return false; return false;
json stamps = json::array();
for (const VendorSource& v : ordered)
stamps.push_back({v.name, v.version});
// What this function derives is a pure function of that stamped set, so // What this function derives is a pure function of that stamped set, so
// the derived JSON is cached whole: a fresh cache makes an open one // the derived JSON is cached whole: a fresh cache makes an open one
@@ -1461,18 +1461,26 @@ bool GuideFrame::BuildProfileDataFromVendors()
} }
// Each vendor comes from its preset cache where one covers it, which is // Each vendor comes from its preset cache where one covers it, which is
// what makes this worth doing instead of the scan below. // what makes this worth doing instead of the scan below; loading into a
PresetBundle bundle; // bundle per vendor keeps the install order the startup path has.
std::vector<std::string> failed; PresetBundle bundle;
const std::string errors = bundle.load_vendors(ordered, ForwardCompatibilitySubstitutionRule::EnableSilent, auto load_vendor = [](PresetBundle& into, const std::string& vendor,
/*allow_cache=*/true, m_cancel_token.get(), &failed).second; const boost::filesystem::path& dir, const PresetBundle* base) {
if (*m_cancel_token || bundle.vendors.empty()) into.load_vendor_configs_from_json(dir.string(), vendor, PresetBundle::LoadSystem,
return false; ForwardCompatibilitySubstitutionRule::EnableSilent, base);
if (! errors.empty()) };
BOOST_LOG_TRIVIAL(warning) << "GuideFrame: loading the vendors reported: " << errors; for (const VendorSource& v : ordered) {
// A vendor that failed to load sends this open to the scan below, which lists if (*m_cancel_token)
// what it can read of every vendor. return false; // as in the scan below: a vendor without a cache is parsed, and that takes time
if (! failed.empty()) if (v.name == filament_library) {
load_vendor(bundle, v.name, v.dir, nullptr);
} else {
PresetBundle tmp;
load_vendor(tmp, v.name, v.dir, &bundle);
bundle.merge_presets(std::move(tmp));
}
}
if (bundle.vendors.empty())
return false; return false;
if (! BuildProfileJson(bundle, /*require_all_resource_vendors=*/false)) if (! BuildProfileJson(bundle, /*require_all_resource_vendors=*/false))
return false; return false;
@@ -1724,7 +1732,7 @@ int GuideFrame::LoadProfileFamily(std::string strVendor, std::string strFilePath
cover_path = (boost::filesystem::absolute(boost::filesystem::path(resources_dir()) / "/web/image/printer/") / cover_file) cover_path = (boost::filesystem::absolute(boost::filesystem::path(resources_dir()) / "/web/image/printer/") / cover_file)
.make_preferred(); .make_preferred();
} }
OneModel["cover"] = into_u8(file_url_from_path(cover_path)); OneModel["cover"] = cover_path.string();
OneModel["nozzle_selected"] = ""; OneModel["nozzle_selected"] = "";
+1 -1
View File
@@ -599,7 +599,7 @@ void ZUserLogin::OnScriptResponseMessage(wxCommandEvent &WXUNUSED(evt))
bool ZUserLogin::ShowErrorPage() bool ZUserLogin::ShowErrorPage()
{ {
wxString ErrortUrl = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/login/error.html"); wxString ErrortUrl = from_u8((boost::filesystem::path(resources_dir()) / "web\\login\\error.html").make_preferred().string());
load_url(ErrortUrl); load_url(ErrortUrl);
return true; return true;
+2 -2
View File
@@ -36,10 +36,10 @@ namespace GUI {
WebViewPanel::WebViewPanel(wxWindow *parent) WebViewPanel::WebViewPanel(wxWindow *parent)
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize) : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
{ {
wxString url = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/homepage/index.html"); wxString url = wxString::Format("file://%s/web/homepage/index.html", from_u8(resources_dir()));
wxString strlang = wxGetApp().current_language_code_safe(); wxString strlang = wxGetApp().current_language_code_safe();
if (strlang != "") if (strlang != "")
url += "?lang=" + strlang; url = wxString::Format("file://%s/web/homepage/index.html?lang=%s", from_u8(resources_dir()), strlang);
wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL); wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL);
+4 -2
View File
@@ -1,6 +1,5 @@
#include "MultiNozzleSync.hpp" #include "MultiNozzleSync.hpp"
#include "../GUI.hpp"
#include "../GUI_App.hpp" #include "../GUI_App.hpp"
#include "../I18N.hpp" #include "../I18N.hpp"
#include "../Plater.hpp" #include "../Plater.hpp"
@@ -22,6 +21,8 @@
#include <set> #include <set>
#include <wx/choice.h> #include <wx/choice.h>
#include <wx/filename.h>
#include <wx/filesys.h>
#include <wx/sizer.h> #include <wx/sizer.h>
#include <wx/stattext.h> #include <wx/stattext.h>
@@ -640,7 +641,8 @@ NozzleListTable::NozzleListTable(wxWindow* parent) : wxPanel(parent,wxID_ANY,wxD
m_web_view->AddScriptMessageHandler("nozzleListTable"); m_web_view->AddScriptMessageHandler("nozzleListTable");
m_web_view->EnableContextMenu(false); m_web_view->EnableContextMenu(false);
fs::path filepath = fs::path(resources_dir()) / "web/flush/NozzleListTable.html"; fs::path filepath = fs::path(resources_dir()) / "web/flush/NozzleListTable.html";
wxString url = file_url_from_path(filepath); wxFileName fn(wxString::FromUTF8(filepath.string()));
wxString url = wxFileSystem::FileNameToURL(fn);
m_web_view->LoadURL(url); m_web_view->LoadURL(url);
auto sizer = new wxBoxSizer(wxVERTICAL); auto sizer = new wxBoxSizer(wxVERTICAL);
+6 -5
View File
@@ -48,19 +48,20 @@ constexpr char ORCA_BRIDGE_JS[] = R"JS(
wxString bootstrap_url() wxString bootstrap_url()
{ {
return file_url_from_path(boost::filesystem::path(resources_dir()) / BOOTSTRAP_PAGE); return wxString("file://") + from_u8((boost::filesystem::path(resources_dir()) / BOOTSTRAP_PAGE).make_preferred().string());
} }
wxString content_base_url() wxString content_base_url()
{ {
return file_url_from_path(boost::filesystem::path(resources_dir()) / "web") + "/"; const std::string dir = (boost::filesystem::path(resources_dir()) / "web").make_preferred().string();
return wxString("file://") + from_u8(dir) + "/";
} }
bool is_content_url(const wxString& url) bool is_content_url(const wxString& url)
{ {
// The web view reports the URL it parsed, which may escape the resources path differently // The web view reports the URL it parsed, which escapes anything the resources path holds
// from content_base_url(). // (a space, a non-ASCII character), while content_base_url() is the raw path.
return wxURI::Unescape(url.BeforeFirst('#')) == wxURI::Unescape(content_base_url()); return wxURI::Unescape(url.BeforeFirst('#')) == content_base_url();
} }
const char* orca_bridge_script() { return ORCA_BRIDGE_JS; } const char* orca_bridge_script() { return ORCA_BRIDGE_JS; }
+2 -2
View File
@@ -192,7 +192,7 @@ bool WebViewHostDialog::create_webview(const std::string& resource_path,
wxString WebViewHostDialog::build_resource_url(const std::string& resource_path) const wxString WebViewHostDialog::build_resource_url(const std::string& resource_path) const
{ {
wxString target_url = file_url_from_path(boost::filesystem::path(resources_dir()) / resource_path); wxString target_url = from_u8((boost::filesystem::path(resources_dir()) / resource_path).make_preferred().string());
if (append_language_to_url()) { if (append_language_to_url()) {
const wxString lang = wxGetApp().current_language_code_safe(); const wxString lang = wxGetApp().current_language_code_safe();
@@ -202,7 +202,7 @@ wxString WebViewHostDialog::build_resource_url(const std::string& resource_path)
} }
} }
return target_url; return wxString("file://") + target_url;
} }
void WebViewHostDialog::load_url(const wxString& url) void WebViewHostDialog::load_url(const wxString& url)
+1 -1
View File
@@ -464,7 +464,7 @@ WipingDialog::WipingDialog(wxWindow* parent, const int max_flush_volume) :
wxString filepath_str = from_path(filepath); wxString filepath_str = from_path(filepath);
wxFileName fn(filepath_str); wxFileName fn(filepath_str);
if(fn.FileExists()) { if(fn.FileExists()) {
wxString url = file_url_from_path(filepath); wxString url = wxFileSystem::FileNameToURL(fn);
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__<< "File exists and load url " << url.ToStdString(); BOOST_LOG_TRIVIAL(debug) << __FUNCTION__<< "File exists and load url " << url.ToStdString();
m_webview->LoadURL(url); m_webview->LoadURL(url);
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__<< "Successfully loaded url: " << url.ToStdString(); BOOST_LOG_TRIVIAL(debug) << __FUNCTION__<< "Successfully loaded url: " << url.ToStdString();
+9
View File
@@ -274,4 +274,13 @@ bool Duet::start_print(wxString &msg, const std::string &filename, ConnectionTyp
return res; return res;
} }
int Duet::get_err_code_from_body(const std::string &body) const
{
pt::ptree root;
std::istringstream iss (body); // wrap returned json to istringstream
pt::read_json(iss, root);
return root.get<int>("err", 0);
}
} }
+1
View File
@@ -40,6 +40,7 @@ private:
ConnectionType connect(wxString &msg) const; ConnectionType connect(wxString &msg) const;
void disconnect(ConnectionType connectionType) const; void disconnect(ConnectionType connectionType) const;
bool start_print(wxString &msg, const std::string &filename, ConnectionType connectionType, bool simulationMode) const; bool start_print(wxString &msg, const std::string &filename, ConnectionType connectionType, bool simulationMode) const;
int get_err_code_from_body(const std::string &body) const;
}; };
} }
+9
View File
@@ -146,6 +146,15 @@ bool ESP3D::start_print(wxString& msg, const std::string& filename) const
return ret; return ret;
} }
int ESP3D::get_err_code_from_body(const std::string& body) const
{
pt::ptree root;
std::istringstream iss(body); // wrap returned json to istringstream
pt::read_json(iss, root);
return root.get<int>("err", 0);
}
// ESP3D only accepts 8.3 filenames else it crashes marlin and other undefined behaviour // ESP3D only accepts 8.3 filenames else it crashes marlin and other undefined behaviour
std::string ESP3D::get_short_name(const std::string& filename) const std::string ESP3D::get_short_name(const std::string& filename) const
{ {
+1
View File
@@ -33,6 +33,7 @@ private:
std::string m_console_port; std::string m_console_port;
bool start_print(wxString& msg, const std::string& filename) const; bool start_print(wxString& msg, const std::string& filename) const;
int get_err_code_from_body(const std::string& body) const;
std::string get_short_name(const std::string& filename) const; std::string get_short_name(const std::string& filename) const;
std::string format_command(const std::string& path, const std::string& arg, const std::string& val) const; std::string format_command(const std::string& path, const std::string& arg, const std::string& val) const;
}; };
+3 -1
View File
@@ -340,7 +340,9 @@ namespace Slic3r {
if (classify_printer_model(config->opt_string("printer_model")) != ElegooPrinterType::CC2) if (classify_printer_model(config->opt_string("printer_model")) != ElegooPrinterType::CC2)
return fallback_webui; return fallback_webui;
std::string web_path = GUI::into_u8(GUI::file_url_from_path(boost::filesystem::path(resources_dir()) / "web/elegoolink/lan_service_web/index.html")); std::string web_path = resources_dir() + "/web/elegoolink/lan_service_web/index.html";
std::replace(web_path.begin(), web_path.end(), '\\', '/');
web_path = "file://" + web_path;
const std::string token = get_cc2_token(config->opt_string("printhost_apikey")); const std::string token = get_cc2_token(config->opt_string("printhost_apikey"));
const std::string host_ip = Http::get_host_header_value(host); const std::string host_ip = Http::get_host_header_value(host);

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