mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-29 20:01:26 +00:00
Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ecfcc96d76 | ||
|
|
ff6f935be5 | ||
|
|
674e036864 | ||
|
|
ecf5f59988 | ||
|
|
8b091f709b | ||
|
|
7f30eb8a49 | ||
|
|
b5d1866445 | ||
|
|
3b1a5c0630 | ||
|
|
18867094fa | ||
|
|
5ae88ae5c7 | ||
|
|
ea3a40d073 | ||
|
|
806568287c | ||
|
|
0342ddbe26 | ||
|
|
33af0651b7 | ||
|
|
781a5d8516 | ||
|
|
38446ab77a | ||
|
|
9080269f6f |
@@ -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
|
||||
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
|
||||
(`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.
|
||||
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
|
||||
@@ -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.
|
||||
|
||||
`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):
|
||||
|
||||
```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`
|
||||
|
||||
**`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):
|
||||
`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.
|
||||
|
||||
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
|
||||
retraction overrides carry one value per variant of `filament_extruder_variant` (Standard, High Flow, …). The
|
||||
`filament_max_volumetric_speed`, `filament_flow_ratio`, `nozzle_temperature` and the retraction
|
||||
overrides carry one value per variant of `filament_extruder_variant` (Standard, High Flow, …). The
|
||||
exact key set is [`filament_options_with_variant`](extruder-variants.md#the-four-key-sets);
|
||||
`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
|
||||
([extruder-variants.md](extruder-variants.md#filament)).
|
||||
|
||||
|
||||
+45
-19
@@ -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
|
||||
vendors keep theirs — even when the bumped vendor is the shared Orca filament
|
||||
library everyone else inherits from.
|
||||
- The setup wizard, which loads vendors one at a time, gets the same speedup as
|
||||
startup without a second code path.
|
||||
- The setup wizard loads its vendors through the same routine as startup, so it
|
||||
gets the same speedup without a second code path.
|
||||
- A vendor with no cache, or a broken one, costs only that vendor a parse.
|
||||
|
||||
A cache holds *system* presets only. User presets, project settings and modified
|
||||
@@ -162,15 +162,20 @@ cache nothing can invalidate is worse than no cache.
|
||||
|
||||
Vendors load in a fixed order, because filament inheritance crosses exactly one
|
||||
boundary: any vendor's filament may inherit from the shared Orca filament library,
|
||||
and nothing else reaches across vendors — an `include` is always vendor-local. The
|
||||
library therefore goes first, alone; every other vendor follows in parallel, resolving
|
||||
against it; and the results are merged in a stable order:
|
||||
and nothing else reaches across vendors — an `include` is always vendor-local. Only
|
||||
installing a vendor's presets crosses it; reading the vendor, from its cache or its
|
||||
JSONs, needs nothing from the library. So every other vendor is read while the library
|
||||
loads, each is installed against it as soon as both are done, and the results are
|
||||
merged in a stable order:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
lib["1 · OrcaFilamentLibrary<br/>loaded first, synchronously"] --> par["2 · every other vendor in parallel,<br/>each into its own bundle, filaments<br/>resolving against the loaded library"] --> merge["3 · bundles merged into one,<br/>sequentially, in stable vendor order"]
|
||||
lib["1 · OrcaFilamentLibrary loaded;<br/>meanwhile every other vendor read<br/>from its cache or its JSONs"] --> par["2 · every other vendor installed<br/>in parallel, each into its own bundle,<br/>filaments resolving against the library"] --> merge["3 · bundles merged into one,<br/>in one pass per collection,<br/>in stable vendor order"]
|
||||
```
|
||||
|
||||
`PresetBundle::load_vendors` runs these steps for startup and for the setup wizard,
|
||||
which hand it the vendors to load and the directory each is installed in.
|
||||
|
||||
Whether a vendor comes from its cache or from a parse changes nothing in that
|
||||
order — both produce the same bundle, so cached and parsed vendors mix freely in
|
||||
one startup.
|
||||
@@ -211,11 +216,12 @@ shipped cache answered first, so the profile in `<data_dir>/system/` was never p
|
||||
and its cache was never written back.
|
||||
|
||||
Serving from a cache is not a memory-image restore. The entries are deserialized and
|
||||
then installed one by one — inheritance resolved against the presets installed before
|
||||
them and the currently loaded filament library, includes layered in, configs flattened
|
||||
onto the collection defaults, validated and registered — by the same function the JSON
|
||||
path calls straight after parsing a sub-file. An `include` layers what the included
|
||||
base states, between the parent and the preset's own keys: the base's diff against the
|
||||
then installed by `install_vendor`, the routine the JSON path hands the vendor's entries
|
||||
to once it has parsed the sub-files: inheritance resolved against the presets installed
|
||||
before them and the currently loaded filament library, includes layered in, configs
|
||||
flattened onto the collection defaults, validated and registered. An `include` layers
|
||||
what the included base states, between the parent and the preset's own keys: the base's
|
||||
diff against the
|
||||
default, taken when the base itself was installed and before the per-variant padding
|
||||
`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
|
||||
@@ -223,6 +229,16 @@ indistinguishable from a JSON-loaded one by construction rather than by test cov
|
||||
Installation also rebuilds each preset's file path from the local data directory, so a
|
||||
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
|
||||
`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
|
||||
@@ -254,17 +270,20 @@ the wizard caches the *derived JSON*, not another form of the inputs:
|
||||
open, the wizard computes the current stamps (one version peek per vendor) and, when
|
||||
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
|
||||
per-vendor caches costs ~2 s of preset installation whatever feeds it, so only
|
||||
skipping the rebuild entirely wins.
|
||||
per-vendor caches costs over a second of preset installation whatever feeds it, so
|
||||
only skipping the rebuild entirely wins.
|
||||
|
||||
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;
|
||||
the wizard then rebuilds the bundle vendor by vendor (per-vendor caches serving where
|
||||
they cover) and writes the catalog back. Selections, region and per-open decorations
|
||||
are applied downstream of the cache either way, so a served catalog is
|
||||
indistinguishable from a rebuilt one. Nothing ships this file and the updater never
|
||||
touches it; it is a locally written artifact, re-derived whenever stale, written
|
||||
through a temp file and rename so half a cache is never readable.
|
||||
the wizard then rebuilds the bundle with `PresetBundle::load_vendors`, the load
|
||||
startup uses (per-vendor caches serving where they cover), and writes the catalog
|
||||
back. When a vendor fails to load, the filament library included, that open falls
|
||||
back to the wizard's own scan of the vendor JSONs, as when no bundle can be built,
|
||||
and writes nothing. Selections, region and per-open decorations are applied
|
||||
downstream of the cache either way, so a served catalog is indistinguishable from a
|
||||
rebuilt one. Nothing ships this file and the updater never touches it; it is a
|
||||
locally written artifact, re-derived whenever stale, written through a temp file and
|
||||
rename so half a cache is never readable.
|
||||
|
||||
The cache lives under `<data_dir>/cache/`, not beside the vendors: everything that
|
||||
scans `<data_dir>/system/` treats any `.opc` there as a vendor, so a non-vendor
|
||||
@@ -378,6 +397,13 @@ enumerates only `*.json` will find no vendors at all in a packaged build.
|
||||
the `CachedPreset` field list — written and read by `visit_entry` in
|
||||
`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.
|
||||
- **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
|
||||
65535 options and one cache at most 65535 distinct enum value names.
|
||||
`CacheDictionary::save` throws past that, which surfaces when CI generates the
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "OrcaFilamentLibrary",
|
||||
"version": "02.04.00.16",
|
||||
"version": "02.04.00.15",
|
||||
"force_update": "0",
|
||||
"description": "Orca Filament Library",
|
||||
"filament_list": [
|
||||
|
||||
@@ -111,7 +111,6 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -317,7 +316,6 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
-2
@@ -111,7 +111,6 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -317,7 +316,6 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
@@ -111,7 +111,6 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -317,7 +316,6 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
-2
@@ -111,7 +111,6 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -317,7 +316,6 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
-2
@@ -111,7 +111,6 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -317,7 +316,6 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
-2
@@ -111,7 +111,6 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -317,7 +316,6 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
@@ -111,7 +111,6 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -317,7 +316,6 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
@@ -111,7 +111,6 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -317,7 +316,6 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
-2
@@ -111,7 +111,6 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -317,7 +316,6 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
-2
@@ -111,7 +111,6 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -317,7 +316,6 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
-2
@@ -111,7 +111,6 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -317,7 +316,6 @@
|
||||
"1"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
-2
@@ -111,7 +111,6 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -317,7 +316,6 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
-2
@@ -111,7 +111,6 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -317,7 +316,6 @@
|
||||
"0"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
@@ -111,7 +111,6 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -317,7 +316,6 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
@@ -111,7 +111,6 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -317,7 +316,6 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
-2
@@ -111,7 +111,6 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -317,7 +316,6 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
-2
@@ -111,7 +111,6 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -317,7 +316,6 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
@@ -111,7 +111,6 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -317,7 +316,6 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
@@ -111,7 +111,6 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -317,7 +316,6 @@
|
||||
"0"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
-2
@@ -111,7 +111,6 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -317,7 +316,6 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
@@ -111,7 +111,6 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -317,7 +316,6 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
-2
@@ -111,7 +111,6 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -317,7 +316,6 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
-2
@@ -111,7 +111,6 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -317,7 +316,6 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
-2
@@ -111,7 +111,6 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -317,7 +316,6 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
-2
@@ -111,7 +111,6 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -317,7 +316,6 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
-2
@@ -111,7 +111,6 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -317,7 +316,6 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
+30
-129
@@ -214,9 +214,9 @@ function ShowModelInfo( pModel )
|
||||
|
||||
SendWXDebugInfo("Model Name: "+sModelName);
|
||||
|
||||
$('#ModelName').text(sModelName);
|
||||
$('#ModelName').html(sModelName);
|
||||
$('#ModelName').attr('title',sModelName);
|
||||
$('#ModelAuthorName').text(sModelAuthor);
|
||||
$('#ModelAuthorName').html(sModelAuthor);
|
||||
|
||||
switch(UploadType)
|
||||
{
|
||||
@@ -268,7 +268,7 @@ function ShowModelInfo( pModel )
|
||||
break;
|
||||
}
|
||||
|
||||
$('#Model_Desc').empty().append( SanitizeDescHtml( html_decode(sModelDesc) ) );
|
||||
$('#Model_Desc').html( html_decode(sModelDesc) );
|
||||
|
||||
let ModelPreviewList=pModel.preview_img;
|
||||
let TotalPreview=ModelPreviewList.length;
|
||||
@@ -281,15 +281,16 @@ function ShowModelInfo( pModel )
|
||||
|
||||
if(TotalPreview>0)
|
||||
{
|
||||
$('#ModelPreviewList').empty();
|
||||
let htmlPreview='';
|
||||
for(let pn=0;pn<TotalPreview;pn++)
|
||||
{
|
||||
//let FTmpPath=decodeURIComponent(ModelPreviewList[pn]);
|
||||
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({
|
||||
title: false,
|
||||
fullsreen: false,
|
||||
@@ -409,8 +410,7 @@ function ConstructFileHtml( ID, pItem )
|
||||
{
|
||||
let fTotal=pItem.length;
|
||||
|
||||
let pBoard=$('#'+ID+' .FileListBoard');
|
||||
pBoard.empty();
|
||||
let strHtml='';
|
||||
for( let f=0;f<fTotal;f++ )
|
||||
{
|
||||
let pOne=pItem[f];
|
||||
@@ -443,139 +443,39 @@ function ConstructFileHtml( ID, pItem )
|
||||
ImgPath='img/default.png';
|
||||
}
|
||||
|
||||
//Add html. File names come from the 3MF, so build the nodes rather than concatenating markup.
|
||||
let pIconImg=$('<img />').attr('src',ImgPath);
|
||||
let pMenu=$('<div class="FileMenu"><img src="img/s.svg" /></div>');
|
||||
//Add html
|
||||
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
|
||||
{
|
||||
ImgID++;
|
||||
let TmpImgID="AF"+ImgID;
|
||||
|
||||
pIconImg.attr('id',TmpImgID);
|
||||
pMenu.on('click', function(){ OnClickOpenImage(TmpImgID); });
|
||||
strHtml+='<div class="FileItem">'+
|
||||
' <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 )
|
||||
$('#'+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 )
|
||||
{
|
||||
//==========Profile Info==========
|
||||
@@ -583,10 +483,10 @@ function ShowProfilelInfo( pProfile )
|
||||
let sProfileAuthor=decodeURIComponent(pProfile.author);
|
||||
let sProfileDesc=decodeURIComponent(pProfile.description);
|
||||
|
||||
$('#ProfileName').text(sProfileName);
|
||||
$('#ProfileAuthor').text(sProfileAuthor);
|
||||
$('#ProfileName').html(sProfileName);
|
||||
$('#ProfileAuthor').html(sProfileAuthor);
|
||||
|
||||
$('#Profile_Desc').empty().append( SanitizeDescHtml( html_decode(sProfileDesc) ) );
|
||||
$('#Profile_Desc').html( html_decode(sProfileDesc) );
|
||||
|
||||
let ProfilePreviewList=pProfile.preview_img;
|
||||
let TotalPreview=ProfilePreviewList.length;
|
||||
@@ -599,14 +499,15 @@ function ShowProfilelInfo( pProfile )
|
||||
|
||||
if(TotalPreview>0)
|
||||
{
|
||||
$('#ProfilePreviewList').empty();
|
||||
let htmlPreview='';
|
||||
for(let pn=0;pn<TotalPreview;pn++)
|
||||
{
|
||||
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({
|
||||
title: false,
|
||||
fullsreen: false,
|
||||
|
||||
@@ -3476,14 +3476,6 @@ int CLI::run(int argc, char **argv)
|
||||
}
|
||||
new_variant_counts = 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++) {
|
||||
DynamicPrintConfig& config = load_filaments_config[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
|
||||
if (old_variant_count != new_variant_count)
|
||||
{
|
||||
@@ -4108,9 +4081,6 @@ int CLI::run(int argc, char **argv)
|
||||
if (printer_technology == ptFFF) {
|
||||
fff_print_config.apply(m_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 {
|
||||
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);
|
||||
|
||||
+46
-64
@@ -5,6 +5,7 @@
|
||||
//BBS
|
||||
#include "Preset.hpp"
|
||||
#include "Exception.hpp"
|
||||
#include "InstanceLock.hpp"
|
||||
#include "LocalesUtils.hpp"
|
||||
#include "Thread.hpp"
|
||||
#include "format.hpp"
|
||||
@@ -739,10 +740,13 @@ static bool verify_config_file_checksum(boost::nowide::ifstream &ifs)
|
||||
|
||||
|
||||
#ifdef USE_JSON_CONFIG
|
||||
std::string AppConfig::load()
|
||||
std::string AppConfig::load(bool read_only)
|
||||
{
|
||||
json j;
|
||||
|
||||
// Keep another instance from replacing or restoring the file mid-read.
|
||||
InstanceLock instance_lock(read_only ? std::string() : lock_path());
|
||||
|
||||
// 1) Read the complete config file into a boost::property_tree.
|
||||
namespace pt = boost::property_tree;
|
||||
pt::ptree tree;
|
||||
@@ -992,7 +996,6 @@ void AppConfig::save()
|
||||
// The config is first written to a file with a PID suffix and then moved
|
||||
// to avoid race conditions with multiple instances of Slic3r
|
||||
const auto path = config_path();
|
||||
std::string path_pid = (boost::format("%1%.%2%") % path % get_current_pid()).str();
|
||||
|
||||
json j;
|
||||
|
||||
@@ -1122,43 +1125,18 @@ void AppConfig::save()
|
||||
|
||||
j["local_machines"][local_machine.first] = m_json;
|
||||
}
|
||||
boost::nowide::ofstream c;
|
||||
c.open(path_pid, std::ios::out | std::ios::trunc);
|
||||
c << j.dump(1, '\t') << std::endl;
|
||||
|
||||
#ifdef WIN32
|
||||
// WIN32 specific: The final "rename_file()" call is not safe in case of an application crash, there is no atomic "rename file" API
|
||||
// provided by Windows (sic!). Therefore we save a MD5 checksum to be able to verify file corruption. In addition,
|
||||
// we save the config file into a backup first before moving it to the final destination.
|
||||
c << appconfig_md5_hash_line(j.dump(1, '\t'));
|
||||
#endif
|
||||
|
||||
c.close();
|
||||
if (c.fail()) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Failed to write new configuration to " << path_pid << "; aborting attempt to overwrite original configuration";
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
// Make a backup of the configuration file before copying it to the final destination.
|
||||
std::string error_message;
|
||||
std::string backup_path = (boost::format("%1%.bak") % path).str();
|
||||
// Copy configuration file with PID suffix into the configuration file with "bak" suffix.
|
||||
if (copy_file(path_pid, backup_path, error_message, false) != SUCCESS)
|
||||
BOOST_LOG_TRIVIAL(error) << "Copying from " << path_pid << " to " << backup_path << " failed. Failed to create a backup configuration.";
|
||||
#endif
|
||||
|
||||
// Rename the config atomically.
|
||||
// On Windows, the rename is likely NOT atomic, thus it may fail if PrusaSlicer crashes on another thread in the meanwhile.
|
||||
// To cope with that, we already made a backup of the config on Windows.
|
||||
rename_file(path_pid, path);
|
||||
const std::string config_str = j.dump(1, '\t');
|
||||
if (write_config_file(path, config_str + "\n", config_str))
|
||||
m_dirty = false;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
std::string AppConfig::load()
|
||||
std::string AppConfig::load(bool read_only)
|
||||
{
|
||||
// Keep another instance from replacing or restoring the file mid-read.
|
||||
InstanceLock instance_lock(read_only ? std::string() : lock_path());
|
||||
|
||||
// 1) Read the complete config file into a boost::property_tree.
|
||||
namespace pt = boost::property_tree;
|
||||
pt::ptree tree;
|
||||
@@ -1296,7 +1274,6 @@ void AppConfig::save()
|
||||
// The config is first written to a file with a PID suffix and then moved
|
||||
// to avoid race conditions with multiple instances of Slic3r
|
||||
const auto path = config_path();
|
||||
std::string path_pid = (boost::format("%1%.%2%") % path % get_current_pid()).str();
|
||||
|
||||
std::stringstream config_ss;
|
||||
if (m_mode == EAppMode::Editor)
|
||||
@@ -1332,39 +1309,39 @@ void AppConfig::save()
|
||||
// One empty line before the MD5 sum.
|
||||
config_ss << std::endl;
|
||||
|
||||
std::string config_str = config_ss.str();
|
||||
boost::nowide::ofstream c;
|
||||
c.open(path_pid, std::ios::out | std::ios::trunc);
|
||||
c << config_str;
|
||||
#ifdef WIN32
|
||||
// WIN32 specific: The final "rename_file()" call is not safe in case of an application crash, there is no atomic "rename file" API
|
||||
// provided by Windows (sic!). Therefore we save a MD5 checksum to be able to verify file corruption. In addition,
|
||||
// we save the config file into a backup first before moving it to the final destination.
|
||||
c << appconfig_md5_hash_line(config_str);
|
||||
#endif
|
||||
c.close();
|
||||
if (c.fail()) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Failed to write new configuration to " << path_pid << "; aborting attempt to overwrite original configuration";
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
// Make a backup of the configuration file before copying it to the final destination.
|
||||
std::string error_message;
|
||||
std::string backup_path = (boost::format("%1%.bak") % path).str();
|
||||
// Copy configuration file with PID suffix into the configuration file with "bak" suffix.
|
||||
if (copy_file(path_pid, backup_path, error_message, false) != SUCCESS)
|
||||
BOOST_LOG_TRIVIAL(error) << "Copying from " << path_pid << " to " << backup_path << " failed. Failed to create a backup configuration.";
|
||||
#endif
|
||||
|
||||
// Rename the config atomically.
|
||||
// On Windows, the rename is likely NOT atomic, thus it may fail if PrusaSlicer crashes on another thread in the meanwhile.
|
||||
// To cope with that, we already made a backup of the config on Windows.
|
||||
rename_file(path_pid, path);
|
||||
const std::string config_str = config_ss.str();
|
||||
if (write_config_file(path, config_str, config_str))
|
||||
m_dirty = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool AppConfig::write_config_file(const std::string &path, std::string body, const std::string &checksum_source)
|
||||
{
|
||||
// Everything before this is assembly; only the writes need the other instances kept out.
|
||||
InstanceLock instance_lock(lock_path());
|
||||
#ifdef WIN32
|
||||
// WIN32 specific: the final replace is not safe in case of an application crash, there is no atomic "rename file" API
|
||||
// provided by Windows (sic!). Therefore we save a MD5 checksum to be able to verify file corruption. In addition,
|
||||
// we save the config file into a backup first before moving it to the final destination.
|
||||
body += appconfig_md5_hash_line(checksum_source);
|
||||
#endif
|
||||
// Not flushed to the device: the idle handler saves on the GUI thread after
|
||||
// any change, and the rename already gives a complete old or new file.
|
||||
if (const std::error_code ec = write_file_atomically(path, body)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Failed to write the configuration " << path << ": " << ec.message() << "; trying again in 10 s";
|
||||
m_retry_save_at = std::chrono::steady_clock::now() + std::chrono::seconds(10);
|
||||
return false;
|
||||
}
|
||||
m_retry_save_at = {};
|
||||
#ifdef WIN32
|
||||
// Written after the config, so the backup never holds a state that was not confirmed written.
|
||||
const std::string backup_path = (boost::format("%1%.bak") % path).str();
|
||||
if (const std::error_code ec = write_file_atomically(backup_path, body))
|
||||
BOOST_LOG_TRIVIAL(error) << "Failed to write the backup configuration " << backup_path << ": " << ec.message();
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AppConfig::get_variant(const std::string &vendor, const std::string &model, const std::string &variant) const
|
||||
{
|
||||
const auto it_v = m_vendors.find(vendor);
|
||||
@@ -1853,6 +1830,11 @@ void AppConfig::reset_selections()
|
||||
}
|
||||
}
|
||||
|
||||
std::string AppConfig::lock_path()
|
||||
{
|
||||
return Slic3r::data_dir().empty() ? std::string() : config_path() + ".lock";
|
||||
}
|
||||
|
||||
std::string AppConfig::config_path()
|
||||
{
|
||||
#ifdef USE_JSON_CONFIG
|
||||
@@ -1889,7 +1871,7 @@ bool AppConfig::exists()
|
||||
|
||||
std::string AppConfig::load_if_exists()
|
||||
{
|
||||
return boost::filesystem::exists(loading_path()) ? load() : std::string();
|
||||
return boost::filesystem::exists(loading_path()) ? load(/*read_only=*/true) : std::string();
|
||||
}
|
||||
|
||||
}; // namespace Slic3r
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define slic3r_AppConfig_hpp_
|
||||
|
||||
#include <set>
|
||||
#include <chrono>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include "nlohmann/json.hpp"
|
||||
@@ -122,14 +123,18 @@ public:
|
||||
|
||||
// Load the slic3r.ini from a user profile directory (or a datadir, if configured).
|
||||
// Return an error string, or an empty string on success.
|
||||
std::string load();
|
||||
std::string load(bool read_only = false);
|
||||
// Treat a missing config as default state; otherwise load it normally.
|
||||
// The CLI's load: it never saves, so it takes no lock and creates no lock file.
|
||||
std::string load_if_exists();
|
||||
// Store the slic3r.ini into a user profile directory (or a datadir, if configured).
|
||||
void save();
|
||||
|
||||
// Does this config need to be saved?
|
||||
bool dirty() const { return m_dirty; }
|
||||
// False for ten seconds after a failed write, so the idle handler does not
|
||||
// repeat a hopeless attempt on every event; an explicit save() always tries.
|
||||
bool save_due() const { return std::chrono::steady_clock::now() >= m_retry_save_at; }
|
||||
|
||||
|
||||
void set_dirty() { m_dirty = true; }
|
||||
@@ -339,6 +344,8 @@ public:
|
||||
|
||||
// Get the default config path from Slic3r::data_dir().
|
||||
std::string config_path();
|
||||
// Lock file guarding config_path() against other running instances; empty without a data dir.
|
||||
std::string lock_path();
|
||||
|
||||
// Returns true if the user's data directory comes from before Slic3r 1.40.0 (no updating)
|
||||
bool legacy_datadir() const { return m_legacy_datadir; }
|
||||
@@ -449,8 +456,16 @@ private:
|
||||
|
||||
// Preset for each machine
|
||||
MachineSettingMap m_printer_settings;
|
||||
// Writes the assembled config text, and on Windows its checksum and a backup copy; false when the
|
||||
// config itself could not be written, in which case the caller stays dirty and retries. `checksum_source`
|
||||
// is the text load() will verify, which for the JSON config ends before the trailing newline.
|
||||
bool write_config_file(const std::string &path, std::string body, const std::string &checksum_source);
|
||||
|
||||
// Has any value been modified since the config.ini has been last saved or loaded?
|
||||
bool m_dirty;
|
||||
// After a failed write, save_due() is false for the next ten seconds, so the
|
||||
// idle handler does not repeat a hopeless write on every event.
|
||||
std::chrono::steady_clock::time_point m_retry_save_at{};
|
||||
// Original version found in the ini file before it was overwritten
|
||||
Semver m_orig_version;
|
||||
// Whether the existing version is before system profiles & configuration updating
|
||||
|
||||
@@ -306,6 +306,8 @@ set(lisbslic3r_sources
|
||||
Geometry/VoronoiUtils.cpp
|
||||
Geometry/VoronoiUtils.hpp
|
||||
Geometry/VoronoiVisualUtils.hpp
|
||||
InstanceLock.cpp
|
||||
InstanceLock.hpp
|
||||
Int128.hpp
|
||||
KDTreeIndirect.hpp
|
||||
Layer.cpp
|
||||
@@ -355,6 +357,7 @@ set(lisbslic3r_sources
|
||||
Optimize/Optimizer.hpp
|
||||
Orient.cpp
|
||||
Orient.hpp
|
||||
ParallelResolve.hpp
|
||||
ParameterUtils.cpp
|
||||
ParameterUtils.hpp
|
||||
pchheader.cpp
|
||||
|
||||
+29
-18
@@ -849,6 +849,19 @@ ConfigSubstitutions ConfigBase::load_from_json(const std::string &file, ForwardC
|
||||
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)
|
||||
{
|
||||
json j;
|
||||
@@ -916,44 +929,44 @@ int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContex
|
||||
}
|
||||
//parse the json elements
|
||||
for (auto it = j.begin(); it != j.end(); it++) {
|
||||
if (boost::iequals(it.key(),BBL_JSON_KEY_VERSION)) {
|
||||
if (ascii_iequals(it.key(), BBL_JSON_KEY_VERSION)) {
|
||||
key_values.emplace(BBL_JSON_KEY_VERSION, it.value());
|
||||
}
|
||||
else if (boost::iequals(it.key(), BBL_JSON_KEY_IS_CUSTOM)) {
|
||||
else if (ascii_iequals(it.key(), BBL_JSON_KEY_IS_CUSTOM)) {
|
||||
//skip it
|
||||
}
|
||||
else if (boost::iequals(it.key(), BBL_JSON_KEY_NAME)) {
|
||||
else if (ascii_iequals(it.key(), BBL_JSON_KEY_NAME)) {
|
||||
key_values.emplace(BBL_JSON_KEY_NAME, it.value());
|
||||
if (it.value() == "project_settings")
|
||||
is_project_settings = true;
|
||||
}
|
||||
else if (boost::iequals(it.key(), BBL_JSON_KEY_URL)) {
|
||||
else if (ascii_iequals(it.key(), BBL_JSON_KEY_URL)) {
|
||||
key_values.emplace(BBL_JSON_KEY_URL, it.value());
|
||||
}
|
||||
else if (boost::iequals(it.key(), BBL_JSON_KEY_TYPE)) {
|
||||
else if (ascii_iequals(it.key(), BBL_JSON_KEY_TYPE)) {
|
||||
key_values.emplace(BBL_JSON_KEY_TYPE, it.value());
|
||||
}
|
||||
else if (boost::iequals(it.key(), BBL_JSON_KEY_SETTING_ID)) {
|
||||
else if (ascii_iequals(it.key(), BBL_JSON_KEY_SETTING_ID)) {
|
||||
key_values.emplace(BBL_JSON_KEY_SETTING_ID, it.value());
|
||||
}
|
||||
else if (boost::iequals(it.key(), BBL_JSON_KEY_FILAMENT_ID)) {
|
||||
else if (ascii_iequals(it.key(), BBL_JSON_KEY_FILAMENT_ID)) {
|
||||
key_values.emplace(BBL_JSON_KEY_FILAMENT_ID, it.value());
|
||||
}
|
||||
else if (boost::iequals(it.key(), BBL_JSON_KEY_FROM)) {
|
||||
else if (ascii_iequals(it.key(), BBL_JSON_KEY_FROM)) {
|
||||
key_values.emplace(BBL_JSON_KEY_FROM, it.value());
|
||||
}
|
||||
else if (boost::iequals(it.key(), BBL_JSON_KEY_DESCRIPTION)) {
|
||||
else if (ascii_iequals(it.key(), BBL_JSON_KEY_DESCRIPTION)) {
|
||||
key_values.emplace(BBL_JSON_KEY_DESCRIPTION, it.value());
|
||||
}
|
||||
else if (boost::iequals(it.key(), BBL_JSON_KEY_INSTANTIATION)) {
|
||||
else if (ascii_iequals(it.key(), BBL_JSON_KEY_INSTANTIATION)) {
|
||||
key_values.emplace(BBL_JSON_KEY_INSTANTIATION, it.value());
|
||||
}
|
||||
else if (!load_inherits_to_config && boost::iequals(it.key(), BBL_JSON_KEY_INHERITS)) {
|
||||
else if (!load_inherits_to_config && ascii_iequals(it.key(), BBL_JSON_KEY_INHERITS)) {
|
||||
key_values.emplace(BBL_JSON_KEY_INHERITS, it.value());
|
||||
}
|
||||
else if (!load_inherits_to_config && boost::iequals(it.key(), BBL_JSON_KEY_INCLUDES)) {
|
||||
else if (!load_inherits_to_config && ascii_iequals(it.key(), BBL_JSON_KEY_INCLUDES)) {
|
||||
key_values.emplace(BBL_JSON_KEY_INCLUDES, it.value().dump());
|
||||
} else if (boost::iequals(it.key(), ORCA_JSON_KEY_RENAMED_FROM)) {
|
||||
} else if (ascii_iequals(it.key(), ORCA_JSON_KEY_RENAMED_FROM)) {
|
||||
key_values.emplace(ORCA_JSON_KEY_RENAMED_FROM, it.value());
|
||||
} else {
|
||||
t_config_option_key opt_key = it.key();
|
||||
@@ -1525,11 +1538,9 @@ void ConfigBase::save_to_json(const std::string &file, const std::string &name,
|
||||
// Serialize first: if that throws (invalid UTF-8), the existing file stays untouched.
|
||||
std::ostringstream ss;
|
||||
this->save_to_json(ss, name, from, version);
|
||||
boost::nowide::ofstream c;
|
||||
c.open(file, std::ios::out | std::ios::trunc);
|
||||
c << ss.str();
|
||||
c.close();
|
||||
|
||||
if (const std::error_code ec = write_file_atomically(file, ss.str()))
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": failed to save config to %1%: %2%") % file % ec.message();
|
||||
else
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", saved config to %1%\n")%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]);
|
||||
int face_index =its.indices.size() - 1;
|
||||
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()) {
|
||||
bool is_merge_ka_kd = true;
|
||||
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(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.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) {
|
||||
set_face_color(face_index, data.usemtls[0].name, corners);
|
||||
set_face_color(face_index, data.usemtls[0].name);
|
||||
} else {
|
||||
for (size_t k = 0; k < data.usemtls.size(); k++) {
|
||||
auto mtl = data.usemtls[k];
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
if (exist_mtl) {
|
||||
set_face_color_by_mtl(face_index, {0, 1, 2});
|
||||
set_face_color_by_mtl(face_index);
|
||||
}
|
||||
if (cnt == 4) {
|
||||
its.indices.emplace_back(indices[0], indices[2], indices[3]);
|
||||
int face_index = its.indices.size() - 1;
|
||||
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.");
|
||||
return false;
|
||||
}
|
||||
if (meshptr->volume() < 0) {
|
||||
if (meshptr->volume() < 0)
|
||||
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.
|
||||
if (out_mtl)
|
||||
*out_mtl = mtl_data;
|
||||
|
||||
+68
-48
@@ -1497,7 +1497,12 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
check_add_eol(toolchange_gcode_str);
|
||||
|
||||
// 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.
|
||||
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);
|
||||
|
||||
// SoftFever: set new PA for new filament
|
||||
if (new_extruder_id != -1)
|
||||
gcode += gcodegen.set_filament_pressure_advance(new_extruder_id);
|
||||
if (new_extruder_id != -1 && gcodegen.config().enable_pressure_advance.get_at(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.
|
||||
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);
|
||||
|
||||
// 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.
|
||||
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
|
||||
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.
|
||||
@@ -8376,16 +8390,12 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
|
||||
// Orca: Dynamic PA
|
||||
// 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 ||
|
||||
m_curr_print->calib_mode() == CalibMode::Calib_PA_Pattern ||
|
||||
m_curr_print->calib_mode() == CalibMode::Calib_PA_Tower;
|
||||
bool evaluate_adaptive_pa = false;
|
||||
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;
|
||||
// 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.
|
||||
@@ -8468,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
|
||||
// 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)
|
||||
// 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)
|
||||
// 4) the current acceleration (to pass to the model for evaluation)
|
||||
// 5) whether this is an external perimeter (for future use)
|
||||
@@ -8479,7 +8489,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
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",
|
||||
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,
|
||||
acceleration_i,
|
||||
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
|
||||
@@ -8492,7 +8502,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
// 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",
|
||||
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
|
||||
pa_config_index,
|
||||
m_writer.filament()->id(),
|
||||
_mm3_per_mm,
|
||||
acceleration_i,
|
||||
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
|
||||
@@ -8600,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
|
||||
// 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
|
||||
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(m_config.gcode_comments){
|
||||
sprintf(buf, "; Ramp down-non-variable\n");
|
||||
@@ -8608,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",
|
||||
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
|
||||
pa_config_index,
|
||||
m_writer.filament()->id(),
|
||||
_mm3_per_mm,
|
||||
acceleration_i,
|
||||
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
|
||||
@@ -8623,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",
|
||||
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
|
||||
pa_config_index,
|
||||
m_writer.filament()->id(),
|
||||
_mm3_per_mm,
|
||||
acceleration_i,
|
||||
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
|
||||
@@ -8829,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
|
||||
// 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.
|
||||
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(m_config.gcode_comments) {
|
||||
sprintf(buf, "; Ramp up-variable\n");
|
||||
@@ -8837,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",
|
||||
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
|
||||
pa_config_index,
|
||||
m_writer.filament()->id(),
|
||||
_mm3_per_mm,
|
||||
acceleration_i,
|
||||
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
|
||||
@@ -8852,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",
|
||||
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
|
||||
pa_config_index,
|
||||
m_writer.filament()->id(),
|
||||
_mm3_per_mm,
|
||||
acceleration_i,
|
||||
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
|
||||
@@ -9405,29 +9422,33 @@ void GCode::update_placeholder_parser_with_variant_params()
|
||||
if (num_filaments == 0)
|
||||
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.
|
||||
auto remap_by_filament = [&](const auto &src) {
|
||||
std::decay_t<decltype(src.values)> dst(num_filaments);
|
||||
auto remap_floats_by_filament = [&](const auto &src) {
|
||||
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)
|
||||
dst[i] = src.get_at(get_filament_config_index(i));
|
||||
return dst;
|
||||
};
|
||||
|
||||
// --- 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_pre_cooling_temperature", new ConfigOptionInts(remap_by_filament(m_config.filament_pre_cooling_temperature)));
|
||||
this->placeholder_parser().set("filament_pre_cooling_temperature_nc", new ConfigOptionInts(remap_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("nozzle_temperature_initial_layer", new ConfigOptionInts(remap_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("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_ints_by_filament(m_config.filament_pre_cooling_temperature)));
|
||||
this->placeholder_parser().set("filament_pre_cooling_temperature_nc", new ConfigOptionInts(remap_ints_by_filament(m_config.filament_pre_cooling_temperature_nc)));
|
||||
this->placeholder_parser().set("filament_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_ints_by_filament(m_config.nozzle_temperature_initial_layer)));
|
||||
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
|
||||
this->placeholder_parser().set("first_layer_temperature", new ConfigOptionInts(remap_by_filament(m_config.nozzle_temperature_initial_layer)));
|
||||
this->placeholder_parser().set("pressure_advance", new ConfigOptionFloats(remap_by_filament(m_config.pressure_advance)));
|
||||
this->placeholder_parser().set("enable_pressure_advance", new ConfigOptionBools(remap_by_filament(m_config.enable_pressure_advance)));
|
||||
this->placeholder_parser().set("first_layer_temperature", new ConfigOptionInts(remap_ints_by_filament(m_config.nozzle_temperature_initial_layer)));
|
||||
|
||||
// --- 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
|
||||
|
||||
// --- filament_map: per-layer dynamic, sync from m_config to placeholder_parser ---
|
||||
@@ -9437,9 +9458,9 @@ void GCode::update_placeholder_parser_with_variant_params()
|
||||
{
|
||||
// Fast purge mode uses filament_flush_temp_fast; Default is inert.
|
||||
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 filament_max_v = remap_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_v_speed = remap_floats_by_filament(m_config.filament_flush_volumetric_speed);
|
||||
auto filament_max_v = remap_floats_by_filament(m_config.filament_max_volumetric_speed);
|
||||
auto flush_temps = remap_ints_by_filament(use_fast_flush ? m_config.filament_flush_temp_fast
|
||||
: m_config.filament_flush_temp);
|
||||
for (size_t i = 0; i < num_filaments; ++i) {
|
||||
if (flush_v_speed[i] == 0)
|
||||
@@ -9492,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);
|
||||
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);
|
||||
if (Extruder *fil = m_writer.filament())
|
||||
@@ -9883,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)
|
||||
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
|
||||
m_last_pos_defined = false;
|
||||
|
||||
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) {
|
||||
std::ostringstream gcode;
|
||||
gcode << "[";
|
||||
|
||||
@@ -281,8 +281,6 @@ public:
|
||||
// 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 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();
|
||||
WipeTowerType wipe_tower_type();
|
||||
|
||||
|
||||
@@ -21,30 +21,38 @@ namespace Slic3r {
|
||||
*
|
||||
* @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_config(gcodegen.config()),
|
||||
m_last_predicted_pa(0.0),
|
||||
m_max_next_feedrate(0.0),
|
||||
m_next_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_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.
|
||||
// The model is built the first time an index is requested, as the indices in use depend on
|
||||
// the extruder variant each filament prints with.
|
||||
AdaptivePAInterpolator* AdaptivePAProcessor::getInterpolator(unsigned int config_index) {
|
||||
auto [it, inserted] = m_AdaptivePAInterpolators.try_emplace(config_index);
|
||||
// Only enable model for the index if both PA and adaptive PA options are enabled
|
||||
if (inserted && m_config.adaptive_pressure_advance.get_at(config_index) && m_config.enable_pressure_advance.get_at(config_index)) {
|
||||
it->second = std::make_unique<AdaptivePAInterpolator>();
|
||||
it->second->parseAndSetData(m_config.adaptive_pressure_advance_model.get_at(config_index));
|
||||
}
|
||||
// Method to get the interpolator for a specific tool ID
|
||||
AdaptivePAInterpolator* AdaptivePAProcessor::getInterpolator(unsigned int tool_id) {
|
||||
auto it = m_AdaptivePAInterpolators.find(tool_id);
|
||||
if (it != m_AdaptivePAInterpolators.end()) {
|
||||
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.
|
||||
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)) {
|
||||
// The tag carries the filament config index, which selects the PA settings of the filament's extruder variant
|
||||
int config_index = std::stoi(m_match[1].str());
|
||||
int extruder_id = std::stoi(m_match[1].str());
|
||||
mm3mm_value = std::stod(m_match[2].str());
|
||||
accel_value = std::stod(m_match[3].str());
|
||||
int isBridge = std::stoi(m_match[4].str());
|
||||
int roleChange = std::stoi(m_match[5].str());
|
||||
int isOverhang = std::stoi(m_match[6].str());
|
||||
|
||||
// Check if the filament config index has changed
|
||||
bool config_index_changed = (config_index != m_last_config_index);
|
||||
m_last_config_index = config_index;
|
||||
// Check if the extruder ID has changed
|
||||
bool extruder_changed = (extruder_id != m_last_extruder_id);
|
||||
m_last_extruder_id = extruder_id;
|
||||
|
||||
// Save the PA_CHANGE line to output later after finding feedrate
|
||||
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
|
||||
// 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 adaptive_PA_speed = 0;
|
||||
|
||||
if(!interpolator){ // Tool not found in the interpolator 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";
|
||||
} 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
|
||||
// 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
|
||||
// however check for robustness sake.
|
||||
{
|
||||
// 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";
|
||||
} else { // Model setup succeeded
|
||||
// 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);
|
||||
|
||||
// This is a bridge, use the dedicated PA setting.
|
||||
if(isBridge && m_config.adaptive_pressure_advance_bridges.get_at(m_last_config_index) > EPSILON)
|
||||
predicted_pa = m_config.adaptive_pressure_advance_bridges.get_at(m_last_config_index);
|
||||
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_extruder_id);
|
||||
|
||||
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 debug GCode comments
|
||||
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 Current Speed: " << std::to_string(m_current_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 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
|
||||
m_last_predicted_pa = predicted_pa; // Update the last predicted PA value
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <regex>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include "AdaptivePAInterpolator.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
@@ -32,7 +33,7 @@ public:
|
||||
*
|
||||
* @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.
|
||||
@@ -69,25 +70,28 @@ public:
|
||||
|
||||
private:
|
||||
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.
|
||||
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_next_feedrate; ///< First feed rate (speed) for the upcomming island.
|
||||
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_g1_f_pattern; ///< Regular expression to detect G1 F pattern.
|
||||
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.
|
||||
* @return The Adaptive PA Interpolator object corresponding to that index, or nullptr when adaptive PA is off for it.
|
||||
* This method manually sets the adaptive PA internally held value.
|
||||
* 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
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
#include "InstanceLock.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <system_error>
|
||||
#include <thread>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#ifdef _WIN32
|
||||
#include <boost/interprocess/sync/file_lock.hpp>
|
||||
#include <boost/nowide/convert.hpp>
|
||||
#else
|
||||
#include <cerrno>
|
||||
#include <fcntl.h>
|
||||
#include <sys/file.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
#ifdef _WIN32
|
||||
// LockFileEx, held by this handle alone.
|
||||
using NativeFileLock = boost::interprocess::file_lock;
|
||||
#else
|
||||
// flock(2) rather than an fcntl lock: it belongs to this open file description,
|
||||
// so any other code in the process that opens and closes the lock file, as a
|
||||
// backup or an export walking the data dir might, cannot drop it. An fcntl
|
||||
// lock would go with the first such close.
|
||||
class NativeFileLock
|
||||
{
|
||||
public:
|
||||
explicit NativeFileLock(const char *path) : m_fd(::open(path, O_RDWR | O_CREAT | O_CLOEXEC, 0644))
|
||||
{
|
||||
if (m_fd < 0)
|
||||
throw std::system_error(errno, std::generic_category(), path);
|
||||
}
|
||||
~NativeFileLock() { ::close(m_fd); }
|
||||
bool try_lock()
|
||||
{
|
||||
if (::flock(m_fd, LOCK_EX | LOCK_NB) == 0)
|
||||
return true;
|
||||
// A signal (a child exiting, for one) interrupts the call like any other; the caller polls again.
|
||||
if (errno == EWOULDBLOCK || errno == EINTR)
|
||||
return false;
|
||||
throw std::system_error(errno, std::generic_category(), "flock");
|
||||
}
|
||||
void unlock() { ::flock(m_fd, LOCK_UN); }
|
||||
private:
|
||||
int m_fd;
|
||||
};
|
||||
#endif
|
||||
|
||||
// One slot per lock file, shared by every guard in the process: one lock
|
||||
// object per path behind a mutex is what makes the guard re-entrant and safe
|
||||
// to use from the preset sync thread and the GUI thread at once. The lock
|
||||
// file is opened by the outermost guard and closed when it goes, so the file
|
||||
// is never held open between guards: whatever is at the path is what gets
|
||||
// locked, and a data dir can be removed once nothing is saving into it. The
|
||||
// file is kept rather than deleted on release because the lock state lives in
|
||||
// the kernel on the open file, and deleting it would let a third instance
|
||||
// lock a fresh file while the second still holds the old one.
|
||||
struct InstanceLock::Slot
|
||||
{
|
||||
std::recursive_mutex mutex;
|
||||
// Non-null exactly while this process holds the file lock.
|
||||
std::unique_ptr<NativeFileLock> file_lock;
|
||||
int depth{0};
|
||||
// Until this point, after a guard could not open, lock or wait out the
|
||||
// file, guards do not touch it.
|
||||
std::chrono::steady_clock::time_point cooldown_until{};
|
||||
};
|
||||
|
||||
InstanceLock::Slot &InstanceLock::slot_for(const std::string &lock_file_path)
|
||||
{
|
||||
// Never freed: a save during static destruction still needs its slot.
|
||||
static auto *registry_mutex = new std::mutex();
|
||||
static auto *registry = new std::map<std::string, std::unique_ptr<Slot>>();
|
||||
|
||||
std::lock_guard<std::mutex> guard(*registry_mutex);
|
||||
std::unique_ptr<Slot> &slot = (*registry)[lock_file_path];
|
||||
if (! slot)
|
||||
slot = std::make_unique<Slot>();
|
||||
return *slot;
|
||||
}
|
||||
|
||||
// Starts the cool-down. Called with the slot mutex held.
|
||||
void InstanceLock::defer(Slot &slot, const std::string &reason)
|
||||
{
|
||||
slot.cooldown_until = std::chrono::steady_clock::now() + cooldown;
|
||||
BOOST_LOG_TRIVIAL(warning) << reason << "; proceeding without the lock for the next " << cooldown.count() << " ms";
|
||||
}
|
||||
|
||||
// Creates the lock file if needed and opens it, or starts the cool-down.
|
||||
// Called with the slot mutex held.
|
||||
bool InstanceLock::open_lock_file(Slot &slot, const std::string &lock_file_path)
|
||||
{
|
||||
try {
|
||||
#ifdef _WIN32
|
||||
// The lock opens an existing file; created once, on the first miss.
|
||||
const std::wstring wide_path = boost::nowide::widen(lock_file_path);
|
||||
try {
|
||||
slot.file_lock = std::make_unique<NativeFileLock>(wide_path.c_str());
|
||||
} catch (const std::exception &) {
|
||||
boost::nowide::ofstream(lock_file_path, std::ios::app).close();
|
||||
slot.file_lock = std::make_unique<NativeFileLock>(wide_path.c_str());
|
||||
}
|
||||
#else
|
||||
slot.file_lock = std::make_unique<NativeFileLock>(lock_file_path.c_str());
|
||||
#endif
|
||||
return true;
|
||||
} catch (const std::exception &e) {
|
||||
defer(slot, "Cannot open lock file " + lock_file_path + ": " + e.what() + " (check its owner and permissions)");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
InstanceLock::InstanceLock(const std::string &lock_file_path, std::chrono::milliseconds timeout)
|
||||
{
|
||||
if (lock_file_path.empty())
|
||||
return;
|
||||
m_slot = &slot_for(lock_file_path);
|
||||
m_slot_guard = std::unique_lock<std::recursive_mutex>(m_slot->mutex);
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (m_slot->depth == 0 && now >= m_slot->cooldown_until && open_lock_file(*m_slot, lock_file_path)) {
|
||||
const auto deadline = now + timeout;
|
||||
bool taken = false;
|
||||
for (;;) {
|
||||
try {
|
||||
if ((taken = m_slot->file_lock->try_lock()))
|
||||
break;
|
||||
} catch (const std::exception &e) {
|
||||
defer(*m_slot, "Cannot lock " + lock_file_path + ": " + e.what());
|
||||
break;
|
||||
}
|
||||
if (std::chrono::steady_clock::now() >= deadline) {
|
||||
defer(*m_slot, "Another instance has held " + lock_file_path + " for over " + std::to_string(timeout.count()) + " ms");
|
||||
break;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
}
|
||||
if (! taken)
|
||||
m_slot->file_lock.reset();
|
||||
}
|
||||
// Counted last, so a throw above leaves the slot exactly as it was found.
|
||||
++ m_slot->depth;
|
||||
m_locked = m_slot->file_lock != nullptr;
|
||||
}
|
||||
|
||||
InstanceLock::~InstanceLock()
|
||||
{
|
||||
if (m_slot == nullptr)
|
||||
return;
|
||||
if (-- m_slot->depth == 0 && m_slot->file_lock) {
|
||||
try {
|
||||
m_slot->file_lock->unlock();
|
||||
} catch (const std::exception &e) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Cannot unlock instance lock: " << e.what();
|
||||
}
|
||||
m_slot->file_lock.reset();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Scoped write lock on a file shared by every running instance of the
|
||||
// application, such as the app config or the user preset directory: threads
|
||||
// of this process are serialised through a recursive mutex, other processes
|
||||
// through an advisory OS file lock on `lock_file_path`. The lock file is
|
||||
// created on first use and kept; the OS releases the lock when its holder
|
||||
// exits, so a crashed instance never leaves a stale lock behind.
|
||||
//
|
||||
// Best effort: when the lock file cannot be opened or locked, or another
|
||||
// instance still holds it after `timeout`, the guard keeps only the in-process
|
||||
// mutex, locked() reports false and the write proceeds, since a hung instance
|
||||
// must never block another one from saving. For `cooldown` afterwards guards
|
||||
// leave the file alone. The wait for the in-process mutex is bounded only by
|
||||
// the longest critical section, so a guard covers a few file operations and
|
||||
// nothing slower.
|
||||
//
|
||||
// Lock order: the preset collection mutex may be held when a guard is taken
|
||||
// (set_sync_info_and_save() calls save_info() under it), never the reverse;
|
||||
// that is why the guards sit at the leaf readers and writers and why a guard
|
||||
// must not be added around save_user_presets(), which takes the collection
|
||||
// mutex through delete_preset().
|
||||
class InstanceLock
|
||||
{
|
||||
public:
|
||||
// Long against a critical section of milliseconds, short against the GUI
|
||||
// thread, which is where most guards are taken.
|
||||
static constexpr std::chrono::milliseconds default_timeout{1000};
|
||||
// Long enough that a holder stuck in a debugger does not cost a stall per
|
||||
// save; mutable so tests can shorten it.
|
||||
static inline std::chrono::milliseconds cooldown{10000};
|
||||
|
||||
// An empty path makes the guard a no-op.
|
||||
explicit InstanceLock(const std::string &lock_file_path, std::chrono::milliseconds timeout = default_timeout);
|
||||
~InstanceLock();
|
||||
|
||||
InstanceLock(const InstanceLock &) = delete;
|
||||
InstanceLock &operator=(const InstanceLock &) = delete;
|
||||
|
||||
// True while this process holds the cross-process file lock.
|
||||
bool locked() const { return m_locked; }
|
||||
|
||||
private:
|
||||
struct Slot;
|
||||
static Slot &slot_for(const std::string &lock_file_path);
|
||||
static bool open_lock_file(Slot &slot, const std::string &lock_file_path);
|
||||
static void defer(Slot &slot, const std::string &reason);
|
||||
|
||||
Slot *m_slot{nullptr};
|
||||
std::unique_lock<std::recursive_mutex> m_slot_guard;
|
||||
bool m_locked{false};
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -13,8 +13,19 @@
|
||||
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()
|
||||
{
|
||||
// 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
|
||||
_configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
|
||||
m_orig_numeric_locale = std::setlocale(LC_NUMERIC, nullptr);
|
||||
@@ -29,12 +40,17 @@ CNumericLocalesSetter::CNumericLocalesSetter()
|
||||
m_new_locale = newlocale(LC_NUMERIC_MASK, "C", m_new_locale);
|
||||
uselocale(m_new_locale);
|
||||
#endif
|
||||
// Counted last, since the destructor does not run for a constructor that throws.
|
||||
++ s_numeric_locale_depth;
|
||||
}
|
||||
|
||||
|
||||
|
||||
CNumericLocalesSetter::~CNumericLocalesSetter()
|
||||
{
|
||||
-- s_numeric_locale_depth;
|
||||
if (m_nested)
|
||||
return;
|
||||
#ifdef _WIN32
|
||||
std::setlocale(LC_NUMERIC, m_orig_numeric_locale.data());
|
||||
#else
|
||||
|
||||
@@ -19,8 +19,13 @@ class CNumericLocalesSetter {
|
||||
public:
|
||||
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:
|
||||
// Inside another setter on this thread, which does the setting and restoring.
|
||||
bool m_nested { false };
|
||||
#ifdef _WIN32
|
||||
std::string m_orig_numeric_locale;
|
||||
#else
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include <tbb/blocked_range.h>
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/task_arena.h>
|
||||
#include <tbb/task_group.h>
|
||||
|
||||
#include "Exception.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// A batch of resolved presets, each a whole config, is what resolve_then_commit adds
|
||||
// to peak memory, so it stays far below a vendor's preset count and above any core count.
|
||||
inline constexpr size_t resolve_batch_size = 64;
|
||||
|
||||
// Resolve `count` items that do not depend on each other and install them one at
|
||||
// a time.
|
||||
//
|
||||
// `resolve(i)` runs on any thread and must read only, since the items are
|
||||
// resolved side by side. `commit(i, resolved)` is called for every item in index
|
||||
// order, on the calling thread, and is where shared state is written.
|
||||
//
|
||||
// The items are worked through in batches, so what is resolved and held at once
|
||||
// does not grow with `count`. An exception from either callable propagates after
|
||||
// the batches before it have been committed. A cancellation of the caller's task
|
||||
// group, which stops a batch partway without an exception, throws RuntimeError
|
||||
// before that batch commits.
|
||||
//
|
||||
// `ChunkSetup`, when given, is constructed once for each piece of a batch TBB hands
|
||||
// out, for per-thread state a resolve would otherwise set up per item, such as the
|
||||
// C numeric locale, whose setting takes a lock the whole process shares.
|
||||
template<class ChunkSetup = std::monostate, class Resolve, class Commit>
|
||||
void resolve_then_commit(size_t count, Resolve resolve, Commit commit)
|
||||
{
|
||||
using Resolved = std::invoke_result_t<Resolve&, size_t>;
|
||||
|
||||
std::vector<Resolved> resolved(std::min(count, resolve_batch_size));
|
||||
for (size_t first = 0; first < count; first += resolve_batch_size) {
|
||||
const size_t last = std::min(first + resolve_batch_size, count);
|
||||
// Isolated, so a thread waiting on the batch runs none of the caller's other
|
||||
// tasks before finishing it.
|
||||
tbb::this_task_arena::isolate([&] {
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(first, last),
|
||||
[&](const tbb::blocked_range<size_t>& range) {
|
||||
ChunkSetup setup;
|
||||
(void) setup;
|
||||
for (size_t i = range.begin(); i < range.end(); ++ i)
|
||||
resolved[i - first] = resolve(i);
|
||||
});
|
||||
});
|
||||
if (tbb::is_current_task_group_canceling())
|
||||
throw RuntimeError("resolve_then_commit: canceled before the batch was resolved");
|
||||
for (size_t i = first; i < last; ++ i)
|
||||
commit(i, std::move(resolved[i - first]));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
+350
-168
@@ -5,6 +5,8 @@
|
||||
#include "Preset.hpp"
|
||||
#include "PresetBundle.hpp"
|
||||
#include "AppConfig.hpp"
|
||||
#include "LocalesUtils.hpp"
|
||||
#include "ParallelResolve.hpp"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
@@ -49,6 +51,9 @@
|
||||
#include "libslic3r.h"
|
||||
#include "LifecycleEvents.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "InstanceLock.hpp"
|
||||
|
||||
#include <sstream>
|
||||
#include "Time.hpp"
|
||||
#include "PlaceholderParser.hpp"
|
||||
#include "libslic3r/GCode/Thumbnails.hpp"
|
||||
@@ -105,6 +110,32 @@ std::string get_preset_canonical_name(const std::string &preset_bare_name, const
|
||||
}
|
||||
}
|
||||
|
||||
std::string user_presets_lock_path(bool read_only)
|
||||
{
|
||||
return read_only || data_dir().empty() ? std::string() : (fs::path(data_dir()) / (PRESET_USER_DIR ".lock")).string();
|
||||
}
|
||||
|
||||
// Removes a preset file the scan could not load, and its .info, under the lock.
|
||||
// The scan passes read_only when it could not take the lock, since the file may
|
||||
// then be another instance's fresh write that it merely raced.
|
||||
static void remove_preset_files(const std::string &preset_file, bool read_only)
|
||||
{
|
||||
if (read_only)
|
||||
return;
|
||||
InstanceLock instance_lock(user_presets_lock_path());
|
||||
// A symlink whose target is missing stays, since the target may come back; one
|
||||
// that cannot even be followed is removed like any other unreadable file.
|
||||
auto remove = [](const fs::path &file) {
|
||||
boost::system::error_code ec;
|
||||
if (fs::status(file, ec).type() != fs::file_not_found)
|
||||
fs::remove(file, ec);
|
||||
};
|
||||
fs::path file_path(preset_file);
|
||||
remove(file_path);
|
||||
file_path.replace_extension(".info");
|
||||
remove(file_path);
|
||||
}
|
||||
|
||||
std::string get_preset_bare_name(const std::string &canonical_name)
|
||||
{
|
||||
const auto pos = canonical_name.find_last_of('/');
|
||||
@@ -506,10 +537,11 @@ void Preset::normalize(DynamicPrintConfig &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;
|
||||
for (const std::string &key : config.keys())
|
||||
for (const std::string &key : (added != nullptr ? *added : config).keys())
|
||||
if (! default_config.has(key)) {
|
||||
if (incorrect_keys.empty())
|
||||
incorrect_keys = key;
|
||||
@@ -621,7 +653,6 @@ void Preset::load_info(const std::string& file)
|
||||
}
|
||||
else if (v.first.compare("base_id") == 0) {
|
||||
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)
|
||||
this->base_id.clear();
|
||||
}
|
||||
@@ -647,18 +678,20 @@ void Preset::save_info(std::string file)
|
||||
file = idx_file.string();
|
||||
}
|
||||
|
||||
boost::nowide::ofstream c;
|
||||
c.open(file, std::ios::out | std::ios::trunc);
|
||||
std::string sync_info_to_save;
|
||||
//BBS: hold is used for stop requesting to server this time
|
||||
if (this->sync_info.compare("hold") != 0)
|
||||
sync_info_to_save = this->sync_info;
|
||||
std::ostringstream c;
|
||||
c << "sync_info" << " = " << sync_info_to_save << std::endl;
|
||||
c << "user_id" << " = " << this->user_id << std::endl;
|
||||
c << "setting_id" << " = " << this->setting_id << std::endl;
|
||||
c << "base_id" << " = " << this->base_id << std::endl;
|
||||
c << "updated_time" << " = " << std::to_string(this->updated_time) << std::endl;
|
||||
c.close();
|
||||
|
||||
InstanceLock instance_lock(user_presets_lock_path());
|
||||
if (const std::error_code ec = write_file_atomically(file, c.str()))
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": failed to save " << file << ": " << ec.message();
|
||||
}
|
||||
|
||||
void Preset::remove_files(bool cloud_already_deleted)
|
||||
@@ -667,6 +700,7 @@ void Preset::remove_files(bool cloud_already_deleted)
|
||||
if (this->is_project_embedded) {
|
||||
return;
|
||||
}
|
||||
InstanceLock instance_lock(user_presets_lock_path());
|
||||
// Erase the preset file.
|
||||
boost::nowide::remove(this->file.c_str());
|
||||
fs::path idx_path(this->file);
|
||||
@@ -684,11 +718,11 @@ void Preset::remove_files(bool cloud_already_deleted)
|
||||
}
|
||||
|
||||
//BBS: add logic for only difference save
|
||||
void Preset::save(DynamicPrintConfig* parent_config)
|
||||
bool Preset::save(DynamicPrintConfig* parent_config)
|
||||
{
|
||||
//BBS: add project embedded preset logic
|
||||
if (this->is_project_embedded)
|
||||
return;
|
||||
return true;
|
||||
//BBS: change to json format
|
||||
//this->config.save(this->file);
|
||||
std::string from_str;
|
||||
@@ -703,12 +737,16 @@ void Preset::save(DynamicPrintConfig* parent_config)
|
||||
else
|
||||
from_str = std::string("Default");
|
||||
|
||||
boost::filesystem::create_directories(fs::path(this->file).parent_path());
|
||||
const std::string bare_name = get_preset_bare_name(this->name);
|
||||
|
||||
// What gets written: the diff against the parent, the config plus its
|
||||
// filament id, or the config as is. Built before the lock is taken so the
|
||||
// exclusive window covers only the file writes.
|
||||
DynamicPrintConfig temp_config;
|
||||
const DynamicPrintConfig *to_save = &this->config;
|
||||
|
||||
//BBS: only save difference if it has parent
|
||||
if (parent_config) {
|
||||
DynamicPrintConfig temp_config;
|
||||
std::vector<std::string> dirty_options = config.diff(*parent_config);
|
||||
|
||||
std::string extruder_id_name, extruder_variant_name;
|
||||
@@ -744,13 +782,22 @@ void Preset::save(DynamicPrintConfig* parent_config)
|
||||
opt_dst->set(opt_src);
|
||||
}
|
||||
}
|
||||
temp_config.save_to_json(this->file, bare_name, from_str, this->version.to_string());
|
||||
to_save = &temp_config;
|
||||
} else if (!filament_id.empty() && inherits().empty()) {
|
||||
DynamicPrintConfig temp_config = config;
|
||||
temp_config = config;
|
||||
temp_config.set_key_value(BBL_JSON_KEY_FILAMENT_ID, new ConfigOptionString(filament_id));
|
||||
temp_config.save_to_json(this->file, bare_name, from_str, this->version.to_string());
|
||||
} else {
|
||||
this->config.save_to_json(this->file, bare_name, from_str, this->version.to_string());
|
||||
to_save = &temp_config;
|
||||
}
|
||||
|
||||
std::ostringstream json;
|
||||
to_save->save_to_json(json, bare_name, from_str, this->version.to_string());
|
||||
|
||||
InstanceLock instance_lock(user_presets_lock_path());
|
||||
boost::filesystem::create_directories(fs::path(this->file).parent_path());
|
||||
if (const std::error_code ec = write_file_atomically(this->file, json.str())) {
|
||||
// No .info either: one without its preset reads as a cloud deletion request.
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": failed to save " << this->file << ": " << ec.message();
|
||||
return false;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " save config for: " << this->name << " and filament_id: " << filament_id << " and base_id: " << this->base_id;
|
||||
|
||||
@@ -760,6 +807,7 @@ void Preset::save(DynamicPrintConfig* parent_config)
|
||||
idx_file.replace_extension(".info");
|
||||
this->save_info(idx_file.string());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Preset::reload(Preset const &parent)
|
||||
@@ -771,6 +819,7 @@ void Preset::reload(Preset const &parent)
|
||||
std::string reason;
|
||||
ForwardCompatibilitySubstitutionRule substitution_rule = ForwardCompatibilitySubstitutionRule::Disable;
|
||||
try {
|
||||
InstanceLock instance_lock(user_presets_lock_path());
|
||||
ConfigSubstitutions config_substitutions = config.load_from_json(file, substitution_rule, key_values, reason);
|
||||
this->config = parent.config;
|
||||
this->config.apply(std::move(config));
|
||||
@@ -1669,6 +1718,185 @@ std::string PresetCollection::canonical_preset_name(const std::string &name, con
|
||||
return get_preset_canonical_name(parsed.bare, origin);
|
||||
}
|
||||
|
||||
PresetCollection::PresetFilesOnDisk PresetCollection::PresetFilesOnDisk::read(const boost::filesystem::path &file)
|
||||
{
|
||||
auto read_if_present = [](const fs::path &path) -> std::optional<std::string> {
|
||||
std::string bytes;
|
||||
try {
|
||||
load_string_file(path, bytes);
|
||||
} catch (const std::exception &) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return bytes;
|
||||
};
|
||||
fs::path info_path(file);
|
||||
info_path.replace_extension(".info");
|
||||
return { read_if_present(file), read_if_present(info_path) };
|
||||
}
|
||||
|
||||
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();
|
||||
// Before either file is parsed, so a save that lands during the parse still shows
|
||||
// up as a difference when commit compares.
|
||||
out.on_disk = PresetFilesOnDisk::read(file);
|
||||
// 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)
|
||||
remove_preset_files(loaded.preset.file, read_only);
|
||||
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)
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " added compatible_printers for preset: " << preset.name << " (not written back)";
|
||||
else if (preset.save(nullptr))
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " added compatible_printers for preset: " << preset.name;
|
||||
else
|
||||
++m_errors; // save() logged why
|
||||
} 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.
|
||||
// Throws an exception on error.
|
||||
void PresetCollection::load_presets(
|
||||
@@ -1706,6 +1934,8 @@ void PresetCollection::load_presets(
|
||||
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);
|
||||
|
||||
struct UserPresetFile { fs::path path; std::string canonical_name; };
|
||||
std::vector<UserPresetFile> files;
|
||||
//BBS: change to json format
|
||||
for (auto &dir_entry : boost::filesystem::directory_iterator(dir))
|
||||
{
|
||||
@@ -1721,149 +1951,47 @@ void PresetCollection::load_presets(
|
||||
BOOST_LOG_TRIVIAL(warning) << "Preset already present, not loading: " << canonical_name;
|
||||
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 i, UserPresetLoad &&loaded) {
|
||||
// Resolve read the files without the lock, so another instance may have saved
|
||||
// over them since. A file that changed is resolved again under the lock before
|
||||
// commit removes or rewrites it, which also keeps its .json and .info from two
|
||||
// different saves apart. Without the lock nothing is checked, so commit leaves
|
||||
// the files alone.
|
||||
const std::string lock_path = user_presets_lock_path(read_only);
|
||||
InstanceLock instance_lock(lock_path);
|
||||
if (instance_lock.locked()) {
|
||||
const PresetFilesOnDisk on_disk = PresetFilesOnDisk::read(files[i].path);
|
||||
boost::system::error_code ec;
|
||||
// The link itself, and a stat error counts as present, so a file that is
|
||||
// there but cannot be read is still counted and removed by commit.
|
||||
if (! on_disk.json && fs::symlink_status(files[i].path, ec).type() == fs::file_not_found)
|
||||
return; // removed by another instance since it was read
|
||||
if (! (on_disk == loaded.on_disk)) {
|
||||
CNumericLocalesSetter locales_setter;
|
||||
loaded = 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);
|
||||
}
|
||||
}
|
||||
const bool leave_files = read_only || (! lock_path.empty() && ! instance_lock.locked());
|
||||
// Committing can remove an unreadable preset's file, and a filesystem error
|
||||
// there is reported without stopping the rest of the directory.
|
||||
try {
|
||||
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;
|
||||
this->commit_user_preset(std::move(loaded), presets_loaded, substitutions, preset_loaded_fn, leave_files);
|
||||
} catch (const std::runtime_error &err) {
|
||||
errors_cummulative += err.what();
|
||||
errors_cummulative += "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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()));
|
||||
sort_presets();
|
||||
@@ -2167,7 +2295,10 @@ void PresetCollection::set_sync_info_and_save(std::string name, std::string sett
|
||||
preset->setting_id = setting_id;
|
||||
if (update_time > 0)
|
||||
preset->updated_time = update_time;
|
||||
preset->sync_info == "update" ? preset->save(nullptr) : preset->save_info();
|
||||
if (preset->sync_info == "update")
|
||||
preset->save(nullptr);
|
||||
else
|
||||
preset->save_info();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -2827,6 +2958,18 @@ std::pair<Preset*, bool> PresetCollection::load_external_preset(
|
||||
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)
|
||||
{
|
||||
lock();
|
||||
@@ -3846,28 +3989,53 @@ bool PresetCollection::select_preset_by_name_strict(const std::string &name)
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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)
|
||||
std::vector<std::vector<std::string>> PresetCollection::merge_presets(const std::vector<PresetCollection*> &others, const VendorMap &new_vendors)
|
||||
{
|
||||
std::vector<std::string> duplicates;
|
||||
for (Preset &preset : other.m_presets) {
|
||||
if (preset.is_default || preset.is_external)
|
||||
continue;
|
||||
Preset key(m_type, preset.name);
|
||||
auto it = (m_type == Preset::TYPE_FILAMENT)
|
||||
? std::lower_bound(m_presets.begin() + m_num_default_presets, m_presets.end(), key, filament_preset_less)
|
||||
: std::lower_bound(m_presets.begin() + m_num_default_presets, m_presets.end(), key);
|
||||
if (it == m_presets.end() || it->name != preset.name) {
|
||||
auto less = [this](const Preset &a, const Preset &b) {
|
||||
return m_type == Preset::TYPE_FILAMENT ? filament_preset_less(a, b) : a < b;
|
||||
};
|
||||
struct Incoming { Preset *preset; size_t source; };
|
||||
auto incoming_less = [&less](const Incoming &a, const Incoming &b) { return less(*a.preset, *b.preset); };
|
||||
// Each of `others` is sorted, so its presets form one sorted run.
|
||||
std::vector<Incoming> incoming;
|
||||
std::vector<size_t> run_ends { 0 };
|
||||
for (size_t source = 0; source < others.size(); ++ source) {
|
||||
for (Preset &preset : others[source]->m_presets)
|
||||
if (! preset.is_default && ! preset.is_external)
|
||||
incoming.push_back({ &preset, source });
|
||||
assert(std::is_sorted(incoming.begin() + run_ends.back(), incoming.end(), incoming_less));
|
||||
run_ends.push_back(incoming.size());
|
||||
}
|
||||
// Merged pairwise and stably, so equal names stay in the order of `others`.
|
||||
const size_t runs = others.size();
|
||||
for (size_t width = 1; width < runs; width *= 2)
|
||||
for (size_t i = 0; i + width < runs; i += 2 * width)
|
||||
std::inplace_merge(incoming.begin() + run_ends[i], incoming.begin() + run_ends[i + width],
|
||||
incoming.begin() + run_ends[std::min(i + 2 * width, runs)], incoming_less);
|
||||
|
||||
std::vector<std::vector<std::string>> duplicates(others.size());
|
||||
std::deque<Preset> merged;
|
||||
auto own = m_presets.begin() + m_num_default_presets;
|
||||
std::move(m_presets.begin(), own, std::back_inserter(merged));
|
||||
// On equal names this collection's preset is kept, else the earliest of `others`,
|
||||
// and each repeat is listed under the collection it came from.
|
||||
for (auto next = incoming.begin(); own != m_presets.end() || next != incoming.end();) {
|
||||
if (next == incoming.end() || (own != m_presets.end() && ! less(*next->preset, *own)))
|
||||
merged.emplace_back(std::move(*own ++));
|
||||
else {
|
||||
Preset &preset = *(next ++)->preset;
|
||||
if (preset.vendor != nullptr) {
|
||||
// Re-assign a pointer to the vendor structure in the new PresetBundle.
|
||||
auto it = new_vendors.find(preset.vendor->id);
|
||||
assert(it != new_vendors.end());
|
||||
preset.vendor = &it->second;
|
||||
}
|
||||
m_presets.emplace(it, std::move(preset));
|
||||
} else
|
||||
duplicates.emplace_back(std::move(preset.name));
|
||||
merged.emplace_back(std::move(preset));
|
||||
}
|
||||
for (; next != incoming.end() && next->preset->name == merged.back().name; ++ next)
|
||||
duplicates[next->source].emplace_back(next->preset->name);
|
||||
}
|
||||
m_presets = std::move(merged);
|
||||
return duplicates;
|
||||
}
|
||||
|
||||
@@ -4212,8 +4380,15 @@ void PhysicalPrinter::update_preset_names_in_config()
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicalPrinter::save(DynamicPrintConfig* /* parent_config */)
|
||||
{
|
||||
InstanceLock instance_lock(user_presets_lock_path());
|
||||
this->config.save_to_json(this->file, std::string("Physical_Printer"), std::string("User"), std::string(SLIC3R_VERSION));
|
||||
}
|
||||
|
||||
void PhysicalPrinter::save(const std::string& file_name_from, const std::string& file_name_to)
|
||||
{
|
||||
InstanceLock instance_lock(user_presets_lock_path());
|
||||
// rename the file
|
||||
boost::nowide::rename(file_name_from.data(), file_name_to.data());
|
||||
this->file = file_name_to;
|
||||
@@ -4345,6 +4520,7 @@ void PhysicalPrinterCollection::load_printers(
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
InstanceLock instance_lock(user_presets_lock_path());
|
||||
PhysicalPrinter printer(name, this->default_config());
|
||||
printer.file = dir_entry.path().string();
|
||||
// Load the preset file, apply preset values on top of defaults.
|
||||
@@ -4537,7 +4713,10 @@ bool PhysicalPrinterCollection::delete_printer(const std::string& name)
|
||||
|
||||
const PhysicalPrinter& printer = *it;
|
||||
// Erase the preset file.
|
||||
{
|
||||
InstanceLock instance_lock(user_presets_lock_path());
|
||||
boost::nowide::remove(printer.file.c_str());
|
||||
}
|
||||
m_printers.erase(it);
|
||||
return true;
|
||||
}
|
||||
@@ -4549,7 +4728,10 @@ bool PhysicalPrinterCollection::delete_selected_printer()
|
||||
const PhysicalPrinter& printer = this->get_selected_printer();
|
||||
|
||||
// Erase the preset file.
|
||||
{
|
||||
InstanceLock instance_lock(user_presets_lock_path());
|
||||
boost::nowide::remove(printer.file.c_str());
|
||||
}
|
||||
// Remove the preset from the list.
|
||||
m_printers.erase(m_printers.begin() + m_idx_selected);
|
||||
// unselect all printers
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <unordered_set>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/property_tree/ptree_fwd.hpp>
|
||||
@@ -330,7 +331,8 @@ public:
|
||||
|
||||
//BBS: add logic for only difference save
|
||||
//if parent_config is null, save all keys, otherwise, only save difference
|
||||
void save(DynamicPrintConfig* parent_config);
|
||||
// Returns false when the preset file could not be written.
|
||||
bool save(DynamicPrintConfig* parent_config);
|
||||
void reload(Preset const & parent);
|
||||
|
||||
// Return a label of this preset, consisting of a name and a "(modified)" suffix, if this preset is dirty.
|
||||
@@ -445,7 +447,10 @@ public:
|
||||
static std::string remove_suffix_modified(const std::string& name);
|
||||
static void normalize(DynamicPrintConfig &config);
|
||||
// Report configuration fields, which are misplaced into a wrong group, remove them from the config.
|
||||
static std::string remove_invalid_keys(DynamicPrintConfig &config, const DynamicPrintConfig &default_config);
|
||||
// `added`, when given, is the diff applied over a copy of default_config, and only
|
||||
// its keys are checked, since no other key can be missing from default_config.
|
||||
static std::string remove_invalid_keys(DynamicPrintConfig &config, const DynamicPrintConfig &default_config,
|
||||
const DynamicPrintConfig *added = nullptr);
|
||||
|
||||
// BBS: move constructor to public
|
||||
Preset(Type type, const std::string &name, bool is_default = false) : type(type), is_default(is_default), name(name) {}
|
||||
@@ -485,6 +490,12 @@ std::string get_preset_canonical_name(const std::string &preset_bare_name, const
|
||||
// Tail segment of a canonical name — what's written to the bundle's .json filename and JSON "name" field.
|
||||
std::string get_preset_bare_name(const std::string &canonical_name);
|
||||
|
||||
// Lock file guarding every user preset file under data_dir() against other
|
||||
// running instances and the preset sync thread. Empty without a data dir, and
|
||||
// for a read-only load (the CLI), which never rewrites or deletes and may run
|
||||
// many jobs on one data dir.
|
||||
std::string user_presets_lock_path(bool read_only = false);
|
||||
|
||||
// Resolve an origin from a directory path when the caller passes Kind::Auto.
|
||||
PresetOrigin detect_origin_from_path(const boost::filesystem::path &path, const PresetOrigin &explicit_origin = PresetOrigin());
|
||||
|
||||
@@ -801,6 +812,8 @@ public:
|
||||
// Return number of presets including the "- default -" preset.
|
||||
size_t size() const { return m_presets.size(); }
|
||||
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.
|
||||
template<typename PreferedCondition>
|
||||
@@ -874,8 +887,10 @@ protected:
|
||||
// This is a temporary state, which shall be fixed immediately by the following step.
|
||||
bool select_preset_by_name_strict(const std::string &name);
|
||||
|
||||
// Merge one vendor's presets with the other vendor's presets, report duplicates.
|
||||
std::vector<std::string> merge_presets(PresetCollection &&other, const VendorMap &new_vendors);
|
||||
// Move the presets of `others` into this collection in one pass. A name this
|
||||
// collection or an earlier one of `others` already has is left out, and reported
|
||||
// in the list of the collection that repeats it.
|
||||
std::vector<std::vector<std::string>> merge_presets(const std::vector<PresetCollection*> &others, const VendorMap &new_vendors);
|
||||
|
||||
// Update m_map_alias_to_profile_name from loaded system profiles.
|
||||
void update_map_alias_to_profile_name();
|
||||
@@ -890,6 +905,56 @@ protected:
|
||||
void set_custom_preset_alias(Preset &preset);
|
||||
|
||||
private:
|
||||
// A preset file and its .info as read from disk, std::nullopt for one that is missing.
|
||||
struct PresetFilesOnDisk
|
||||
{
|
||||
std::optional<std::string> json;
|
||||
std::optional<std::string> info;
|
||||
|
||||
static PresetFilesOnDisk read(const boost::filesystem::path &file);
|
||||
bool operator==(const PresetFilesOnDisk &rhs) const { return json == rhs.json && info == rhs.info; }
|
||||
};
|
||||
|
||||
// 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;
|
||||
// Both files as they were before the preset was read from them. Resolve runs
|
||||
// without the instance lock, so commit compares this with the disk under it.
|
||||
PresetFilesOnDisk on_disk;
|
||||
// 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;
|
||||
|
||||
// Comparator that sorts "Generic " prefixed presets before others, then alphabetically within each group.
|
||||
@@ -901,6 +966,10 @@ private:
|
||||
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.
|
||||
void sort_presets() {
|
||||
if (m_type == Preset::TYPE_FILAMENT)
|
||||
@@ -1054,7 +1123,7 @@ public:
|
||||
|
||||
//BBS: change to json format
|
||||
//void save() { this->config.save(this->file); }
|
||||
void save(DynamicPrintConfig* parent_config) { this->config.save_to_json(this->file, std::string("Physical_Printer"), std::string("User"), std::string(SLIC3R_VERSION)); }
|
||||
void save(DynamicPrintConfig* parent_config);
|
||||
void save(const std::string& file_name_from, const std::string& file_name_to);
|
||||
|
||||
void update_from_preset(const Preset& preset);
|
||||
|
||||
+584
-398
File diff suppressed because it is too large
Load Diff
+172
-27
@@ -15,6 +15,7 @@
|
||||
#include <unordered_map>
|
||||
#include <optional>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <unordered_set>
|
||||
|
||||
@@ -622,36 +623,180 @@ public:
|
||||
// default_filament_profile must resolve to a system filament.
|
||||
bool check_printer_default_materials() const;
|
||||
|
||||
// Merge one vendor's presets with the other vendor's presets, report duplicates.
|
||||
// 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::vector<std::string> merge_presets(PresetBundle &&other);
|
||||
// One vendor to load, and the directory it is installed in.
|
||||
struct VendorSource
|
||||
{
|
||||
std::string name;
|
||||
boost::filesystem::path dir;
|
||||
};
|
||||
|
||||
// Load `vendors` into this bundle, the Orca filament library directly and every
|
||||
// other vendor in parallel into a bundle of its own that inherits from it, merged
|
||||
// in the order given. A vendor that cannot be loaded has its error added to the
|
||||
// returned text, or thrown in validation mode, and its name to `failed`; it is
|
||||
// left out, except for the library, which keeps what it installed before the
|
||||
// failure. Once `cancel` is set, no further vendor starts loading.
|
||||
std::pair<PresetsConfigSubstitutions, std::string> load_vendors(const std::vector<VendorSource>& vendors,
|
||||
ForwardCompatibilitySubstitutionRule compatibility_rule, bool allow_cache,
|
||||
const std::atomic<bool>* cancel = nullptr, std::vector<std::string>* failed = nullptr);
|
||||
|
||||
private:
|
||||
// Load one vendor from the preset cache installed in `dir`, judged against
|
||||
// the vendor profile there. False, with this bundle left clean, when there
|
||||
// is no usable cache and the vendor has to be parsed. This is how
|
||||
// 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);
|
||||
// Move the presets and vendor profiles of `others` into this bundle, in one pass
|
||||
// over each collection. A preset whose name this bundle or an earlier one of
|
||||
// `others` already has is left out and listed under the bundle that repeats it.
|
||||
std::vector<std::vector<std::string>> merge_presets(const std::vector<PresetBundle*> &others);
|
||||
|
||||
// Load one source-form preset entry into this bundle: resolve `inherits`
|
||||
// and `include`, flatten, validate and register the preset. Returns the
|
||||
// reason loading failed, empty on success. See the definition for the
|
||||
// sharing contract between the JSON parse and the cache load.
|
||||
// retain_configs / retain_includes, when non-null, name the only presets
|
||||
// registered into config_maps / include_maps (a config copy each). The
|
||||
// cache load passes the names its entries inherit / include — the only
|
||||
// ones ever looked up again; the JSON parse retains all, not knowing what
|
||||
// later subfiles name.
|
||||
std::string load_vendor_preset(const CachedPreset& entry,
|
||||
const std::string& path, const std::string& vendor_name,
|
||||
const PresetBundle* base_bundle,
|
||||
LoadConfigBundleAttributes flags,
|
||||
ConfigSubstitutionContext& substitution_context, PresetsConfigSubstitutions& substitutions,
|
||||
std::map<std::string, DynamicPrintConfig>& config_maps, std::map<std::string, DynamicPrintConfig>& include_maps,
|
||||
std::map<std::string, std::string>& filament_id_maps,
|
||||
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);
|
||||
// What parsing one entry's JSON sub-file reported. Its errors and warnings are
|
||||
// logged when the entry installs, so they come out in listing order with the
|
||||
// entry's install errors, as parsing and installing one entry at a time leaves them.
|
||||
struct EntryParse
|
||||
{
|
||||
ConfigSubstitutions substitutions;
|
||||
// Counted in the bundle's error count.
|
||||
std::vector<std::string> errors;
|
||||
std::vector<std::string> warnings;
|
||||
};
|
||||
|
||||
// An EntryParse for each entry of the VendorCacheData list of the same name.
|
||||
struct VendorParse
|
||||
{
|
||||
std::vector<EntryParse> process_entries;
|
||||
std::vector<EntryParse> filament_entries;
|
||||
std::vector<EntryParse> machine_entries;
|
||||
};
|
||||
|
||||
// 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.
|
||||
void clear_printer_hold_aliases();
|
||||
|
||||
@@ -400,46 +400,24 @@ bool write_cache_blob(const std::string& path, const std::string& blob)
|
||||
{
|
||||
boost::crc_32_type crc;
|
||||
crc.process_bytes(blob.data(), blob.size());
|
||||
// Written beside the target and moved into place, as AppConfig::save does:
|
||||
// a cache is truncated and rewritten in full, so a write that dies partway
|
||||
// would otherwise leave a header claiming more body than the file holds.
|
||||
// The PID suffix also keeps two instances writing the same vendor from
|
||||
// interleaving.
|
||||
const std::string tmp_path = path + "." + std::to_string(get_current_pid()) + ".tmp";
|
||||
// Written beside the target and moved into place: a cache is truncated and
|
||||
// rewritten in full, so a write that dies partway would otherwise leave a
|
||||
// header claiming more body than the file holds.
|
||||
try {
|
||||
boost::filesystem::create_directories(boost::filesystem::path(path).parent_path());
|
||||
{
|
||||
boost::nowide::ofstream ofs(tmp_path, std::ios::binary | std::ios::trunc);
|
||||
if (!ofs.is_open()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "VendorCacheFile: cannot open for writing: " << tmp_path;
|
||||
return false;
|
||||
}
|
||||
CacheFileHeader fhdr;
|
||||
fhdr.magic = CACHE_MAGIC;
|
||||
fhdr.version = CACHE_VERSION;
|
||||
fhdr.data_size = static_cast<uint64_t>(blob.size());
|
||||
fhdr.crc32 = crc.checksum();
|
||||
ofs.write(reinterpret_cast<const char*>(&fhdr), sizeof(fhdr));
|
||||
ofs.write(blob.data(), static_cast<std::streamsize>(blob.size()));
|
||||
ofs.close(); // flush; close() raises failbit on error
|
||||
if (! ofs.good()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "VendorCacheFile: write failed (" << tmp_path << ")";
|
||||
boost::system::error_code ec;
|
||||
boost::filesystem::remove(tmp_path, ec);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (const std::error_code ec = rename_file(tmp_path, path)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "VendorCacheFile: could not move " << tmp_path << " into place: " << ec.message();
|
||||
boost::system::error_code rm;
|
||||
boost::filesystem::remove(tmp_path, rm);
|
||||
const std::string_view header(reinterpret_cast<const char*>(&fhdr), sizeof(fhdr));
|
||||
if (const std::error_code ec = write_file_atomically(path, { header, std::string_view(blob) }, /*binary=*/true)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "VendorCacheFile: write failed (" << path << "): " << ec.message();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "VendorCacheFile: write failed (" << path << "): " << e.what();
|
||||
boost::system::error_code ec;
|
||||
boost::filesystem::remove(tmp_path, ec);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
// preset it inherits and the presets it includes, and the parse metadata —
|
||||
// everything the parse phase of load_vendor_configs_from_json extracts and
|
||||
// everything PresetBundle::parse_vendor_json extracts and
|
||||
// nothing it derives. Inheritance and includes are resolved when the entry is
|
||||
// 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
|
||||
|
||||
+5
-11
@@ -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)
|
||||
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
|
||||
const std::vector<int> &self_index = m_config.filament_self_index.values;
|
||||
const size_t pa_columns = std::max(m_config.adaptive_pressure_advance_model.size(), size_t(extruders.back()) + 1);
|
||||
for (size_t column = 0; column < pa_columns; ++column) {
|
||||
// filament_self_index maps a column to its filament once the filament arrays hold one column per variant
|
||||
const unsigned int filament_id = self_index.size() == pa_columns ? self_index[column] - 1 : column;
|
||||
if (!std::binary_search(extruders.begin(), extruders.end(), filament_id))
|
||||
continue;
|
||||
if (m_config.adaptive_pressure_advance.get_at(column) &&
|
||||
m_config.enable_pressure_advance.get_at(column)) {
|
||||
// check adaptive pressure advance model
|
||||
for (unsigned int extruder_id : extruders) {
|
||||
if (m_config.adaptive_pressure_advance.get_at(extruder_id) &&
|
||||
m_config.enable_pressure_advance.get_at(extruder_id)) {
|
||||
|
||||
const std::string pa_model = m_config.adaptive_pressure_advance_model.get_at(column);
|
||||
const std::string pa_model = m_config.adaptive_pressure_advance_model.get_at(extruder_id);
|
||||
if (!pa_model.empty()) {
|
||||
std::string validation_error = AdaptivePAProcessor::validate_adaptive_pa_model(pa_model);
|
||||
if (!validation_error.empty()) {
|
||||
|
||||
@@ -9403,10 +9403,6 @@ void PrintConfigDef::handle_legacy_composite(DynamicPrintConfig &config)
|
||||
}
|
||||
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;
|
||||
@@ -9509,13 +9505,6 @@ std::set<std::string> filament_options_with_variant = {
|
||||
"filament_ironing_spacing",
|
||||
"filament_ironing_inset",
|
||||
"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_during_print",
|
||||
"activate_air_filtration_on_completion",
|
||||
@@ -10723,25 +10712,6 @@ void set_variant_override(ConfigOptionVectorBase &target, const ConfigOptionVect
|
||||
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
|
||||
//use the smallest of multiple to single
|
||||
|
||||
@@ -884,11 +884,6 @@ extern std::set<std::string> empty_options;
|
||||
void set_variant_override(ConfigOptionVectorBase &target, const ConfigOptionVectorBase &source,
|
||||
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 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);
|
||||
|
||||
+18
-10
@@ -8,6 +8,8 @@
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
#include <system_error>
|
||||
#include <initializer_list>
|
||||
#include <string_view>
|
||||
#include <regex>
|
||||
|
||||
#include <boost/system/error_code.hpp>
|
||||
@@ -224,6 +226,21 @@ extern std::vector<std::string> split_string(const std::string &str, char delimi
|
||||
// On Windows, the file explorer (or anti-virus or whatever else) often locks the file
|
||||
// for a short while, so the file may not be movable. Retry while we see recoverable errors.
|
||||
extern std::error_code rename_file(const std::string &from, const std::string &to);
|
||||
// Write `chunks`, in order, to `path` through a temporary file beside it that is
|
||||
// then renamed over the target, so a concurrent reader sees the old or the new
|
||||
// file, never a partial one. The temporary is removed on failure and an existing
|
||||
// target keeps its permissions. Text mode unless `binary`, so Windows writes CRLF
|
||||
// as the streams this replaces did. A target that is not a regular file (a
|
||||
// device or pipe) is written in place, since replacing it would change what it
|
||||
// is, and so is an existing target beside which no temporary can be created or
|
||||
// whose replace the filesystem refuses; a symlink is followed and the file it
|
||||
// names is replaced. On Windows a reader holding the
|
||||
// target open without sharing its deletion, which the C runtime does not, makes
|
||||
// the replace fall back to the in-place write too, so an unlocked reader there
|
||||
// can still see a partial file.
|
||||
extern std::error_code write_file_atomically(const std::string &path, std::initializer_list<std::string_view> chunks, bool binary = false);
|
||||
inline std::error_code write_file_atomically(const std::string &path, const std::string &content, bool binary = false)
|
||||
{ return write_file_atomically(path, { std::string_view(content) }, binary); }
|
||||
|
||||
enum CopyFileResult {
|
||||
SUCCESS = 0,
|
||||
@@ -256,19 +273,10 @@ extern bool is_gallery_file(const std::string& path, char const* type);
|
||||
extern bool is_shapes_dir(const std::string& dir);
|
||||
//BBS: add json support
|
||||
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
|
||||
// is rejected on all of them.
|
||||
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
|
||||
inline bool is_orca_open(const std::string& url) { return boost::starts_with(url, "orcaslicer://open"); }
|
||||
|
||||
@@ -4,9 +4,6 @@
|
||||
#include "miniz_extension.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#if defined(_MSC_VER) || defined(__MINGW64__)
|
||||
#include "boost/nowide/cstdio.hpp"
|
||||
#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);
|
||||
}
|
||||
|
||||
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_zip_zero_struct(&arch);
|
||||
|
||||
@@ -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_writer(mz_zip_archive *zip);
|
||||
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 {
|
||||
public:
|
||||
|
||||
+91
-41
@@ -9,6 +9,8 @@
|
||||
#include <stdio.h>
|
||||
#include <filesystem>
|
||||
#include <sstream>
|
||||
#include <cerrno>
|
||||
#include <mutex>
|
||||
#include <iomanip>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
@@ -70,7 +72,6 @@
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/algorithm/string/case_conv.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
@@ -703,13 +704,99 @@ namespace WindowsSupport
|
||||
std::error_code rename_file(const std::string &from, const std::string &to)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// Retries and moves an open destination aside itself.
|
||||
return WindowsSupport::rename(from, to);
|
||||
#else
|
||||
boost::nowide::remove(to.c_str());
|
||||
return std::make_error_code(static_cast<std::errc>(boost::nowide::rename(from.c_str(), to.c_str())));
|
||||
// rename(2) replaces an existing target atomically; removing it first would
|
||||
// leave a window in which the file does not exist at all.
|
||||
if (boost::nowide::rename(from.c_str(), to.c_str()) == 0)
|
||||
return {};
|
||||
const int err = errno;
|
||||
// Some mounts (sshfs, gvfs, MTP and a few SMB setups) refuse to replace an
|
||||
// existing target in one step, each with the error it sees fit; every error
|
||||
// is worth the remove-then-rename this always did, except the ones no retry
|
||||
// can help: nothing at the source, a different device, or a directory where
|
||||
// a file was expected and the reverse.
|
||||
const bool worth_retrying = err != ENOENT && err != EXDEV && err != ENOTDIR && err != EISDIR;
|
||||
if (worth_retrying && boost::nowide::remove(to.c_str()) == 0 && boost::nowide::rename(from.c_str(), to.c_str()) == 0)
|
||||
return {};
|
||||
return std::make_error_code(static_cast<std::errc>(err));
|
||||
#endif
|
||||
}
|
||||
|
||||
static std::error_code write_whole_file(const std::string &path, std::initializer_list<std::string_view> chunks, bool binary)
|
||||
{
|
||||
errno = 0;
|
||||
FILE *file = boost::nowide::fopen(path.c_str(), binary ? "wb" : "w");
|
||||
if (file == nullptr)
|
||||
return std::make_error_code(errno != 0 ? static_cast<std::errc>(errno) : std::errc::io_error);
|
||||
bool ok = true;
|
||||
for (const std::string_view chunk : chunks)
|
||||
ok = ok && std::fwrite(chunk.data(), 1, chunk.size(), file) == chunk.size();
|
||||
ok = ok && std::fflush(file) == 0;
|
||||
const int err = ok ? 0 : errno;
|
||||
ok = std::fclose(file) == 0 && ok;
|
||||
if (ok)
|
||||
return {};
|
||||
return std::make_error_code(err != 0 ? static_cast<std::errc>(err) : std::errc::io_error);
|
||||
}
|
||||
|
||||
// The in-place fallback truncates the target, so two threads of this process
|
||||
// on the same file must not both be in it. One mutex for all such writes: they
|
||||
// are the rare case. Never freed, like the InstanceLock registry, so a save
|
||||
// during static destruction still finds it.
|
||||
static std::error_code write_in_place(const std::string &path, std::initializer_list<std::string_view> chunks, bool binary)
|
||||
{
|
||||
static auto *mutex = new std::mutex();
|
||||
std::lock_guard<std::mutex> guard(*mutex);
|
||||
return write_whole_file(path, chunks, binary);
|
||||
}
|
||||
|
||||
std::error_code write_file_atomically(const std::string &path, std::initializer_list<std::string_view> chunks, bool binary)
|
||||
{
|
||||
boost::system::error_code bec;
|
||||
const boost::filesystem::file_status target = boost::filesystem::symlink_status(path, bec);
|
||||
const bool target_exists = ! bec && boost::filesystem::exists(target);
|
||||
if (target_exists && boost::filesystem::is_symlink(target)) {
|
||||
// A config or preset kept in a dotfiles repository: the link stays,
|
||||
// the file it points to is replaced like any other.
|
||||
const boost::filesystem::path resolved = boost::filesystem::canonical(path, bec);
|
||||
if (! bec && boost::filesystem::is_regular_file(resolved, bec))
|
||||
return write_file_atomically(resolved.string(), chunks, binary);
|
||||
}
|
||||
if (target_exists && ! boost::filesystem::is_regular_file(target))
|
||||
return write_in_place(path, chunks, binary);
|
||||
|
||||
// Unique per process and per call, so two threads writing one target
|
||||
// without a lock never share a temporary.
|
||||
static std::atomic<unsigned> counter{0};
|
||||
const std::string tmp_path = path + "." + std::to_string(get_current_pid()) + "." + std::to_string(counter++) + ".tmp";
|
||||
if (const std::error_code ec = write_whole_file(tmp_path, chunks, binary)) {
|
||||
boost::nowide::remove(tmp_path.c_str());
|
||||
if (! target_exists)
|
||||
return ec;
|
||||
// A directory that lets this process write its files but not create
|
||||
// one: losing the save is worse than a reader seeing a partial file.
|
||||
BOOST_LOG_TRIVIAL(warning) << "Cannot create a temporary beside " << path << " (" << ec.message() << "); writing in place";
|
||||
return write_in_place(path, chunks, binary);
|
||||
}
|
||||
#ifndef _WIN32
|
||||
// Not on Windows, where a read-only bit on the temporary would stop the rename itself.
|
||||
if (target_exists)
|
||||
boost::filesystem::permissions(tmp_path, target.permissions(), bec);
|
||||
#endif
|
||||
if (const std::error_code ec = rename_file(tmp_path, path)) {
|
||||
boost::nowide::remove(tmp_path.c_str());
|
||||
// A reader on Windows holding the target open without FILE_SHARE_DELETE,
|
||||
// or a mount that cannot replace a file at all. Losing the save is worse
|
||||
// than a reader seeing a partial file, so write in place the way this
|
||||
// used to work before the atomic path existed.
|
||||
BOOST_LOG_TRIVIAL(warning) << "Cannot replace " << path << " (" << ec.message() << "); writing in place";
|
||||
return write_in_place(path, chunks, binary);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
// Copied from boost::filesystem.
|
||||
// Called by copy_file_linux() in case linux sendfile() API is not supported.
|
||||
@@ -1094,9 +1181,6 @@ bool is_path_within_root(const std::string &rel_path, const boost::filesystem::p
|
||||
auto is_separator = [](char c) { return c == '/' || c == '\\'; };
|
||||
if (rel_path.empty() || is_separator(rel_path.front()) || (rel_path.size() > 1 && rel_path[1] == ':'))
|
||||
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();) {
|
||||
size_t end = start;
|
||||
while (end < rel_path.size() && !is_separator(rel_path[end]))
|
||||
@@ -1107,10 +1191,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.
|
||||
try {
|
||||
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 root_str = boost::filesystem::weakly_canonical(root).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 &&
|
||||
(full_str.size() == root_str.size() || full_str[root_str.size()] == boost::filesystem::path::preferred_separator);
|
||||
@@ -1119,37 +1200,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)
|
||||
{
|
||||
return boost::iends_with(path, ".png") || boost::iends_with(path, ".svg");
|
||||
|
||||
@@ -1019,9 +1019,14 @@ if (UNIX AND NOT APPLE)
|
||||
find_package(GTK${SLIC3R_GTK} REQUIRED)
|
||||
pkg_check_modules(LIBSECRET REQUIRED libsecret-1)
|
||||
pkg_check_modules(webkit2gtk REQUIRED webkit2gtk-4.1)
|
||||
if (FLATPAK)
|
||||
# I don't know why this is needed, but for whatever reason slic3r isn't
|
||||
# linking to X11 and webkit2gtk. force it.
|
||||
find_package(X11 REQUIRED)
|
||||
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_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
|
||||
# LinuxDisplayBackend.cpp can include the right GDK headers.
|
||||
|
||||
@@ -428,7 +428,7 @@ void AuFile::on_dclick(wxMouseEvent &evt)
|
||||
if (m_type == AddFileButton)
|
||||
return;
|
||||
else
|
||||
desktop_open_project_attachment(this, m_file_path);
|
||||
wxLaunchDefaultApplication(m_file_path.wstring(), 0);
|
||||
}
|
||||
|
||||
void AuFile::on_mouse_left_up(wxMouseEvent &evt)
|
||||
|
||||
@@ -171,16 +171,14 @@ void ConfigManipulation::check_adaptive_pressure_advance_model(DynamicPrintConfi
|
||||
return;
|
||||
|
||||
const auto* model = config->option<ConfigOptionStrings>("adaptive_pressure_advance_model");
|
||||
if (model == nullptr)
|
||||
if (model == nullptr || model->values.empty())
|
||||
return;
|
||||
|
||||
// Each extruder variant holds its own model.
|
||||
std::string error;
|
||||
for (const std::string& variant_model : model->values) {
|
||||
error = AdaptivePAProcessor::validate_adaptive_pa_model(variant_model);
|
||||
if (!error.empty())
|
||||
break;
|
||||
}
|
||||
std::string raw_model;
|
||||
for (const std::string& chunk : model->values)
|
||||
raw_model += chunk;
|
||||
|
||||
std::string error = AdaptivePAProcessor::validate_adaptive_pa_model(raw_model);
|
||||
if (!error.empty()) {
|
||||
wxString msg_text = _L("Adaptive Pressure Advance model validation failed:\n");
|
||||
msg_text += from_u8(error);
|
||||
|
||||
@@ -14,9 +14,6 @@
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/any.hpp>
|
||||
|
||||
#include <wx/filename.h>
|
||||
#include <wx/filesys.h>
|
||||
|
||||
#if __APPLE__
|
||||
#import <IOKit/pwr_mgt/IOPMLib.h>
|
||||
#elif _WIN32
|
||||
@@ -30,13 +27,11 @@
|
||||
|
||||
#include "AboutDialog.hpp"
|
||||
#include "MsgDialog.hpp"
|
||||
#include "Plater.hpp"
|
||||
#include "format.hpp"
|
||||
|
||||
#include "WebUserLoginDialog.hpp"
|
||||
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -536,11 +531,6 @@ boost::filesystem::path into_path(const wxString &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()
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
} }
|
||||
|
||||
@@ -76,9 +76,6 @@ std::string into_u8(const wxString &str);
|
||||
wxString from_path(const boost::filesystem::path &path);
|
||||
// boost path from wxString
|
||||
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
|
||||
extern void about();
|
||||
@@ -86,9 +83,6 @@ extern void about();
|
||||
extern void desktop_open_datadir_folder();
|
||||
// Ask the destop to open one folder
|
||||
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 Slic3r
|
||||
|
||||
|
||||
+21
-33
@@ -85,6 +85,7 @@
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/I18N.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/InstanceLock.hpp"
|
||||
#include "libslic3r/Thread.hpp"
|
||||
#include "libslic3r/miniz_extension.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
@@ -1512,33 +1513,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());
|
||||
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;
|
||||
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 {
|
||||
boost::filesystem::create_directories(dest_path.parent_path());
|
||||
// symlink_status so that an existing symlink, dangling or not, is replaced rather than written through.
|
||||
if (fs::exists(fs::symlink_status(dest_path))) {
|
||||
std::string dest_zip_file = encode_path(dest_path.string().c_str());
|
||||
try {
|
||||
if (fs::exists(dest_path)) {
|
||||
boost::system::error_code ec;
|
||||
fs::remove(dest_path, ec);
|
||||
if (ec) {
|
||||
@@ -1566,8 +1545,9 @@ int GUI_App::install_plugin(std::string name, std::string package_name, InstallP
|
||||
}
|
||||
mz_bool res = 0;
|
||||
#ifndef WIN32
|
||||
if (is_link) {
|
||||
res = 1;
|
||||
if (S_ISLNK(stat.m_external_attr >> 16)) {
|
||||
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 {
|
||||
boost::filesystem::create_symlink(link, dest_path);
|
||||
} catch (const std::exception &e) {
|
||||
@@ -3570,7 +3550,7 @@ bool GUI_App::on_init_inner()
|
||||
update_publish_status();
|
||||
}
|
||||
|
||||
if (m_post_initialized && app_config->dirty())
|
||||
if (m_post_initialized && app_config->dirty() && app_config->save_due())
|
||||
app_config->save();
|
||||
|
||||
});
|
||||
@@ -7664,8 +7644,11 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg)
|
||||
|
||||
// Delete the bundle folder and bundle
|
||||
fs::path bundle_folder = fs::path(bundle.path.c_str()).parent_path();
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
InstanceLock instance_lock(user_presets_lock_path());
|
||||
boost::filesystem::remove_all(bundle_folder, ec);
|
||||
}
|
||||
|
||||
preset_bundle->bundles.WriteLock();
|
||||
preset_bundle->bundles.m_bundles.erase(bundle.id);
|
||||
@@ -8952,6 +8935,7 @@ void GUI_App::preset_deleted_from_cloud(std::string setting_id)
|
||||
|
||||
// Delete the .info file after cloud deletion is confirmed
|
||||
if (!preset_file_path.empty() && fs::exists(fs::path(preset_file_path))) {
|
||||
InstanceLock instance_lock(user_presets_lock_path());
|
||||
boost::nowide::remove(preset_file_path.c_str());
|
||||
BOOST_LOG_TRIVIAL(info) << "Deleted .info file after cloud confirmation: " << preset_file_path;
|
||||
}
|
||||
@@ -9014,17 +8998,21 @@ void GUI_App::scan_orphaned_info_files()
|
||||
fs::path preset_file = info_file;
|
||||
preset_file.replace_extension(".json");
|
||||
|
||||
// If .json doesn't exist, .info is orphaned
|
||||
if (!fs::exists(preset_file)) {
|
||||
// Extract setting_id from .info file
|
||||
std::string setting_id = extract_setting_id_from_info(info_file.string());
|
||||
// If .json doesn't exist, .info is orphaned. Read under the lock, so a
|
||||
// remove_files() in another instance is seen whole or not at all; the
|
||||
// delete queue's own mutex is taken after the lock is released.
|
||||
std::string setting_id;
|
||||
{
|
||||
InstanceLock instance_lock(user_presets_lock_path());
|
||||
if (!fs::exists(preset_file))
|
||||
setting_id = extract_setting_id_from_info(info_file.string());
|
||||
}
|
||||
if (!setting_id.empty()) {
|
||||
// Add to need_delete_presets
|
||||
delete_preset_from_cloud(setting_id, info_file.string());
|
||||
BOOST_LOG_TRIVIAL(info) << "Found orphaned .info file on startup: " << info_file.string();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ec)
|
||||
BOOST_LOG_TRIVIAL(warning) << "scan_orphaned_info_files: failed to scan " << type_dir.string() << ": " << ec.message();
|
||||
}
|
||||
|
||||
@@ -98,7 +98,10 @@ void MarkdownTip::LoadStyle()
|
||||
ph /= "tooltip/styled.html";
|
||||
_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();
|
||||
}
|
||||
|
||||
|
||||
+16
-28
@@ -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)
|
||||
{
|
||||
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 apikey;
|
||||
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 {
|
||||
const auto host_type = cfg.option<ConfigOptionEnum<PrintHostType>>("host_type")->value;
|
||||
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);
|
||||
}
|
||||
|
||||
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("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);
|
||||
}
|
||||
|
||||
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("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);
|
||||
}
|
||||
|
||||
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("input_shaping_emit", new ConfigOptionBool{true});
|
||||
|
||||
@@ -57,7 +57,9 @@ PrivacyUpdateDialog::PrivacyUpdateDialog(wxWindow* parent, wxWindowID id, const
|
||||
|
||||
fs::path ph(resources_dir());
|
||||
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_sizer_right->Add(m_vebview_release_note, 0, wxEXPAND | wxRIGHT | wxLEFT, FromDIP(15));
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
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();
|
||||
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);
|
||||
|
||||
@@ -293,8 +293,10 @@ void ProjectPanel::OnScriptMessage(wxWebViewEvent& evt)
|
||||
if (!accessory_path.empty()) {
|
||||
std::string decode_path = wxGetApp().url_decode(accessory_path.ToStdString());
|
||||
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") {
|
||||
|
||||
@@ -510,7 +510,7 @@ void UpdateVersionDialog::update_version_info(wxString release_note, wxString ve
|
||||
out_buf->append(text, size);
|
||||
}, (void*) &html_source, MD_DIALECT_GITHUB | MD_FLAG_STRIKETHROUGH | MD_FLAG_WIKILINKS, 0);
|
||||
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());
|
||||
SetMaxSize(GetSize());
|
||||
|
||||
+19
-20
@@ -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("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("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");
|
||||
|
||||
// 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_overhangs", "material_flow_ratio_and_pressure_advance#enable-adaptive-pressure-advance-for-overhangs-beta", 0);
|
||||
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", "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");
|
||||
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.is_code = true;
|
||||
option.opt.height = 15;
|
||||
@@ -4432,7 +4432,7 @@ void TabFilament::build()
|
||||
DynamicPrintConfig& filament_config = m_preset_bundle->filaments.get_edited_preset().config;
|
||||
|
||||
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);
|
||||
|
||||
on_value_change(opt_key, value);
|
||||
@@ -4829,11 +4829,8 @@ void TabFilament::toggle_options()
|
||||
}
|
||||
if (m_active_page->title() == L("Filament"))
|
||||
{
|
||||
const int selection = m_variant_combo ? m_variant_combo->GetSelection() : 0;
|
||||
const unsigned int variant_idx = (unsigned int) std::max(selection, 0);
|
||||
|
||||
bool pa = m_config->opt_bool("enable_pressure_advance", variant_idx);
|
||||
toggle_option("pressure_advance", pa, 0);
|
||||
bool pa = m_config->opt_bool("enable_pressure_advance", 0);
|
||||
toggle_option("pressure_advance", pa);
|
||||
|
||||
//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;
|
||||
@@ -4861,12 +4858,12 @@ void TabFilament::toggle_options()
|
||||
// Orca: adaptive pressure advance and calibration model
|
||||
// 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
|
||||
toggle_option("adaptive_pressure_advance", pa, 0);
|
||||
toggle_option("adaptive_pressure_advance_overhangs", pa, 0);
|
||||
bool has_adaptive_pa = m_config->opt_bool("adaptive_pressure_advance", variant_idx);
|
||||
toggle_line("adaptive_pressure_advance_overhangs", has_adaptive_pa && pa, 0);
|
||||
toggle_line("adaptive_pressure_advance_model", has_adaptive_pa && pa, 0);
|
||||
toggle_line("adaptive_pressure_advance_bridges", has_adaptive_pa && pa, 0);
|
||||
toggle_option("adaptive_pressure_advance", pa);
|
||||
toggle_option("adaptive_pressure_advance_overhangs", pa);
|
||||
bool has_adaptive_pa = m_config->opt_bool("adaptive_pressure_advance", 0);
|
||||
toggle_line("adaptive_pressure_advance_overhangs", has_adaptive_pa && pa);
|
||||
toggle_line("adaptive_pressure_advance_model", has_adaptive_pa && pa);
|
||||
toggle_line("adaptive_pressure_advance_bridges", has_adaptive_pa && pa);
|
||||
|
||||
bool is_pellet_printer = printer_cfg.opt_bool("pellet_modded_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"));
|
||||
|
||||
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);
|
||||
bool enable_fit = volumetric_speed_cos != "0 0 0 0 0 0";
|
||||
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 = 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("auxiliary_fan", "printer_basic_information_accessory#auxiliary-part-cooling-fan");
|
||||
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);
|
||||
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", "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)
|
||||
{
|
||||
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;
|
||||
const double new_nd = boost::any_cast<double>(value);
|
||||
|
||||
@@ -31,15 +31,14 @@ int UserManager::parse_json(std::string payload)
|
||||
{
|
||||
bool restored_json = false;
|
||||
json j;
|
||||
|
||||
//bind/unbind
|
||||
|
||||
try {
|
||||
json j_pre = json::parse(payload);
|
||||
if (j_pre.empty()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
//bind/unbind
|
||||
|
||||
try {
|
||||
if (j_pre.contains("bind")) {
|
||||
if (j_pre["bind"].contains("command")) {
|
||||
|
||||
|
||||
@@ -38,7 +38,9 @@ DownPluginFrame::DownPluginFrame(GUI_App *pGUI) : wxDialog((wxWindow *) (pGUI->m
|
||||
|
||||
// set the frame icon
|
||||
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
|
||||
m_browser = WebView::CreateWebView(this, TargetUrl);
|
||||
|
||||
@@ -218,38 +218,37 @@ wxString GuideFrame::SetStartPage(GuidePage startpage, bool load)
|
||||
m_page = 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() );
|
||||
const wxString guide_url = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/guide/0/index.html");
|
||||
wxString TargetUrl = guide_url + "?target=1";
|
||||
wxString TargetUrl = from_u8( (boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=1").make_preferred().string() );
|
||||
//wxLogMessage("GUIDE: webpage_2 %s", TargetUrl.mb_str());
|
||||
|
||||
if (startpage == BBL_WELCOME){
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
SetTitle(_L("Setup Wizard"));
|
||||
|
||||
int nSize = m_ProfileJson["model"].size();
|
||||
|
||||
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
|
||||
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) {
|
||||
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) {
|
||||
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 {
|
||||
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();
|
||||
@@ -257,6 +256,7 @@ wxString GuideFrame::SetStartPage(GuidePage startpage, bool load)
|
||||
if (strlang != "")
|
||||
TargetUrl = wxString::Format("%s&lang=%s", w2s(TargetUrl), strlang);
|
||||
|
||||
TargetUrl = "file://" + TargetUrl;
|
||||
if (load)
|
||||
load_url(TargetUrl);
|
||||
|
||||
@@ -1284,7 +1284,7 @@ bool GuideFrame::BuildProfileJson(const PresetBundle& bundle, bool require_all_r
|
||||
entry["vendor"] = vp.id;
|
||||
entry["nozzle_diameter"] = nozzle_str;
|
||||
entry["materials"] = materials_str;
|
||||
entry["cover"] = into_u8(file_url_from_path(cover_path));
|
||||
entry["cover"] = cover_path.string();
|
||||
entry["nozzle_selected"] = "";
|
||||
entry["sub_path"] = "";
|
||||
m_ProfileJson["model"].push_back(entry);
|
||||
@@ -1405,23 +1405,26 @@ bool GuideFrame::BuildProfileDataFromVendors()
|
||||
// is served from the shipped profiles. Each is stamped by name and
|
||||
// version alone: a profile change requires a version bump, so those two
|
||||
// determine content wherever the vendor's copy sits.
|
||||
struct VendorSource { std::string name; boost::filesystem::path dir; std::string version; };
|
||||
std::vector<VendorSource> ordered;
|
||||
auto add_vendor = [&ordered](const std::string& name, const boost::filesystem::path& dir) {
|
||||
std::vector<PresetBundle::VendorSource> ordered;
|
||||
json stamps = json::array();
|
||||
auto add_vendor = [&ordered, &stamps](const std::string& name, const boost::filesystem::path& dir) {
|
||||
// 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
|
||||
// it), the cache's own stamp where the cache is the whole vendor.
|
||||
// A profile without a version (blacklist.json) carries no presets
|
||||
// and is passed over.
|
||||
const boost::filesystem::path profile = dir / (name + ".json");
|
||||
std::string version;
|
||||
if (boost::filesystem::exists(profile)) {
|
||||
const Semver v = get_version_from_json(profile.string());
|
||||
if (v.valid())
|
||||
ordered.push_back({name, dir, v.to_string()});
|
||||
if (! v.valid())
|
||||
return;
|
||||
version = v.to_string();
|
||||
} else {
|
||||
ordered.push_back({name, dir,
|
||||
VendorCacheFile::peek_version((dir / (name + ".opc")).string(), name)});
|
||||
version = 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);
|
||||
if (auto it = vendor_sources.find(filament_library); it != vendor_sources.end())
|
||||
@@ -1431,9 +1434,6 @@ bool GuideFrame::BuildProfileDataFromVendors()
|
||||
add_vendor(name, dir);
|
||||
if (ordered.empty())
|
||||
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
|
||||
// the derived JSON is cached whole: a fresh cache makes an open one
|
||||
@@ -1461,34 +1461,24 @@ bool GuideFrame::BuildProfileDataFromVendors()
|
||||
}
|
||||
|
||||
// Each vendor comes from its preset cache where one covers it, which is
|
||||
// what makes this worth doing instead of the scan below; loading into a
|
||||
// bundle per vendor keeps the install order the startup path has.
|
||||
// what makes this worth doing instead of the scan below.
|
||||
PresetBundle bundle;
|
||||
auto load_vendor = [](PresetBundle& into, const std::string& vendor,
|
||||
const boost::filesystem::path& dir, const PresetBundle* base) {
|
||||
into.load_vendor_configs_from_json(dir.string(), vendor, PresetBundle::LoadSystem,
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent, base);
|
||||
};
|
||||
for (const VendorSource& v : ordered) {
|
||||
if (*m_cancel_token)
|
||||
return false; // as in the scan below: a vendor without a cache is parsed, and that takes time
|
||||
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())
|
||||
std::vector<std::string> failed;
|
||||
const std::string errors = bundle.load_vendors(ordered, ForwardCompatibilitySubstitutionRule::EnableSilent,
|
||||
/*allow_cache=*/true, m_cancel_token.get(), &failed).second;
|
||||
if (*m_cancel_token || bundle.vendors.empty())
|
||||
return false;
|
||||
if (! errors.empty())
|
||||
BOOST_LOG_TRIVIAL(warning) << "GuideFrame: loading the vendors reported: " << errors;
|
||||
// A vendor that failed to load sends this open to the scan below, which lists
|
||||
// what it can read of every vendor.
|
||||
if (! failed.empty())
|
||||
return false;
|
||||
if (! BuildProfileJson(bundle, /*require_all_resource_vendors=*/false))
|
||||
return false;
|
||||
|
||||
// Written through a temp file and moved into place, as the preset caches
|
||||
// are: half a cache must never be readable, and the PID suffix keeps two
|
||||
// instances from interleaving on one temp file.
|
||||
const std::string tmp_path = cache_file.string() + "." + std::to_string(get_current_pid()) + ".tmp";
|
||||
// are: half a cache must never be readable.
|
||||
try {
|
||||
json out;
|
||||
out["format"] = 1;
|
||||
@@ -1497,18 +1487,9 @@ bool GuideFrame::BuildProfileDataFromVendors()
|
||||
for (const char* key : { "model", "machine", "filament", "process" })
|
||||
profile[key] = m_ProfileJson[key];
|
||||
boost::filesystem::create_directories(cache_file.parent_path());
|
||||
{
|
||||
boost::nowide::ofstream ofs(tmp_path, std::ios::binary | std::ios::trunc);
|
||||
ofs << out.dump(-1, ' ', false, json::error_handler_t::ignore);
|
||||
ofs.close();
|
||||
if (! ofs.good())
|
||||
throw std::runtime_error("write failed");
|
||||
}
|
||||
if (const std::error_code ec = rename_file(tmp_path, cache_file.string()))
|
||||
if (const std::error_code ec = write_file_atomically(cache_file.string(), out.dump(-1, ' ', false, json::error_handler_t::ignore), /*binary=*/true))
|
||||
throw std::runtime_error(ec.message());
|
||||
} catch (const std::exception& e) {
|
||||
boost::system::error_code rm;
|
||||
boost::filesystem::remove(tmp_path, rm);
|
||||
BOOST_LOG_TRIVIAL(warning) << "GuideFrame: could not write the profile data cache: " << e.what();
|
||||
}
|
||||
return true;
|
||||
@@ -1732,7 +1713,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)
|
||||
.make_preferred();
|
||||
}
|
||||
OneModel["cover"] = into_u8(file_url_from_path(cover_path));
|
||||
OneModel["cover"] = cover_path.string();
|
||||
|
||||
OneModel["nozzle_selected"] = "";
|
||||
|
||||
|
||||
@@ -599,7 +599,7 @@ void ZUserLogin::OnScriptResponseMessage(wxCommandEvent &WXUNUSED(evt))
|
||||
|
||||
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);
|
||||
|
||||
return true;
|
||||
|
||||
@@ -36,10 +36,10 @@ namespace GUI {
|
||||
WebViewPanel::WebViewPanel(wxWindow *parent)
|
||||
: 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();
|
||||
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);
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "MultiNozzleSync.hpp"
|
||||
|
||||
#include "../GUI.hpp"
|
||||
#include "../GUI_App.hpp"
|
||||
#include "../I18N.hpp"
|
||||
#include "../Plater.hpp"
|
||||
@@ -22,6 +21,8 @@
|
||||
#include <set>
|
||||
|
||||
#include <wx/choice.h>
|
||||
#include <wx/filename.h>
|
||||
#include <wx/filesys.h>
|
||||
#include <wx/sizer.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->EnableContextMenu(false);
|
||||
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);
|
||||
|
||||
auto sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
@@ -48,19 +48,20 @@ constexpr char ORCA_BRIDGE_JS[] = R"JS(
|
||||
|
||||
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()
|
||||
{
|
||||
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)
|
||||
{
|
||||
// The web view reports the URL it parsed, which may escape the resources path differently
|
||||
// from content_base_url().
|
||||
return wxURI::Unescape(url.BeforeFirst('#')) == wxURI::Unescape(content_base_url());
|
||||
// The web view reports the URL it parsed, which escapes anything the resources path holds
|
||||
// (a space, a non-ASCII character), while content_base_url() is the raw path.
|
||||
return wxURI::Unescape(url.BeforeFirst('#')) == content_base_url();
|
||||
}
|
||||
|
||||
const char* orca_bridge_script() { return ORCA_BRIDGE_JS; }
|
||||
|
||||
@@ -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 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()) {
|
||||
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)
|
||||
|
||||
@@ -464,7 +464,7 @@ WipingDialog::WipingDialog(wxWindow* parent, const int max_flush_volume) :
|
||||
wxString filepath_str = from_path(filepath);
|
||||
wxFileName fn(filepath_str);
|
||||
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();
|
||||
m_webview->LoadURL(url);
|
||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__<< "Successfully loaded url: " << url.ToStdString();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <system_error>
|
||||
#include <exception>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
@@ -580,9 +581,10 @@ bool C3DPrinterOS::save_api_session(const std::string &session, const std::strin
|
||||
j.put("session", session);
|
||||
j.put("email", email);
|
||||
try {
|
||||
auto temp_path = m_api_session_file_path + ".tmp";
|
||||
pt::write_json(temp_path, j);
|
||||
boost::filesystem::rename(temp_path, m_api_session_file_path);
|
||||
std::ostringstream json;
|
||||
pt::write_json(json, j);
|
||||
if (const std::error_code ec = write_file_atomically(m_api_session_file_path, json.str()))
|
||||
throw std::system_error(ec);
|
||||
} catch (const std::exception &err) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": failed to write json to file. Path = "
|
||||
<< m_api_session_file_path
|
||||
|
||||
@@ -274,4 +274,13 @@ bool Duet::start_print(wxString &msg, const std::string &filename, ConnectionTyp
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ private:
|
||||
ConnectionType connect(wxString &msg) const;
|
||||
void disconnect(ConnectionType connectionType) 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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -146,6 +146,15 @@ bool ESP3D::start_print(wxString& msg, const std::string& filename) const
|
||||
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
|
||||
std::string ESP3D::get_short_name(const std::string& filename) const
|
||||
{
|
||||
|
||||
@@ -33,6 +33,7 @@ private:
|
||||
std::string m_console_port;
|
||||
|
||||
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 format_command(const std::string& path, const std::string& arg, const std::string& val) const;
|
||||
};
|
||||
|
||||
@@ -340,7 +340,9 @@ namespace Slic3r {
|
||||
if (classify_printer_model(config->opt_string("printer_model")) != ElegooPrinterType::CC2)
|
||||
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 host_ip = Http::get_host_header_value(host);
|
||||
|
||||
|
||||
@@ -510,22 +510,12 @@ bool Flashforge::fetch_material_slots(std::vector<FlashforgeMaterialSlot>& slots
|
||||
if (!request_local_api_json("detail", json{{"serialNumber", m_serial_number}, {"checkCode", m_check_code}}.dump(), body, msg))
|
||||
return false;
|
||||
|
||||
if (!parse_material_slots(body, slots, supports_material_station)) {
|
||||
const auto parsed = json::parse(body, nullptr, false, true);
|
||||
if (parsed.is_discarded()) {
|
||||
msg = _(L("Flashforge returned an invalid JSON response."));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Flashforge::parse_material_slots(const std::string& body, std::vector<FlashforgeMaterialSlot>& slots, bool* supports_material_station)
|
||||
{
|
||||
slots.clear();
|
||||
|
||||
const auto parsed = json::parse(body, nullptr, false, true);
|
||||
if (parsed.is_discarded())
|
||||
return false;
|
||||
|
||||
const auto& detail = parsed.contains("detail") ? parsed["detail"] : parsed;
|
||||
const auto& station = detail.contains("matlStationInfo") ? detail["matlStationInfo"] :
|
||||
detail.contains("MatlStationInfo") ? detail["MatlStationInfo"] : json();
|
||||
@@ -552,21 +542,12 @@ bool Flashforge::parse_material_slots(const std::string& body, std::vector<Flash
|
||||
if (supports_material_station != nullptr)
|
||||
*supports_material_station = reports_material_station;
|
||||
|
||||
// Fields are read leniently: firmware may send numbers as strings or flags as numbers.
|
||||
for (const auto& slot : slot_infos) {
|
||||
if (!slot.is_object())
|
||||
continue;
|
||||
FlashforgeMaterialSlot info;
|
||||
info.slot_id = static_cast<int>(slots.size()) + 1;
|
||||
if (const auto it = slot.find("slotId"); it != slot.end())
|
||||
try_parse_json_int(*it, info.slot_id);
|
||||
int has_filament = 0;
|
||||
if (const auto it = slot.find("hasFilament"); it != slot.end() && try_parse_json_int(*it, has_filament))
|
||||
info.has_filament = has_filament != 0;
|
||||
if (const auto it = slot.find("materialName"); it != slot.end() && it->is_string())
|
||||
info.material_name = it->get<std::string>();
|
||||
if (const auto it = slot.find("materialColor"); it != slot.end() && it->is_string())
|
||||
info.material_color = it->get<std::string>();
|
||||
info.slot_id = slot.value("slotId", static_cast<int>(slots.size()) + 1);
|
||||
info.has_filament = slot.value("hasFilament", false);
|
||||
info.material_name = slot.value("materialName", std::string());
|
||||
info.material_color = slot.value("materialColor", std::string());
|
||||
slots.emplace_back(std::move(info));
|
||||
}
|
||||
|
||||
@@ -689,4 +670,13 @@ std::string Flashforge::extract_host_name() const
|
||||
return out;
|
||||
}
|
||||
|
||||
int Flashforge::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);
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -45,8 +45,6 @@ public:
|
||||
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
|
||||
std::string get_host() const override { return m_host; }
|
||||
bool fetch_material_slots(std::vector<FlashforgeMaterialSlot>& slots, bool* supports_material_station, wxString& msg) const;
|
||||
// Parses a local API "detail" reply. Returns false when the body is not valid JSON.
|
||||
static bool parse_material_slots(const std::string& body, std::vector<FlashforgeMaterialSlot>& slots, bool* supports_material_station);
|
||||
static bool discover_printers(std::vector<FlashforgeDiscoveredPrinter>& printers, wxString& msg, int timeout_ms = 10000, int idle_timeout_ms = 1500, int max_retries = 3);
|
||||
|
||||
private:
|
||||
@@ -70,6 +68,7 @@ private:
|
||||
bool request_local_api_json(const std::string& path, const std::string& body, std::string& response_body, wxString& error_msg) const;
|
||||
std::string make_http_url(const std::string& path) const;
|
||||
std::string extract_host_name() const;
|
||||
int get_err_code_from_body(const std::string &body) const;
|
||||
bool connect(wxString& msg) const;
|
||||
bool start_print(wxString& msg, const std::string& filename) const;
|
||||
};
|
||||
|
||||
@@ -141,4 +141,13 @@ bool MKS::start_print(wxString& msg, const std::string& filename) const
|
||||
return ret;
|
||||
}
|
||||
|
||||
int MKS::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);
|
||||
}
|
||||
|
||||
} // Slic3r
|
||||
|
||||
@@ -34,6 +34,7 @@ private:
|
||||
|
||||
std::string get_upload_url(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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -1475,15 +1475,8 @@ void OrcaCloudServiceAgent::save_sync_state()
|
||||
if (sync_state_path.empty())
|
||||
return;
|
||||
|
||||
try {
|
||||
std::string tmp_path = sync_state_path + ".tmp";
|
||||
std::ofstream ofs(tmp_path, std::ios::out | std::ios::trunc);
|
||||
if (ofs.good()) {
|
||||
ofs << std::to_string(sync_state.last_sync_timestamp);
|
||||
ofs.close();
|
||||
boost::filesystem::rename(tmp_path, sync_state_path);
|
||||
}
|
||||
} catch (...) {}
|
||||
if (const std::error_code ec = write_file_atomically(sync_state_path, std::to_string(sync_state.last_sync_timestamp)))
|
||||
BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: failed to save the sync state: " << ec.message();
|
||||
}
|
||||
|
||||
void OrcaCloudServiceAgent::clear_sync_state()
|
||||
@@ -1572,22 +1565,10 @@ void OrcaCloudServiceAgent::persist_user_secret(const std::string& secret)
|
||||
wxFileName::Mkdir(path.GetPath(), wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL);
|
||||
}
|
||||
|
||||
const std::string tmp_path = secret_fallback_path + ".tmp";
|
||||
std::ofstream ofs(tmp_path, std::ios::out | std::ios::trunc | std::ios::binary);
|
||||
if (ofs.good()) {
|
||||
ofs << signed_payload;
|
||||
ofs.flush();
|
||||
ofs.close();
|
||||
|
||||
if (wxRenameFile(wxString::FromUTF8(tmp_path.c_str()), wxString::FromUTF8(secret_fallback_path.c_str()), true)) {
|
||||
if (const std::error_code ec = write_file_atomically(secret_fallback_path, signed_payload, /*binary=*/true))
|
||||
BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: cannot write user secret file " << secret_fallback_path << ": " << ec.message();
|
||||
else
|
||||
stored = true;
|
||||
} else {
|
||||
wxRemoveFile(wxString::FromUTF8(tmp_path.c_str()));
|
||||
BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: failed to atomically replace user secret file";
|
||||
}
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: cannot open user secret file for write - " << secret_fallback_path;
|
||||
}
|
||||
} else {
|
||||
// Use wxSecretStore only
|
||||
wxSecretStore store = wxSecretStore::GetDefault();
|
||||
|
||||
@@ -339,8 +339,62 @@ bool PresetUpdater::priv::get_file(const std::string &url, const fs::path &targe
|
||||
//BBS: refine preset update logic
|
||||
bool PresetUpdater::priv::extract_file(const fs::path &source_path, const fs::path &dest_path)
|
||||
{
|
||||
const std::string parent_path = (!dest_path.empty() ? dest_path : source_path.parent_path()).string();
|
||||
return extract_archive_confined(source_path.string(), parent_path);
|
||||
bool res = true;
|
||||
std::string file_path = source_path.string();
|
||||
std::string parent_path = (!dest_path.empty() ? dest_path : source_path.parent_path()).string();
|
||||
mz_zip_archive archive;
|
||||
mz_zip_zero_struct(&archive);
|
||||
|
||||
if (!open_zip_reader(&archive, file_path))
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << "Unable to open zip reader for "<<file_path;
|
||||
return false;
|
||||
}
|
||||
|
||||
mz_uint num_entries = mz_zip_reader_get_num_files(&archive);
|
||||
|
||||
mz_zip_archive_file_stat stat;
|
||||
// we first loop the entries to read from the archive the .amf file only, in order to extract the version from it
|
||||
for (mz_uint i = 0; i < num_entries; ++i)
|
||||
{
|
||||
if (mz_zip_reader_file_stat(&archive, i, &stat))
|
||||
{
|
||||
std::string dest_file = parent_path+"/"+stat.m_filename;
|
||||
if (stat.m_is_directory) {
|
||||
fs::path dest_path(dest_file);
|
||||
if (!fs::exists(dest_path))
|
||||
fs::create_directories(dest_path);
|
||||
continue;
|
||||
}
|
||||
else if (stat.m_uncomp_size == 0) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater]Unzip: invalid size for file "<<stat.m_filename;
|
||||
continue;
|
||||
}
|
||||
try
|
||||
{
|
||||
res = mz_zip_reader_extract_to_file(&archive, stat.m_file_index, dest_file.c_str(), 0);
|
||||
if (!res) {
|
||||
BOOST_LOG_TRIVIAL(error) << "[Orca Updater]extract file "<<stat.m_filename<<" to dest "<<dest_file<<" failed";
|
||||
close_zip_reader(&archive);
|
||||
return res;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]successfully extract file " << stat.m_file_index << " to "<<dest_file;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
// ensure the zip archive is closed and rethrow the exception
|
||||
close_zip_reader(&archive);
|
||||
BOOST_LOG_TRIVIAL(error) << "[Orca Updater]Archive read exception:"<<e.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater]Unzip: read file stat failed";
|
||||
}
|
||||
}
|
||||
close_zip_reader(&archive);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Remove a leftover partial archive for the vendor about to be synchronized.
|
||||
|
||||
@@ -3,13 +3,10 @@
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
#include <exception>
|
||||
#include <sstream>
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
|
||||
#include <wx/string.h>
|
||||
#include <wx/app.h>
|
||||
@@ -175,20 +172,6 @@ std::string moonraker_error_reason(const std::string &body)
|
||||
|
||||
} // namespace
|
||||
|
||||
int PrintHost::get_err_code_from_body(const std::string &body)
|
||||
{
|
||||
boost::property_tree::ptree root;
|
||||
std::istringstream iss(body);
|
||||
try {
|
||||
boost::property_tree::read_json(iss, root);
|
||||
} catch (const std::exception &ex) {
|
||||
BOOST_LOG_TRIVIAL(error) << "PrintHost: response is not valid JSON: " << ex.what();
|
||||
return -1;
|
||||
}
|
||||
|
||||
return root.get<int>("err", 0);
|
||||
}
|
||||
|
||||
wxString PrintHost::format_error(const std::string &body, const std::string &error, unsigned status) const
|
||||
{
|
||||
if (status != 0) {
|
||||
@@ -432,10 +415,12 @@ void PrintHostJobQueue::priv::remove_source()
|
||||
source_to_remove.clear();
|
||||
}
|
||||
|
||||
bool PrintHostJobQueue::upload_job(PrintHostJob &job, PrintHost::ProgressFn progress_fn, PrintHost::ErrorFn error_fn, PrintHost::InfoFn info_fn)
|
||||
void PrintHostJobQueue::priv::perform_job(PrintHostJob the_job)
|
||||
{
|
||||
emit_progress(0); // Indicate the upload is starting
|
||||
|
||||
// Captured before upload_data is moved into upload() below.
|
||||
const std::string upload_filename = job.upload_data.source_path.filename().string();
|
||||
const std::string upload_filename = the_job.upload_data.source_path.filename().string();
|
||||
|
||||
{
|
||||
LifecycleEventContext ctx;
|
||||
@@ -444,37 +429,19 @@ bool PrintHostJobQueue::upload_job(PrintHostJob &job, PrintHost::ProgressFn prog
|
||||
fire_lifecycle_event(LifecycleEvent::UploadStarted, ctx);
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
std::string error;
|
||||
// A throwing upload must not stop the worker, or later jobs would stay queued forever.
|
||||
try {
|
||||
success = job.printhost->upload(std::move(job.upload_data), std::move(progress_fn), error_fn, std::move(info_fn));
|
||||
} catch (const std::exception &e) {
|
||||
error = e.what();
|
||||
error_fn(error);
|
||||
}
|
||||
bool success = the_job.printhost->upload(std::move(the_job.upload_data),
|
||||
[this](Http::Progress progress, bool &cancel) { this->progress_fn(std::move(progress), cancel); },
|
||||
[this](wxString error) { this->error_fn(std::move(error)); },
|
||||
[this](wxString tag, wxString host) { this->info_fn(std::move(tag), std::move(host)); }
|
||||
);
|
||||
|
||||
{
|
||||
LifecycleEventContext ctx;
|
||||
ctx.name = upload_filename;
|
||||
ctx.code = success ? LifecycleEvtCode::Ok : LifecycleEvtCode::Error;
|
||||
ctx.msg = error;
|
||||
fire_lifecycle_event(LifecycleEvent::UploadFinished, ctx);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void PrintHostJobQueue::priv::perform_job(PrintHostJob the_job)
|
||||
{
|
||||
emit_progress(0); // Indicate the upload is starting
|
||||
|
||||
bool success = PrintHostJobQueue::upload_job(the_job,
|
||||
[this](Http::Progress progress, bool &cancel) { this->progress_fn(std::move(progress), cancel); },
|
||||
[this](wxString error) { this->error_fn(std::move(error)); },
|
||||
[this](wxString tag, wxString host) { this->info_fn(std::move(tag), std::move(host)); }
|
||||
);
|
||||
|
||||
if (success) {
|
||||
emit_progress(100);
|
||||
if (the_job.switch_to_device_tab) {
|
||||
|
||||
@@ -87,8 +87,6 @@ public:
|
||||
|
||||
static PrintHost* get_print_host(DynamicPrintConfig *config);
|
||||
static std::string get_print_host_webui(DynamicPrintConfig *config);
|
||||
// Reads the "err" field of a JSON reply, 0 when absent. Returns -1 when the body is not valid JSON.
|
||||
static int get_err_code_from_body(const std::string &body);
|
||||
|
||||
//Support for cloud webui login
|
||||
virtual bool is_cloud() const { return false; }
|
||||
@@ -152,10 +150,6 @@ public:
|
||||
void enqueue(PrintHostJob job);
|
||||
void cancel(size_t id);
|
||||
|
||||
// Uploads the job, firing UploadStarted and a matching UploadFinished. An exception thrown by
|
||||
// the upload is reported through error_fn and makes the upload fail.
|
||||
static bool upload_job(PrintHostJob &job, PrintHost::ProgressFn progress_fn, PrintHost::ErrorFn error_fn, PrintHost::InfoFn info_fn);
|
||||
|
||||
private:
|
||||
struct priv;
|
||||
std::shared_ptr<priv> p;
|
||||
|
||||
@@ -654,4 +654,13 @@ bool UltiMaker::start_print(wxString &msg, const std::string &filename, Connecti
|
||||
return res;
|
||||
}
|
||||
|
||||
int UltiMaker::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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ private:
|
||||
void set_auth(Http& http) const;
|
||||
void disconnect(ConnectionType connectionType) const;
|
||||
bool start_print(wxString &msg, const std::string &filename, ConnectionType connectionType) const;
|
||||
int get_err_code_from_body(const std::string &body) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -249,21 +249,10 @@ bool PluginConfig::save()
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write to a PID-suffixed file and rename it into place, so a crash mid-write cannot truncate an
|
||||
// existing config. Same approach as AppConfig::save().
|
||||
const std::string path_pid = (boost::format("%1%.%2%") % path % get_current_pid()).str();
|
||||
|
||||
boost::nowide::ofstream file;
|
||||
file.open(path_pid, std::ios::out | std::ios::trunc);
|
||||
file << root.dump(1, '\t') << std::endl;
|
||||
file.close();
|
||||
if (file.fail()) {
|
||||
BOOST_LOG_TRIVIAL(error) << "PluginConfig: failed to write " << path_pid << "; keeping the existing config";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (const std::error_code rename_ec = rename_file(path_pid, path)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "PluginConfig: failed to move " << path_pid << " onto " << path << ": " << rename_ec.message();
|
||||
// Written beside the target and moved into place, so a crash mid-write cannot truncate an
|
||||
// existing config.
|
||||
if (const std::error_code ec = write_file_atomically(path, root.dump(1, '\t') + "\n")) {
|
||||
BOOST_LOG_TRIVIAL(error) << "PluginConfig: failed to write " << path << ": " << ec.message() << "; keeping the existing config";
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -768,128 +768,3 @@ TEST_CASE("Each filament prints with its variant of the extruder's variant strin
|
||||
CHECK(gcode.find("; nozzle_temperature = " + resolved + "\n") != std::string::npos);
|
||||
}
|
||||
}
|
||||
|
||||
// An adaptive pressure advance model predicting the same pressure advance at every flow and acceleration.
|
||||
static std::string constant_pressure_advance_model(const std::string &pa)
|
||||
{
|
||||
return pa + ",1,1000\n" + pa + ",500,1000\n" + pa + ",1,100000\n" + pa + ",500,100000";
|
||||
}
|
||||
|
||||
// The pressure advance values a Klipper G-code sets.
|
||||
static std::set<std::string> pressure_advance_values(const std::string &gcode)
|
||||
{
|
||||
const std::string token = "SET_PRESSURE_ADVANCE ADVANCE=";
|
||||
std::set<std::string> values;
|
||||
std::istringstream stream(gcode);
|
||||
for (std::string line; std::getline(stream, line);)
|
||||
if (line.rfind(token, 0) == 0)
|
||||
values.insert(line.substr(token.size(), line.find(';') - token.size()));
|
||||
return values;
|
||||
}
|
||||
|
||||
// Pressure advance, and the adaptive pressure advance model, are tuned per extruder variant like the
|
||||
// other filament variant settings.
|
||||
TEST_CASE("Each filament sets the pressure advance of its extruder variant", "[MultiFilament]")
|
||||
{
|
||||
auto [nozzle_volume_type, filament, pressure_advance, adaptive_pressure_advance] = GENERATE(table<NozzleVolumeType, int, std::string, std::string>({
|
||||
{ nvtStandard, 1, "0.021", "0.012" },
|
||||
{ nvtHighFlow, 1, "0.037", "0.034" },
|
||||
{ nvtHighFlow, 2, "0.043", "0.056" }, // filament 2 defines no High Flow variant
|
||||
}));
|
||||
const bool adaptive = GENERATE(false, true);
|
||||
DYNAMIC_SECTION(get_nozzle_volume_type_string(nozzle_volume_type) << " nozzle, filament " << filament << (adaptive ? ", adaptive" : "")) {
|
||||
DynamicPrintConfig config = multifilament_config(2, {
|
||||
{ "gcode_flavor", "klipper" },
|
||||
{ "extruder_variant_list", "Direct Drive Standard,Direct Drive High Flow" },
|
||||
// filament 1 defines Standard (0.021) and High Flow (0.037), filament 2 Standard (0.043)
|
||||
{ "filament_extruder_variant", "Direct Drive Standard;Direct Drive High Flow;Direct Drive Standard" },
|
||||
{ "filament_self_index", "1,1,2" },
|
||||
{ "enable_pressure_advance", "1,1,1" },
|
||||
{ "pressure_advance", "0.021,0.037,0.043" },
|
||||
{ "adaptive_pressure_advance", adaptive ? "1,1,1" : "0,0,0" },
|
||||
{ "sparse_infill_filament_id", filament },
|
||||
{ "internal_solid_filament_id", filament },
|
||||
{ "top_surface_filament_id", filament },
|
||||
{ "bottom_surface_filament_id", filament },
|
||||
{ "outer_wall_filament_id", filament },
|
||||
{ "inner_wall_filament_id", filament },
|
||||
{ "enable_prime_tower", 0 },
|
||||
{ "skirt_loops", 0 },
|
||||
{ "brim_type", "no_brim" },
|
||||
// custom G-code indexes the per-filament arrays by filament
|
||||
{ "machine_start_gcode", "; start pressure advance {pressure_advance[initial_extruder]}" },
|
||||
});
|
||||
config.option<ConfigOptionStrings>("adaptive_pressure_advance_model")->values = {
|
||||
constant_pressure_advance_model("0.012"), constant_pressure_advance_model("0.034"), constant_pressure_advance_model("0.056") };
|
||||
config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type", true)->values = { nozzle_volume_type };
|
||||
const std::string gcode = slice({ cube(20) }, config);
|
||||
|
||||
// The toolchange sets the variant's pressure advance; with adaptive pressure advance, the
|
||||
// prediction of the variant's model then replaces it.
|
||||
std::set<std::string> expected{ pressure_advance };
|
||||
if (adaptive)
|
||||
expected.insert(adaptive_pressure_advance);
|
||||
CHECK(pressure_advance_values(gcode) == expected);
|
||||
CHECK(gcode.find("; start pressure advance " + pressure_advance + "\n") != std::string::npos);
|
||||
}
|
||||
}
|
||||
|
||||
// On a printer with two extruders, a filament takes the pressure advance of the variant of the extruder
|
||||
// it is mapped to, whichever filament and extruder that is.
|
||||
TEST_CASE("Each filament sets the pressure advance of its extruder variant on a two-extruder printer", "[MultiFilament]")
|
||||
{
|
||||
auto [filament, extruder, pressure_advance, adaptive_pressure_advance] = GENERATE(table<int, int, std::string, std::string>({
|
||||
{ 1, 1, "0.021", "0.012" },
|
||||
{ 1, 2, "0.037", "0.034" },
|
||||
{ 2, 1, "0.043", "0.056" },
|
||||
{ 2, 2, "0.049", "0.078" },
|
||||
}));
|
||||
const bool adaptive = GENERATE(false, true);
|
||||
DYNAMIC_SECTION("filament " << filament << " on extruder " << extruder << (adaptive ? ", adaptive" : "")) {
|
||||
// the other filament goes on the other extruder
|
||||
const std::string filament_map = filament == 1 ? std::to_string(extruder) + "," + std::to_string(3 - extruder) :
|
||||
std::to_string(3 - extruder) + "," + std::to_string(extruder);
|
||||
DynamicPrintConfig config = multifilament_config(2, {
|
||||
{ "gcode_flavor", "klipper" },
|
||||
{ "single_extruder_multi_material", 0 },
|
||||
{ "nozzle_diameter", "0.4,0.4" },
|
||||
{ "extruder_printable_height", "0,0" },
|
||||
// extruder 1 has a Standard nozzle, extruder 2 a High Flow one
|
||||
{ "printer_extruder_id", "1,2" },
|
||||
{ "printer_extruder_variant", "Direct Drive Standard;Direct Drive High Flow" },
|
||||
{ "extruder_variant_list", "Direct Drive Standard;Direct Drive High Flow" },
|
||||
{ "filament_map", filament_map },
|
||||
// both filaments define Standard and High Flow
|
||||
{ "filament_extruder_variant", "Direct Drive Standard;Direct Drive High Flow;Direct Drive Standard;Direct Drive High Flow" },
|
||||
{ "filament_self_index", "1,1,2,2" },
|
||||
{ "enable_pressure_advance", "1,1,1,1" },
|
||||
{ "pressure_advance", "0.021,0.037,0.043,0.049" },
|
||||
{ "adaptive_pressure_advance", adaptive ? "1,1,1,1" : "0,0,0,0" },
|
||||
{ "sparse_infill_filament_id", filament },
|
||||
{ "internal_solid_filament_id", filament },
|
||||
{ "top_surface_filament_id", filament },
|
||||
{ "bottom_surface_filament_id", filament },
|
||||
{ "outer_wall_filament_id", filament },
|
||||
{ "inner_wall_filament_id", filament },
|
||||
{ "enable_prime_tower", 0 },
|
||||
{ "skirt_loops", 0 },
|
||||
{ "brim_type", "no_brim" },
|
||||
// custom G-code indexes the per-filament arrays by filament
|
||||
{ "machine_start_gcode", "; start pressure advance {pressure_advance[initial_extruder]}" },
|
||||
});
|
||||
config.option<ConfigOptionStrings>("adaptive_pressure_advance_model")->values = {
|
||||
constant_pressure_advance_model("0.012"), constant_pressure_advance_model("0.034"),
|
||||
constant_pressure_advance_model("0.056"), constant_pressure_advance_model("0.078") };
|
||||
config.option<ConfigOptionEnumsGeneric>("extruder_type", true)->values = { etDirectDrive, etDirectDrive };
|
||||
config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type", true)->values = { nvtStandard, nvtHighFlow };
|
||||
// keep the mapping above rather than grouping the filaments automatically
|
||||
config.option<ConfigOptionEnum<FilamentMapMode>>("filament_map_mode", true)->value = fmmManual;
|
||||
const std::string gcode = slice({ cube(20) }, config);
|
||||
|
||||
std::set<std::string> expected{ pressure_advance };
|
||||
if (adaptive)
|
||||
expected.insert(adaptive_pressure_advance);
|
||||
CHECK(pressure_advance_values(gcode) == expected);
|
||||
CHECK(gcode.find("; start pressure advance " + pressure_advance + "\n") != std::string::npos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,10 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_clipper_utils.cpp
|
||||
test_config.cpp
|
||||
test_config_variant_expansion.cpp
|
||||
test_locales_utils.cpp
|
||||
test_drc.cpp
|
||||
test_toolordering_nozzle_group.cpp
|
||||
test_parallel_resolve.cpp
|
||||
test_preset_bundle_loading.cpp
|
||||
test_preset_setting_id.cpp
|
||||
test_preset_diff.cpp
|
||||
@@ -44,7 +46,6 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_lay_on_face.cpp
|
||||
test_model.cpp
|
||||
test_utils.cpp
|
||||
test_miniz_extension.cpp
|
||||
test_timeutils.cpp
|
||||
test_voronoi.cpp
|
||||
test_wipe_tower_estimate.cpp
|
||||
@@ -54,6 +55,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_ordering_strategies.cpp
|
||||
# test_png_io.cpp
|
||||
test_indexed_triangle_set.cpp
|
||||
test_instance_lock.cpp
|
||||
../libnest2d/printer_parts.cpp
|
||||
)
|
||||
|
||||
|
||||
@@ -717,15 +717,3 @@ TEST_CASE("get_index_for_extruder scales the variant column by the requested str
|
||||
REQUIRE(col0_stride2 == 0);
|
||||
REQUIRE(col1_stride2 == 2);
|
||||
}
|
||||
|
||||
// A per-variant filament option read with a single value gives it to every filament variant. A project
|
||||
// exported by an older CLI holds a single value for an option no loaded preset defined, such as
|
||||
// filament_ironing_flow.
|
||||
TEST_CASE("A per-variant filament option read with a single value gives it to every filament variant", "[Config]")
|
||||
{
|
||||
// filament 1 defines Standard and High Flow, filament 2 Standard
|
||||
DynamicPrintConfig config;
|
||||
config.option<ConfigOptionInts>("filament_self_index", true)->values = {1, 1, 2};
|
||||
config.load_from_ini_string("pressure_advance = 0.021", ForwardCompatibilitySubstitutionRule::Disable);
|
||||
REQUIRE(config.option<ConfigOptionFloats>("pressure_advance")->values == std::vector<double>({0.021, 0.021, 0.021}));
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user