mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-30 12:21:05 +00:00
Compare commits
18
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
771ed20d97 | ||
|
|
616f1e4336 | ||
|
|
561737f407 | ||
|
|
7d42ad17a4 | ||
|
|
789f848694 | ||
|
|
3a0694dce6 | ||
|
|
f5679ad343 | ||
|
|
2769b12ce7 | ||
|
|
203bc63f35 | ||
|
|
e40030cf81 | ||
|
|
ba468c842d | ||
|
|
dd9b5dc0d4 | ||
|
|
e68694dbaf | ||
|
|
1504bd7153 | ||
|
|
d87fe1b290 | ||
|
|
72cfe71b81 | ||
|
|
50eea48408 | ||
|
|
ef0c656932 |
@@ -189,8 +189,7 @@ the presets to a base is in [shared-bases.md](shared-bases.md#variant-arrays-on-
|
||||
2. Every key in `filament_options_with_variant` ([the full list](#the-four-key-sets)) that reaches the
|
||||
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`, `pressure_advance`,
|
||||
…) 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`, …) 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
|
||||
@@ -455,7 +454,7 @@ no set: the variant-length resize leaves it alone, and only the pair rebuild of
|
||||
last for contrast; it is not a variant set.
|
||||
|
||||
`check` and `fix-variant` read the four sets from `PrintConfig.cpp` on every run, so they follow the
|
||||
engine. The lists below are from the 2026-09-25 checkout; regenerate them from the repository root
|
||||
engine. The lists below are from the 2026-09-29 checkout; regenerate them from the repository root
|
||||
before relying on them (the recipe strips comments, since an initializer can carry a commented-out entry):
|
||||
|
||||
```bash
|
||||
@@ -478,7 +477,7 @@ EOF
|
||||
|
||||
**`print_options_with_variant`**, process, stride 1 (45): `bridge_acceleration`, `bridge_speed`, `default_acceleration`, `default_jerk`, `default_junction_deviation`, `enable_overhang_speed`, `gap_infill_speed`, `infill_jerk`, `initial_layer_acceleration`, `initial_layer_infill_speed`, `initial_layer_jerk`, `initial_layer_speed`, `initial_layer_travel_acceleration`, `initial_layer_travel_jerk`, `initial_layer_travel_speed`, `inner_wall_acceleration`, `inner_wall_jerk`, `inner_wall_speed`, `internal_bridge_speed`, `internal_solid_infill_acceleration`, `internal_solid_infill_speed`, `outer_wall_acceleration`, `outer_wall_jerk`, `outer_wall_speed`, `overhang_1_4_speed`, `overhang_2_4_speed`, `overhang_3_4_speed`, `overhang_4_4_speed`, `print_extruder_id`, `print_extruder_variant`, `slowdown_for_curled_perimeters`, `small_perimeter_speed`, `small_perimeter_threshold`, `sparse_infill_acceleration`, `sparse_infill_speed`, `support_interface_speed`, `support_speed`, `top_solid_infill_flow_ratio`, `top_surface_acceleration`, `top_surface_jerk`, `top_surface_speed`, `travel_acceleration`, `travel_jerk`, `travel_speed`, `travel_speed_z`
|
||||
|
||||
**`filament_options_with_variant`**, filament, stride 1 (48): `activate_air_filtration`, `activate_air_filtration_during_print`, `activate_air_filtration_on_completion`, `complete_print_exhaust_fan_speed`, `during_print_exhaust_fan_speed`, `filament_adaptive_volumetric_speed`, `filament_cooling_before_tower`, `filament_deretraction_speed`, `filament_extruder_variant`, `filament_flow_ratio`, `filament_flush_temp`, `filament_flush_volumetric_speed`, `filament_ironing_flow`, `filament_ironing_inset`, `filament_ironing_spacing`, `filament_ironing_speed`, `filament_long_retractions_when_cut`, `filament_max_volumetric_speed`, `filament_pre_cooling_temperature`, `filament_pre_cooling_temperature_nc`, `filament_preheat_temperature_delta`, `filament_ramming_travel_time`, `filament_ramming_travel_time_nc`, `filament_ramming_volumetric_speed`, `filament_ramming_volumetric_speed_nc`, `filament_retract_after_wipe`, `filament_retract_before_wipe`, `filament_retract_length_nc`, `filament_retract_length_toolchange`, `filament_retract_lift_above`, `filament_retract_lift_below`, `filament_retract_lift_enforce`, `filament_retract_restart_extra`, `filament_retract_restart_extra_toolchange`, `filament_retract_when_changing_layer`, `filament_retraction_distances_when_cut`, `filament_retraction_length`, `filament_retraction_minimum_travel`, `filament_retraction_speed`, `filament_wipe`, `filament_wipe_distance`, `filament_z_hop`, `filament_z_hop_types`, `long_retractions_when_ec`, `nozzle_temperature`, `nozzle_temperature_initial_layer`, `retraction_distances_when_ec`, `volumetric_speed_coefficients`
|
||||
**`filament_options_with_variant`**, filament, stride 1 (54): `activate_air_filtration`, `activate_air_filtration_during_print`, `activate_air_filtration_on_completion`, `adaptive_pressure_advance`, `adaptive_pressure_advance_bridges`, `adaptive_pressure_advance_model`, `adaptive_pressure_advance_overhangs`, `complete_print_exhaust_fan_speed`, `during_print_exhaust_fan_speed`, `enable_pressure_advance`, `filament_adaptive_volumetric_speed`, `filament_cooling_before_tower`, `filament_deretraction_speed`, `filament_extruder_variant`, `filament_flow_ratio`, `filament_flush_temp`, `filament_flush_volumetric_speed`, `filament_ironing_flow`, `filament_ironing_inset`, `filament_ironing_spacing`, `filament_ironing_speed`, `filament_long_retractions_when_cut`, `filament_max_volumetric_speed`, `filament_pre_cooling_temperature`, `filament_pre_cooling_temperature_nc`, `filament_preheat_temperature_delta`, `filament_ramming_travel_time`, `filament_ramming_travel_time_nc`, `filament_ramming_volumetric_speed`, `filament_ramming_volumetric_speed_nc`, `filament_retract_after_wipe`, `filament_retract_before_wipe`, `filament_retract_length_nc`, `filament_retract_length_toolchange`, `filament_retract_lift_above`, `filament_retract_lift_below`, `filament_retract_lift_enforce`, `filament_retract_restart_extra`, `filament_retract_restart_extra_toolchange`, `filament_retract_when_changing_layer`, `filament_retraction_distances_when_cut`, `filament_retraction_length`, `filament_retraction_minimum_travel`, `filament_retraction_speed`, `filament_wipe`, `filament_wipe_distance`, `filament_z_hop`, `filament_z_hop_types`, `long_retractions_when_ec`, `nozzle_temperature`, `nozzle_temperature_initial_layer`, `pressure_advance`, `retraction_distances_when_ec`, `volumetric_speed_coefficients`
|
||||
|
||||
**`printer_extruder_options`**, machine, one value per extruder, not a variant set (8):
|
||||
`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` 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`, pressure advance 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 `pressure_advance` are not in it. Keep every such array at exactly that width, even where the
|
||||
`slow_down_min_speed` and `fan_max_speed` are not in it. Keep every such array at exactly that width, even where the
|
||||
setting does not differ per variant, and measure the High Flow variant rather than copying Standard
|
||||
([extruder-variants.md](extruder-variants.md#filament)).
|
||||
|
||||
|
||||
@@ -146,7 +146,7 @@ jobs:
|
||||
"repos/${{ github.repository }}/actions/workflows/post_merge_profiles.yml/runs" \
|
||||
-f branch="$branch" -f event=workflow_dispatch -f per_page=100)"
|
||||
run_id="$(jq -r --arg marker "[OFL cron $dispatch_id]" \
|
||||
'[.workflow_runs[] | select((.display_title // "") | contains($marker))] \
|
||||
'[.workflow_runs[] | select((.display_title // "") | contains($marker))]
|
||||
| sort_by(.created_at) | last | .id // empty' <<< "$runs_json")"
|
||||
[ -n "$run_id" ] && break
|
||||
sleep 5
|
||||
|
||||
+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.15",
|
||||
"version": "02.04.00.16",
|
||||
"force_update": "0",
|
||||
"description": "Orca Filament Library",
|
||||
"filament_list": [
|
||||
|
||||
@@ -111,6 +111,7 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -316,6 +317,7 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
+2
@@ -111,6 +111,7 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -316,6 +317,7 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
@@ -111,6 +111,7 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -316,6 +317,7 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
+2
@@ -111,6 +111,7 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -316,6 +317,7 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
+2
@@ -111,6 +111,7 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -316,6 +317,7 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
+2
@@ -111,6 +111,7 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -316,6 +317,7 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
@@ -111,6 +111,7 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -316,6 +317,7 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
@@ -111,6 +111,7 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -316,6 +317,7 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
+2
@@ -111,6 +111,7 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -316,6 +317,7 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
+2
@@ -111,6 +111,7 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -316,6 +317,7 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
+2
@@ -111,6 +111,7 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -316,6 +317,7 @@
|
||||
"1"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
+2
@@ -111,6 +111,7 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -316,6 +317,7 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
+2
@@ -111,6 +111,7 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -316,6 +317,7 @@
|
||||
"0"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
@@ -111,6 +111,7 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -316,6 +317,7 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
@@ -111,6 +111,7 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -316,6 +317,7 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
+2
@@ -111,6 +111,7 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -316,6 +317,7 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
+2
@@ -111,6 +111,7 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -316,6 +317,7 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
@@ -111,6 +111,7 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -316,6 +317,7 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
@@ -111,6 +111,7 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -316,6 +317,7 @@
|
||||
"0"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
+2
@@ -111,6 +111,7 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -316,6 +317,7 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
@@ -111,6 +111,7 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -316,6 +317,7 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
+2
@@ -111,6 +111,7 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -316,6 +317,7 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
+2
@@ -111,6 +111,7 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -316,6 +317,7 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
+2
@@ -111,6 +111,7 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -316,6 +317,7 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
+2
@@ -111,6 +111,7 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -316,6 +317,7 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
+2
@@ -111,6 +111,7 @@
|
||||
"1"
|
||||
],
|
||||
"enable_pressure_advance": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
@@ -316,6 +317,7 @@
|
||||
"2"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02",
|
||||
"0.02"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
|
||||
+129
-30
@@ -214,9 +214,9 @@ function ShowModelInfo( pModel )
|
||||
|
||||
SendWXDebugInfo("Model Name: "+sModelName);
|
||||
|
||||
$('#ModelName').html(sModelName);
|
||||
$('#ModelName').text(sModelName);
|
||||
$('#ModelName').attr('title',sModelName);
|
||||
$('#ModelAuthorName').html(sModelAuthor);
|
||||
$('#ModelAuthorName').text(sModelAuthor);
|
||||
|
||||
switch(UploadType)
|
||||
{
|
||||
@@ -268,7 +268,7 @@ function ShowModelInfo( pModel )
|
||||
break;
|
||||
}
|
||||
|
||||
$('#Model_Desc').html( html_decode(sModelDesc) );
|
||||
$('#Model_Desc').empty().append( SanitizeDescHtml( html_decode(sModelDesc) ) );
|
||||
|
||||
let ModelPreviewList=pModel.preview_img;
|
||||
let TotalPreview=ModelPreviewList.length;
|
||||
@@ -281,16 +281,15 @@ function ShowModelInfo( pModel )
|
||||
|
||||
if(TotalPreview>0)
|
||||
{
|
||||
let htmlPreview='';
|
||||
$('#ModelPreviewList').empty();
|
||||
for(let pn=0;pn<TotalPreview;pn++)
|
||||
{
|
||||
//let FTmpPath=decodeURIComponent(ModelPreviewList[pn]);
|
||||
let FTmpPath=ModelPreviewList[pn]['filepath'];
|
||||
|
||||
htmlPreview+='<div class="swiper-slide"><img class="Model_PrevImg" src="'+FTmpPath+'" /></div>';
|
||||
$('#ModelPreviewList').append( $('<div class="swiper-slide"></div>').append( $('<img class="Model_PrevImg" />').attr('src',FTmpPath) ) );
|
||||
}
|
||||
|
||||
$('#ModelPreviewList').html(htmlPreview);
|
||||
$('#Model_Preview_Image').viewer({
|
||||
title: false,
|
||||
fullsreen: false,
|
||||
@@ -410,7 +409,8 @@ function ConstructFileHtml( ID, pItem )
|
||||
{
|
||||
let fTotal=pItem.length;
|
||||
|
||||
let strHtml='';
|
||||
let pBoard=$('#'+ID+' .FileListBoard');
|
||||
pBoard.empty();
|
||||
for( let f=0;f<fTotal;f++ )
|
||||
{
|
||||
let pOne=pItem[f];
|
||||
@@ -443,39 +443,139 @@ function ConstructFileHtml( ID, pItem )
|
||||
ImgPath='img/default.png';
|
||||
}
|
||||
|
||||
//Add html
|
||||
//Add html. File names come from the 3MF, so build the nodes rather than concatenating markup.
|
||||
let pIconImg=$('<img />').attr('src',ImgPath);
|
||||
let pMenu=$('<div class="FileMenu"><img src="img/s.svg" /></div>');
|
||||
if( strClass!='ImageIcon' )
|
||||
{
|
||||
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>';
|
||||
pMenu.on('click', function(){ OnClickOpenFile(tPath); });
|
||||
}
|
||||
else
|
||||
{
|
||||
ImgID++;
|
||||
let TmpImgID="AF"+ImgID;
|
||||
|
||||
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>';
|
||||
}
|
||||
pIconImg.attr('id',TmpImgID);
|
||||
pMenu.on('click', function(){ OnClickOpenImage(TmpImgID); });
|
||||
}
|
||||
|
||||
$('#'+ID+' .FileListBoard').html(strHtml);
|
||||
let pFileItem=$('<div class="FileItem"></div>');
|
||||
pFileItem.append( $('<div></div>').addClass(strClass).append(pIconImg) );
|
||||
pFileItem.append( $('<div class="FileText"></div>').append( $('<div class="FileName"></div>').text(tName).attr('title',tName) ) );
|
||||
pFileItem.append( pMenu );
|
||||
pBoard.append( pFileItem );
|
||||
}
|
||||
|
||||
if( fTotal>0 )
|
||||
$('#'+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==========
|
||||
@@ -483,10 +583,10 @@ function ShowProfilelInfo( pProfile )
|
||||
let sProfileAuthor=decodeURIComponent(pProfile.author);
|
||||
let sProfileDesc=decodeURIComponent(pProfile.description);
|
||||
|
||||
$('#ProfileName').html(sProfileName);
|
||||
$('#ProfileAuthor').html(sProfileAuthor);
|
||||
$('#ProfileName').text(sProfileName);
|
||||
$('#ProfileAuthor').text(sProfileAuthor);
|
||||
|
||||
$('#Profile_Desc').html( html_decode(sProfileDesc) );
|
||||
$('#Profile_Desc').empty().append( SanitizeDescHtml( html_decode(sProfileDesc) ) );
|
||||
|
||||
let ProfilePreviewList=pProfile.preview_img;
|
||||
let TotalPreview=ProfilePreviewList.length;
|
||||
@@ -499,15 +599,14 @@ function ShowProfilelInfo( pProfile )
|
||||
|
||||
if(TotalPreview>0)
|
||||
{
|
||||
let htmlPreview='';
|
||||
$('#ProfilePreviewList').empty();
|
||||
for(let pn=0;pn<TotalPreview;pn++)
|
||||
{
|
||||
let FTmpPath=ProfilePreviewList[pn]['filepath'];
|
||||
|
||||
htmlPreview+='<div class="swiper-slide"><img class="Model_PrevImg" src="'+FTmpPath+'" /></div>';
|
||||
$('#ProfilePreviewList').append( $('<div class="swiper-slide"></div>').append( $('<img class="Model_PrevImg" />').attr('src',FTmpPath) ) );
|
||||
}
|
||||
|
||||
$('#ProfilePreviewList').html(htmlPreview);
|
||||
$('#Profile_Preview_Image').viewer({
|
||||
title: false,
|
||||
fullsreen: false,
|
||||
|
||||
@@ -1913,6 +1913,9 @@ int CLI::run(int argc, char **argv)
|
||||
old_printable_width = static_cast<int>(old_printable_bbox.size().x());
|
||||
old_printable_depth = static_cast<int>(old_printable_bbox.size().y());
|
||||
}
|
||||
// A 3mf can carry an empty project_settings.config - the models in
|
||||
// resources/handy_models do - and opt_float() dereferences without checking.
|
||||
if (config.option<ConfigOptionFloat>("printable_height"))
|
||||
old_printable_height = (int)(config.opt_float("printable_height"));
|
||||
|
||||
if (config.option<ConfigOptionFloat>("extruder_clearance_height_to_rod"))
|
||||
@@ -2589,6 +2592,7 @@ int CLI::run(int argc, char **argv)
|
||||
orig_printable_width = static_cast<int>(orig_printable_bbox.size().x());
|
||||
orig_printable_depth = static_cast<int>(orig_printable_bbox.size().y());
|
||||
}
|
||||
if (config.option<ConfigOptionFloat>("printable_height"))
|
||||
orig_printable_height = (int)(config.opt_float("printable_height"));
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(":%1%, check printable size: old_printable_width=%2%, orig_printable_width=%3%, old_printable_depth=%4%, orig_printable_depth=%5%, old_printable_height=%6%, orig_printable_height=%7%")
|
||||
%__LINE__ %old_printable_width %orig_printable_width %old_printable_depth %orig_printable_depth %old_printable_height %orig_printable_height;
|
||||
@@ -3476,6 +3480,14 @@ 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];
|
||||
@@ -3647,6 +3659,25 @@ int CLI::run(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
//ORCA: a per-variant option the loaded filament does not define keeps the values of the
|
||||
// variants the filament already had, and a variant new to it takes its first one's.
|
||||
const int old_start = old_start_indice[filament_index - 1];
|
||||
std::vector<int> kept_variant_indice = new_variant_indice;
|
||||
for (int &i : kept_variant_indice)
|
||||
if (i < 0)
|
||||
i = old_start;
|
||||
for (const std::string &opt_key : filament_options_with_variant) {
|
||||
if (config.has(opt_key))
|
||||
continue;
|
||||
auto *opt_vec_dst = dynamic_cast<ConfigOptionVectorBase *>(m_print_config.option(opt_key));
|
||||
if (opt_vec_dst == nullptr || opt_vec_dst->size() < size_t(old_start + old_variant_count))
|
||||
continue;
|
||||
// set_with_restore_2() pads its source in place
|
||||
std::unique_ptr<ConfigOption> old_values(opt_vec_dst->clone());
|
||||
opt_vec_dst->set_with_restore_2(static_cast<ConfigOptionVectorBase *>(old_values.get()), kept_variant_indice, old_start,
|
||||
old_variant_count, true);
|
||||
}
|
||||
|
||||
//update the old index
|
||||
if (old_variant_count != new_variant_count)
|
||||
{
|
||||
@@ -4081,6 +4112,9 @@ 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);
|
||||
@@ -4587,6 +4621,7 @@ int CLI::run(int argc, char **argv)
|
||||
BoundingBoxf temp_printable_bbox(temp_printable_area);
|
||||
printer_plate.printable_width = static_cast<int>(temp_printable_bbox.size().x());
|
||||
printer_plate.printable_depth = static_cast<int>(temp_printable_bbox.size().y());
|
||||
if (config.option<ConfigOptionFloat>("printable_height"))
|
||||
printer_plate.printable_height = (int)(config.opt_float("printable_height"));
|
||||
}
|
||||
if (temp_exclude_area.size() >= 4) {
|
||||
|
||||
@@ -511,6 +511,10 @@ void AppConfig::set_defaults()
|
||||
set_bool("enable_high_low_temp_mixed_printing", false);
|
||||
}
|
||||
|
||||
if (get("remember_print_action").empty()) {
|
||||
set_bool("remember_print_action", false);
|
||||
}
|
||||
|
||||
if (get("ignore_ext_filament_in_filament_map").empty()){
|
||||
set_bool("ignore_ext_filament_in_filament_map", false);
|
||||
}
|
||||
|
||||
@@ -355,6 +355,7 @@ set(lisbslic3r_sources
|
||||
Optimize/Optimizer.hpp
|
||||
Orient.cpp
|
||||
Orient.hpp
|
||||
ParallelResolve.hpp
|
||||
ParameterUtils.cpp
|
||||
ParameterUtils.hpp
|
||||
pchheader.cpp
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Geometry.hpp"
|
||||
#include "ShortestPath.hpp"
|
||||
@@ -817,69 +813,6 @@ Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r
|
||||
{ return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::SurfacesProvider(subject), ClipperUtils::SurfacesProvider(clip), do_safety_offset); }
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset)
|
||||
{ return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::SurfacesPtrProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); }
|
||||
|
||||
namespace ClipperUtils {
|
||||
std::vector<ExPolygonsTile> tile_expolygons(const ExPolygons &expolygons, size_t per_tile)
|
||||
{
|
||||
BoundingBox extent;
|
||||
std::vector<BoundingBox> bboxes;
|
||||
bboxes.reserve(expolygons.size());
|
||||
for (const ExPolygon &expoly : expolygons) {
|
||||
bboxes.emplace_back(get_extents(expoly));
|
||||
extent.merge(bboxes.back());
|
||||
}
|
||||
if (! extent.defined)
|
||||
return {};
|
||||
const int tiles = std::clamp(int(std::sqrt(double(expolygons.size()) / double(std::max<size_t>(per_tile, 1)))), 1, 32);
|
||||
const Point size = extent.size();
|
||||
const coord_t tile_w = std::max<coord_t>(1, size.x() / tiles + 1), tile_h = std::max<coord_t>(1, size.y() / tiles + 1);
|
||||
std::vector<ExPolygonsTile> out(size_t(tiles * tiles));
|
||||
for (size_t i = 0; i < expolygons.size(); ++ i) {
|
||||
const Point c = bboxes[i].center();
|
||||
ExPolygonsTile &tile = out[size_t(std::clamp(int((c.y() - extent.min.y()) / tile_h), 0, tiles - 1) * tiles +
|
||||
std::clamp(int((c.x() - extent.min.x()) / tile_w), 0, tiles - 1))];
|
||||
tile.members.emplace_back(i);
|
||||
tile.bbox.merge(bboxes[i]);
|
||||
}
|
||||
out.erase(std::remove_if(out.begin(), out.end(), [](const ExPolygonsTile &tile) { return tile.members.empty(); }), out.end());
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
static Slic3r::ExPolygons clipper_ex_by_piece(ClipperLib::ClipType clipType, const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
|
||||
{
|
||||
// A few dozen subject ExPolygons to a tile, each tile one ClipperLib call with the clip cut to the tile's box.
|
||||
const std::vector<ClipperUtils::ExPolygonsTile> tiles = ClipperUtils::tile_expolygons(subject, 32);
|
||||
std::vector<BoundingBox> clip_bboxes;
|
||||
clip_bboxes.reserve(clip.size());
|
||||
for (const Polygon &polygon : clip)
|
||||
clip_bboxes.emplace_back(get_extents(polygon));
|
||||
|
||||
std::vector<Slic3r::ExPolygons> out_tiles(tiles.size());
|
||||
tbb::parallel_for(size_t(0), tiles.size(), [&](size_t tile_idx) {
|
||||
const ClipperUtils::ExPolygonsTile &tile = tiles[tile_idx];
|
||||
Slic3r::ExPolygons local_subject;
|
||||
local_subject.reserve(tile.members.size());
|
||||
for (size_t i : tile.members)
|
||||
local_subject.emplace_back(subject[i]);
|
||||
// Grown so that the cut edges of the clip stay clear of the subject, also after the safety offset.
|
||||
const BoundingBox bbox = tile.bbox.inflated(SCALED_EPSILON);
|
||||
Polygons local_clip;
|
||||
for (size_t i = 0; i < clip.size(); ++i)
|
||||
if (clip_bboxes[i].overlap(bbox))
|
||||
if (Polygon clipped = ClipperUtils::clip_clipper_polygon_with_subject_bbox(clip[i], bbox); ! clipped.empty())
|
||||
local_clip.emplace_back(std::move(clipped));
|
||||
out_tiles[tile_idx] = _clipper_ex(clipType, ClipperUtils::ExPolygonsProvider(local_subject), ClipperUtils::PolygonsProvider(local_clip), do_safety_offset);
|
||||
});
|
||||
Slic3r::ExPolygons out;
|
||||
for (Slic3r::ExPolygons &out_tile : out_tiles)
|
||||
append(out, std::move(out_tile));
|
||||
return out;
|
||||
}
|
||||
Slic3r::ExPolygons diff_ex_by_piece(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
|
||||
{ return clipper_ex_by_piece(ClipperLib::ctDifference, subject, clip, do_safety_offset); }
|
||||
Slic3r::ExPolygons intersection_ex_by_piece(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
|
||||
{ return clipper_ex_by_piece(ClipperLib::ctIntersection, subject, clip, do_safety_offset); }
|
||||
// May be used to "heal" unusual models (3DLabPrints etc.) by providing fill_type (pftEvenOdd, pftNonZero, pftPositive, pftNegative).
|
||||
Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFillType fill_type)
|
||||
{ return _clipper_ex(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No, fill_type); }
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#define slic3r_ClipperUtils_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "BoundingBox.hpp"
|
||||
#include "clipper.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "Polygon.hpp"
|
||||
@@ -322,15 +321,6 @@ namespace ClipperUtils {
|
||||
[[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const ExPolygon &src, const BoundingBox &bbox, const bool get_entire_polygons = false);
|
||||
[[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const ExPolygons &src, const BoundingBox &bbox, const bool get_entire_polygons = false);
|
||||
|
||||
// Splits ExPolygons into tiles by the centres of their boxes, about `per_tile` of them to a tile, to run ClipperLib on a
|
||||
// layer of many pieces tile by tile. Returns the non-empty tiles, each with the indices of its ExPolygons and their box.
|
||||
struct ExPolygonsTile
|
||||
{
|
||||
BoundingBox bbox;
|
||||
std::vector<size_t> members;
|
||||
};
|
||||
[[nodiscard]] std::vector<ExPolygonsTile> tile_expolygons(const ExPolygons &expolygons, size_t per_tile);
|
||||
|
||||
}
|
||||
|
||||
// Perform union of input polygons using the non-zero rule, convert to ExPolygons.
|
||||
@@ -528,11 +518,6 @@ Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
// diff_ex() / intersection_ex() of the subject split into tiles, each against only the part of the clip near it, the tiles in
|
||||
// parallel. The same area as the operation on the whole subject when its ExPolygons do not overlap, and much faster for a
|
||||
// subject of thousands of pieces spread over a layer: ClipperLib slows down with the number of edges crossing a scan line.
|
||||
Slic3r::ExPolygons diff_ex_by_piece(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons intersection_ex_by_piece(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygon &clip);
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &clip);
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip);
|
||||
|
||||
+26
-13
@@ -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();
|
||||
|
||||
+12
-31
@@ -9,8 +9,6 @@
|
||||
#include "../PrintConfig.hpp"
|
||||
#include "../Surface.hpp"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
|
||||
#include "AABBTreeLines.hpp"
|
||||
#include "ExtrusionEntity.hpp"
|
||||
#include "Fill.hpp"
|
||||
@@ -632,28 +630,24 @@ void split_solid_surface(size_t layer_id, const SurfaceFill &fill, ExPolygons &n
|
||||
if (!line_based_pattern) {
|
||||
const coord_t scaled_spacing = scaled<coord_t>(fill.params.spacing);
|
||||
|
||||
// Each expolygon is split on its own, so they run in parallel and are collected in their original order.
|
||||
std::vector<std::pair<ExPolygons, ExPolygons>> split_parts(fill.expolygons.size()); // normal, narrow
|
||||
tbb::parallel_for(size_t(0), fill.expolygons.size(), [&](size_t idx) {
|
||||
const ExPolygon &expolygon = fill.expolygons[idx];
|
||||
for (const ExPolygon &expolygon : fill.expolygons) {
|
||||
Polygons filled_area = to_polygons(expolygon);
|
||||
|
||||
// "Core" area: open (erode+dilate) to drop thin features, then clamp back to the original polygon.
|
||||
Polygons inner_area = intersection(filled_area, opening(filled_area, scaled_spacing, scaled_spacing));
|
||||
|
||||
if (inner_area.empty()) {
|
||||
split_parts[idx].second.emplace_back(expolygon);
|
||||
return;
|
||||
narrow_infill.emplace_back(expolygon);
|
||||
continue;
|
||||
}
|
||||
|
||||
ExPolygons inner_ex = union_ex(inner_area);
|
||||
ExPolygons expolys{expolygon};
|
||||
split_parts[idx].second = diff_ex(expolys, inner_ex); // narrow infill area
|
||||
split_parts[idx].first = intersection_ex(expolys, inner_ex); // normal infill area
|
||||
});
|
||||
for (auto &[normal_ex, narrow_ex] : split_parts) {
|
||||
append(normal_infill, std::move(normal_ex));
|
||||
append(narrow_infill, std::move(narrow_ex));
|
||||
ExPolygons narrow_ex = diff_ex(expolys, inner_ex);
|
||||
ExPolygons normal_ex = intersection_ex(expolys, inner_ex);
|
||||
|
||||
append(normal_infill, normal_ex); // normal infill area
|
||||
append(narrow_infill, narrow_ex); // narrow infill area
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -675,10 +669,7 @@ void split_solid_surface(size_t layer_id, const SurfaceFill &fill, ExPolygons &n
|
||||
}
|
||||
const double aligning_angle = -base_angle + PI;
|
||||
|
||||
// Each expolygon is reconstructed on its own, so they run in parallel and are collected in their original order.
|
||||
std::vector<Polygons> split_reconstructed(fill.expolygons.size());
|
||||
tbb::parallel_for(size_t(0), fill.expolygons.size(), [&](size_t expolygon_idx) {
|
||||
const ExPolygon &expolygon = fill.expolygons[expolygon_idx];
|
||||
for (const ExPolygon &expolygon : fill.expolygons) {
|
||||
Polygons filled_area = to_polygons(expolygon);
|
||||
polygons_rotate(filled_area, aligning_angle);
|
||||
BoundingBox bb = get_extents(filled_area);
|
||||
@@ -809,10 +800,8 @@ void split_solid_surface(size_t layer_id, const SurfaceFill &fill, ExPolygons &n
|
||||
}
|
||||
}
|
||||
|
||||
split_reconstructed[expolygon_idx] = std::move(reconstructed_area);
|
||||
});
|
||||
for (Polygons &reconstructed_area : split_reconstructed)
|
||||
polygons_append(normal_fill_areas, std::move(reconstructed_area));
|
||||
polygons_append(normal_fill_areas, reconstructed_area);
|
||||
}
|
||||
|
||||
polygons_rotate(normal_fill_areas, -aligning_angle);
|
||||
|
||||
@@ -1420,15 +1409,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
// Orca: Reuse the body origin used for bridge anchoring, resetting it for each surface.
|
||||
f->set_bounding_box(infill_bounding_box(*this, surface_fill, expoly, bbox));
|
||||
|
||||
// Only the part of the layer-wide no-overlap area under this expolygon matters, so clip it to the
|
||||
// expolygon's box first (padded past the safety offset, which grows the clip side). The result is
|
||||
// identical; the cost is not: a layer split into many small fills, e.g. by colour painting,
|
||||
// otherwise intersects every one of them with the whole layer.
|
||||
BoundingBox no_overlap_bbox = get_extents(expoly);
|
||||
no_overlap_bbox.offset(SCALED_EPSILON);
|
||||
f->no_overlap_expolygons = intersection_ex(
|
||||
ClipperUtils::clip_clipper_polygons_with_subject_bbox(surface_fill.no_overlap_expolygons, no_overlap_bbox),
|
||||
ExPolygons() = {expoly}, ApplySafetyOffset::Yes);
|
||||
f->no_overlap_expolygons = intersection_ex(surface_fill.no_overlap_expolygons, ExPolygons() = {expoly}, ApplySafetyOffset::Yes);
|
||||
if (params.symmetric_infill_y_axis) {
|
||||
params.symmetric_y_axis = f->extended_object_bounding_box().center().x();
|
||||
expoly.symmetric_y(params.symmetric_y_axis);
|
||||
|
||||
@@ -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) {
|
||||
auto set_face_color = [&uvs, &data, &mtl_data, &obj_info, &face_color](int face_index, const std::string mtl_name, const std::array<int, 3> &corners) {
|
||||
if (mtl_data.new_mtl_unmap.find(mtl_name) != mtl_data.new_mtl_unmap.end()) {
|
||||
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[0]), uv_at(uvs[1]), uv_at(uvs[2])};
|
||||
std::array<Vec2f, 3> uv_array{uv_at(uvs[corners[0]]), uv_at(uvs[corners[1]]), uv_at(uvs[corners[2]])};
|
||||
obj_info.uvs.emplace_back(uv_array);
|
||||
}
|
||||
obj_info.face_colors.emplace_back(face_color);
|
||||
@@ -185,27 +185,27 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s
|
||||
}
|
||||
}
|
||||
};
|
||||
auto set_face_color_by_mtl = [&data, &set_face_color](int face_index) {
|
||||
auto set_face_color_by_mtl = [&data, &set_face_color](int face_index, const std::array<int, 3> &corners) {
|
||||
if (data.usemtls.size() == 1) {
|
||||
set_face_color(face_index, data.usemtls[0].name);
|
||||
set_face_color(face_index, data.usemtls[0].name, corners);
|
||||
} 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);
|
||||
set_face_color(face_index, data.usemtls[k].name, corners);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
if (exist_mtl) {
|
||||
set_face_color_by_mtl(face_index);
|
||||
set_face_color_by_mtl(face_index, {0, 1, 2});
|
||||
}
|
||||
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);
|
||||
set_face_color_by_mtl(face_index, {0, 2, 3});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -217,8 +217,12 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s
|
||||
message = _L("This OBJ file couldn't be read because it's empty.");
|
||||
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;
|
||||
|
||||
+70
-88
@@ -1497,12 +1497,7 @@ 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 (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));
|
||||
}
|
||||
gcode += gcodegen.set_filament_pressure_advance(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>());
|
||||
@@ -1826,12 +1821,8 @@ 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 && 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));
|
||||
}
|
||||
if (new_extruder_id != -1)
|
||||
gcode += gcodegen.set_filament_pressure_advance(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>());
|
||||
@@ -3232,7 +3223,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, tool_ordering.all_extruders());
|
||||
m_pa_processor = std::make_unique<AdaptivePAProcessor>(*this);
|
||||
|
||||
// Update output variables after the extruders were initialized.
|
||||
m_placeholder_parser_integration.init(m_writer);
|
||||
@@ -3797,12 +3788,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
}
|
||||
}
|
||||
// Orca: add missing PA settings for initial filament
|
||||
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));
|
||||
}
|
||||
file.write(set_filament_pressure_advance(initial_non_support_extruder_id));
|
||||
}
|
||||
|
||||
//flush FanMover buffer to avoid modifying the start gcode if it's manual.
|
||||
@@ -4188,6 +4174,28 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
|
||||
print.throw_if_canceled();
|
||||
|
||||
// Some firmwares only scan the last N lines for the time estimate, so the stats are written
|
||||
// after the config block.
|
||||
if (!is_bbl_printers && !skip_config_block) {
|
||||
file.write("; CONFIG_BLOCK_START\n");
|
||||
std::string full_config;
|
||||
append_full_config(print, full_config);
|
||||
if (!full_config.empty())
|
||||
file.write(full_config);
|
||||
|
||||
// SoftFever: write compatiple info
|
||||
int first_layer_bed_temperature = get_bed_temperature(0, true, print.config().curr_bed_type);
|
||||
file.write_format("; first_layer_bed_temperature = %d\n", first_layer_bed_temperature);
|
||||
file.write_format("; bed_shape = %s\n", print.full_print_config().opt_serialize("printable_area").c_str());
|
||||
file.write_format("; first_layer_temperature = %d\n", print.config().nozzle_temperature_initial_layer.get_at(0));
|
||||
file.write_format("; first_layer_height = %.3f\n", print.config().initial_layer_print_height.value);
|
||||
|
||||
//SF TODO
|
||||
// file.write_format("; variable_layer_height = %d\n", print.ad.adaptive_layer_height ? 1 : 0);
|
||||
|
||||
file.write("; CONFIG_BLOCK_END\n\n");
|
||||
}
|
||||
|
||||
// Get filament stats.
|
||||
file.write(DoExport::update_print_stats_and_format_filament_stats(
|
||||
// Const inputs
|
||||
@@ -4214,26 +4222,6 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder)
|
||||
.c_str());
|
||||
file.write("\n");
|
||||
if (!skip_config_block) {
|
||||
file.write("; CONFIG_BLOCK_START\n");
|
||||
std::string full_config;
|
||||
append_full_config(print, full_config);
|
||||
if (!full_config.empty())
|
||||
file.write(full_config);
|
||||
|
||||
// SoftFever: write compatiple info
|
||||
int first_layer_bed_temperature = get_bed_temperature(0, true, print.config().curr_bed_type);
|
||||
file.write_format("; first_layer_bed_temperature = %d\n", first_layer_bed_temperature);
|
||||
file.write_format("; bed_shape = %s\n", print.full_print_config().opt_serialize("printable_area").c_str());
|
||||
file.write_format("; first_layer_temperature = %d\n", print.config().nozzle_temperature_initial_layer.get_at(0));
|
||||
file.write_format("; first_layer_height = %.3f\n", print.config().initial_layer_print_height.value);
|
||||
|
||||
//SF TODO
|
||||
// file.write_format("; variable_layer_height = %d\n", print.ad.adaptive_layer_height ? 1 : 0);
|
||||
|
||||
file.write("; CONFIG_BLOCK_END\n\n");
|
||||
} // !skip_config_block
|
||||
|
||||
}
|
||||
file.write("\n");
|
||||
|
||||
@@ -8390,12 +8378,16 @@ 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 && FILAMENT_CONFIG(adaptive_pressure_advance) && FILAMENT_CONFIG(enable_pressure_advance)) {
|
||||
if (!is_pa_calib && adaptive_pa) {
|
||||
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.
|
||||
@@ -8478,7 +8470,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
// Post processor flag generation code segment when option to emit only at role changes is enabled
|
||||
// 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 extruder ID (to identify the PA model for the currently used extruder)
|
||||
// 2) Current filament config index (to identify the PA model for the currently used filament and its extruder variant)
|
||||
// 3) mm3_per_mm value (to then multiply by the final model print speed after slowdown for cooling is applied)
|
||||
// 4) the current acceleration (to pass to the model for evaluation)
|
||||
// 5) whether this is an external perimeter (for future use)
|
||||
@@ -8489,7 +8481,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
if (m_multi_flow_segment_path_average_mm3_per_mm > 0) {
|
||||
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
|
||||
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
|
||||
m_writer.filament()->id(),
|
||||
pa_config_index,
|
||||
m_multi_flow_segment_path_average_mm3_per_mm,
|
||||
acceleration_i,
|
||||
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
|
||||
@@ -8502,7 +8494,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
// to issue a zero flow PA change command for this
|
||||
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
|
||||
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
|
||||
m_writer.filament()->id(),
|
||||
pa_config_index,
|
||||
_mm3_per_mm,
|
||||
acceleration_i,
|
||||
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
|
||||
@@ -8610,11 +8602,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
// or a flow change, so emit the flag to evaluate PA for the upcomming extrusion
|
||||
// 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 &&
|
||||
FILAMENT_CONFIG(adaptive_pressure_advance) &&
|
||||
FILAMENT_CONFIG(enable_pressure_advance) &&
|
||||
FILAMENT_CONFIG(adaptive_pressure_advance_overhangs) &&
|
||||
!evaluate_adaptive_pa){
|
||||
if(_mm3_per_mm >0 && adaptive_pa_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");
|
||||
@@ -8622,7 +8610,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
}
|
||||
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
|
||||
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
|
||||
m_writer.filament()->id(),
|
||||
pa_config_index,
|
||||
_mm3_per_mm,
|
||||
acceleration_i,
|
||||
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
|
||||
@@ -8637,7 +8625,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
}
|
||||
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
|
||||
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
|
||||
m_writer.filament()->id(),
|
||||
pa_config_index,
|
||||
_mm3_per_mm,
|
||||
acceleration_i,
|
||||
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
|
||||
@@ -8843,10 +8831,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
// ORCA: Adaptive PA code segment when adjusting PA within the same feature
|
||||
// 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 &&
|
||||
EXTRUDER_CONFIG(adaptive_pressure_advance) &&
|
||||
EXTRUDER_CONFIG(enable_pressure_advance) &&
|
||||
EXTRUDER_CONFIG(adaptive_pressure_advance_overhangs) ){
|
||||
if(_mm3_per_mm >0 && adaptive_pa_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");
|
||||
@@ -8854,7 +8839,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
}
|
||||
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
|
||||
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
|
||||
m_writer.filament()->id(),
|
||||
pa_config_index,
|
||||
_mm3_per_mm,
|
||||
acceleration_i,
|
||||
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
|
||||
@@ -8869,7 +8854,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
}
|
||||
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
|
||||
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
|
||||
m_writer.filament()->id(),
|
||||
pa_config_index,
|
||||
_mm3_per_mm,
|
||||
acceleration_i,
|
||||
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
|
||||
@@ -9422,33 +9407,29 @@ void GCode::update_placeholder_parser_with_variant_params()
|
||||
if (num_filaments == 0)
|
||||
return;
|
||||
|
||||
// Helpers: remap config arrays from variant index space to filament_id index space.
|
||||
// Helper: remap config arrays from variant index space to filament_id index space.
|
||||
// After remapping, gcode templates can use param[filament_id] directly.
|
||||
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);
|
||||
auto remap_by_filament = [&](const auto &src) {
|
||||
std::decay_t<decltype(src.values)> dst(num_filaments);
|
||||
for (size_t i = 0; i < num_filaments; ++i)
|
||||
dst[i] = src.get_at(get_filament_config_index(i));
|
||||
return dst;
|
||||
};
|
||||
|
||||
// --- filament_options_with_variant: gcode indexes by filament_id ---
|
||||
this->placeholder_parser().set("filament_max_volumetric_speed", new ConfigOptionFloats(remap_floats_by_filament(m_config.filament_max_volumetric_speed)));
|
||||
this->placeholder_parser().set("filament_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)));
|
||||
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)));
|
||||
// first_layer_temperature is a legacy alias of nozzle_temperature_initial_layer
|
||||
this->placeholder_parser().set("first_layer_temperature", new ConfigOptionInts(remap_ints_by_filament(m_config.nozzle_temperature_initial_layer)));
|
||||
this->placeholder_parser().set("first_layer_temperature", new ConfigOptionInts(remap_by_filament(m_config.nozzle_temperature_initial_layer)));
|
||||
this->placeholder_parser().set("pressure_advance", new ConfigOptionFloats(remap_by_filament(m_config.pressure_advance)));
|
||||
this->placeholder_parser().set("enable_pressure_advance", new ConfigOptionBools(remap_by_filament(m_config.enable_pressure_advance)));
|
||||
|
||||
// --- printer_options_with_variant_1: in m_config these are already merged as filament-indexed ---
|
||||
this->placeholder_parser().set("retraction_distances_when_cut", new ConfigOptionFloats(remap_floats_by_filament(m_config.retraction_distances_when_cut)));
|
||||
this->placeholder_parser().set("retraction_distances_when_cut", new ConfigOptionFloats(remap_by_filament(m_config.retraction_distances_when_cut)));
|
||||
// hotend_cooling_rate / hotend_heating_rate: gcode uses [filament_map[x]-1] (extruder_id), no remap needed
|
||||
|
||||
// --- filament_map: per-layer dynamic, sync from m_config to placeholder_parser ---
|
||||
@@ -9458,9 +9439,9 @@ void GCode::update_placeholder_parser_with_variant_params()
|
||||
{
|
||||
// Fast purge mode uses filament_flush_temp_fast; Default is inert.
|
||||
bool use_fast_flush = m_config.prime_volume_mode == PrimeVolumeMode::pvmFast;
|
||||
auto flush_v_speed = remap_floats_by_filament(m_config.filament_flush_volumetric_speed);
|
||||
auto 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
|
||||
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
|
||||
: m_config.filament_flush_temp);
|
||||
for (size_t i = 0; i < num_filaments; ++i) {
|
||||
if (flush_v_speed[i] == 0)
|
||||
@@ -9513,12 +9494,7 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
|
||||
gcode += this->placeholder_parser_process("filament_start_gcode", filament_start_gcode, new_filament_id, &config);
|
||||
check_add_eol(gcode);
|
||||
}
|
||||
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 += set_filament_pressure_advance(new_filament_id);
|
||||
|
||||
gcode += m_writer.toolchange(new_filament_id, new_extruder_id);
|
||||
if (Extruder *fil = m_writer.filament())
|
||||
@@ -9909,18 +9885,24 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
|
||||
if (m_ooze_prevention.enable && !defer_temp_wait)
|
||||
gcode += m_ooze_prevention.post_toolchange(*this);
|
||||
|
||||
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 += set_filament_pressure_advance(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,6 +281,8 @@ 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,38 +21,30 @@ namespace Slic3r {
|
||||
*
|
||||
* @param gcodegen A reference to the GCode object that generates the G-code.
|
||||
*/
|
||||
AdaptivePAProcessor::AdaptivePAProcessor(GCode &gcodegen, const std::vector<unsigned int> &tools_used)
|
||||
AdaptivePAProcessor::AdaptivePAProcessor(GCode &gcodegen)
|
||||
: m_gcodegen(gcodegen),
|
||||
m_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_extruder_id(-1),
|
||||
m_last_config_index(-1),
|
||||
m_pa_change_pattern(R"(; PA_CHANGE:T(\d+) MM3MM:([0-9]*\.[0-9]+) ACCEL:(\d+) BR:(\d+) RC:(\d+) OV:(\d+))"),
|
||||
m_g1_f_pattern(R"(G1 F([0-9]+))")
|
||||
{
|
||||
// Constructor body can be used for further initialization if necessary
|
||||
for (unsigned int tool : tools_used) {
|
||||
// Only enable model for the tool if both PA and adaptive PA options are enabled
|
||||
if(m_config.adaptive_pressure_advance.get_at(tool) && m_config.enable_pressure_advance.get_at(tool)){
|
||||
auto interpolator = std::make_unique<AdaptivePAInterpolator>();
|
||||
// Get calibration values from extruder
|
||||
std::string pa_calibration_values = m_config.adaptive_pressure_advance_model.get_at(tool);
|
||||
// Setup the model and store it in the tool-interpolation model map
|
||||
interpolator->parseAndSetData(pa_calibration_values);
|
||||
m_AdaptivePAInterpolators[tool] = std::move(interpolator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method to get the interpolator for a specific tool ID
|
||||
AdaptivePAInterpolator* AdaptivePAProcessor::getInterpolator(unsigned int tool_id) {
|
||||
auto it = m_AdaptivePAInterpolators.find(tool_id);
|
||||
if (it != m_AdaptivePAInterpolators.end()) {
|
||||
return it->second.get();
|
||||
// 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));
|
||||
}
|
||||
return nullptr; // Handle the case where the tool_id was not found
|
||||
return it->second.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,16 +100,17 @@ std::string AdaptivePAProcessor::process_layer(std::string &&gcode) {
|
||||
// the PA for that material is set. As no tag below will be found for this extruder, the original PA is retained.
|
||||
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)) {
|
||||
int extruder_id = std::stoi(m_match[1].str());
|
||||
// The tag carries the filament config index, which selects the PA settings of the filament's extruder variant
|
||||
int config_index = std::stoi(m_match[1].str());
|
||||
mm3mm_value = std::stod(m_match[2].str());
|
||||
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 extruder ID has changed
|
||||
bool extruder_changed = (extruder_id != m_last_extruder_id);
|
||||
m_last_extruder_id = extruder_id;
|
||||
// Check if the filament config index has changed
|
||||
bool config_index_changed = (config_index != m_last_config_index);
|
||||
m_last_config_index = config_index;
|
||||
|
||||
// Save the PA_CHANGE line to output later after finding feedrate
|
||||
pa_change_line = line;
|
||||
@@ -212,23 +205,23 @@ std::string AdaptivePAProcessor::process_layer(std::string &&gcode) {
|
||||
|
||||
// Calculate the predicted PA using the upcomming feature maximum feedrate
|
||||
// Get the interpolator for the active tool
|
||||
AdaptivePAInterpolator* interpolator = getInterpolator(m_last_extruder_id);
|
||||
AdaptivePAInterpolator* interpolator = getInterpolator(m_last_config_index);
|
||||
|
||||
double predicted_pa = 0;
|
||||
double 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_extruder_id) ? m_config.pressure_advance.get_at(m_last_extruder_id) : 0;
|
||||
predicted_pa = m_config.enable_pressure_advance.get_at(m_last_config_index) ? m_config.pressure_advance.get_at(m_last_config_index) : 0;
|
||||
if(m_config.gcode_comments) output << "; APA: Tool doesnt have APA enabled\n";
|
||||
} else if (!interpolator->isInitialised() || (!m_config.adaptive_pressure_advance.get_at(m_last_extruder_id)) )
|
||||
} else if (!interpolator->isInitialised() || (!m_config.adaptive_pressure_advance.get_at(m_last_config_index)) )
|
||||
// Check if the model is not initialised by the constructor for the active extruder
|
||||
// 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_extruder_id) ? m_config.pressure_advance.get_at(m_last_extruder_id) : 0;
|
||||
predicted_pa = m_config.enable_pressure_advance.get_at(m_last_config_index) ? m_config.pressure_advance.get_at(m_last_config_index) : 0;
|
||||
if(m_config.gcode_comments) output << "; APA: Interpolator setup failed, using default pressure advance\n";
|
||||
} else { // Model setup succeeded
|
||||
// Proceed to identify the print speed to use to calculate the adaptive PA value
|
||||
@@ -249,18 +242,18 @@ std::string AdaptivePAProcessor::process_layer(std::string &&gcode) {
|
||||
predicted_pa = (*interpolator)(mm3mm_value * adaptive_PA_speed, accel_value);
|
||||
|
||||
// This is a bridge, use the dedicated PA setting.
|
||||
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(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 (predicted_pa < 0) { // If extrapolation fails, fall back to the default PA for the extruder.
|
||||
predicted_pa = m_config.enable_pressure_advance.get_at(m_last_extruder_id) ? m_config.pressure_advance.get_at(m_last_extruder_id) : 0;
|
||||
predicted_pa = m_config.enable_pressure_advance.get_at(m_last_config_index) ? m_config.pressure_advance.get_at(m_last_config_index) : 0;
|
||||
if(m_config.gcode_comments) output << "; APA: Interpolation failed, using fallback pressure advance value\n";
|
||||
}
|
||||
}
|
||||
if(m_config.gcode_comments) {
|
||||
// Output 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_extruder_id) > EPSILON)
|
||||
if(isBridge && m_config.adaptive_pressure_advance_bridges.get_at(m_last_config_index) > EPSILON)
|
||||
output << "; APA Model Override (bridge)\n";
|
||||
output << "; APA Current Speed: " << std::to_string(m_current_feedrate) << "\n";
|
||||
output << "; APA Next Speed: " << std::to_string(m_next_feedrate) << "\n";
|
||||
@@ -269,7 +262,7 @@ std::string AdaptivePAProcessor::process_layer(std::string &&gcode) {
|
||||
output << "; APA Flow rate: " << std::to_string(mm3mm_value * m_max_next_feedrate) << "\n";
|
||||
output << "; APA Prev PA: " << std::to_string(m_last_predicted_pa) << " New PA: " << std::to_string(predicted_pa) << "\n";
|
||||
}
|
||||
if (extruder_changed || std::fabs(predicted_pa - m_last_predicted_pa) > EPSILON) {
|
||||
if (config_index_changed || std::fabs(predicted_pa - m_last_predicted_pa) > EPSILON) {
|
||||
output << m_gcodegen.writer().set_pressure_advance(predicted_pa); // Use m_writer to set pressure advance
|
||||
m_last_predicted_pa = predicted_pa; // Update the last predicted PA value
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include <regex>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include "AdaptivePAInterpolator.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
@@ -33,7 +32,7 @@ public:
|
||||
*
|
||||
* @param gcodegen A reference to the GCode object that generates the G-code.
|
||||
*/
|
||||
AdaptivePAProcessor(GCode &gcodegen, const std::vector<unsigned int> &tools_used);
|
||||
AdaptivePAProcessor(GCode &gcodegen);
|
||||
|
||||
/**
|
||||
* @brief Processes a layer of G-code and applies adaptive pressure advance.
|
||||
@@ -70,28 +69,25 @@ 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 tool ID's
|
||||
std::unordered_map<unsigned int, std::unique_ptr<AdaptivePAInterpolator>> m_AdaptivePAInterpolators; ///< Map between Interpolator objects and filament config indices (null when adaptive PA is off)
|
||||
const PrintConfig &m_config; ///< Reference to the print configuration.
|
||||
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_extruder_id; ///< Last used extruder ID.
|
||||
int m_last_config_index; ///< Filament config index of the last PA_CHANGE tag.
|
||||
|
||||
std::regex m_pa_change_pattern; ///< Regular expression to detect PA_CHANGE pattern.
|
||||
std::regex m_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 tool ID.
|
||||
* @brief Get the PA interpolator attached to the specified filament config index.
|
||||
*
|
||||
* 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.
|
||||
* @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.
|
||||
*/
|
||||
AdaptivePAInterpolator* getInterpolator(unsigned int tool_id);
|
||||
AdaptivePAInterpolator* getInterpolator(unsigned int config_index);
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -135,79 +134,15 @@ bool tsp_remove_crossings(std::vector<size_t>& path, const Points& centers)
|
||||
return {std::numeric_limits<size_t>::max(), std::numeric_limits<size_t>::max()};
|
||||
};
|
||||
|
||||
// For many islands, the same scan with the edges binned in a uniform grid over their boxes, so each edge is only tested against the edges sharing a
|
||||
// cell with it - two edges whose boxes overlap always do. It returns the same crossing as the all-pairs scan
|
||||
// (smallest i, then smallest j), so the result is unchanged. The all-pairs scan is quadratic in the edge count and
|
||||
// runs again after every reversal; rebuilding the grid costs more than it saves below the threshold.
|
||||
constexpr size_t grid_min_size = 500;
|
||||
BoundingBox extent;
|
||||
for (size_t idx : path)
|
||||
extent.merge(centers[idx]);
|
||||
const int grid_n = std::clamp(int(std::sqrt(double(pn))), 1, 256);
|
||||
const coord_t cell_w = std::max<coord_t>(1, (extent.max.x() - extent.min.x()) / grid_n + 1);
|
||||
const coord_t cell_h = std::max<coord_t>(1, (extent.max.y() - extent.min.y()) / grid_n + 1);
|
||||
const auto for_cells = [&](const Point& a, const Point& b, auto&& fn) {
|
||||
const int x0 = int((std::min(a.x(), b.x()) - extent.min.x()) / cell_w), x1 = int((std::max(a.x(), b.x()) - extent.min.x()) / cell_w);
|
||||
const int y0 = int((std::min(a.y(), b.y()) - extent.min.y()) / cell_h), y1 = int((std::max(a.y(), b.y()) - extent.min.y()) / cell_h);
|
||||
for (int y = y0; y <= y1; ++y)
|
||||
for (int x = x0; x <= x1; ++x)
|
||||
fn(y * grid_n + x);
|
||||
};
|
||||
std::vector<std::vector<size_t>> edge_cells(size_t(grid_n) * grid_n);
|
||||
|
||||
auto find_crossing_grid = [&]() -> std::pair<size_t, size_t> {
|
||||
for (std::vector<size_t>& cell : edge_cells)
|
||||
cell.clear();
|
||||
for (size_t j = 0; j < n_edges; ++j)
|
||||
for_cells(centers[path[j]], centers[path[(j + 1) % pn]], [&](int cell) { edge_cells[cell].emplace_back(j); });
|
||||
|
||||
for (size_t i = 0; i < n_edges; ++i) {
|
||||
const Point& ai = centers[path[i]];
|
||||
const Point& bi = centers[path[(i + 1) % pn]];
|
||||
|
||||
size_t first_j = std::numeric_limits<size_t>::max();
|
||||
for_cells(ai, bi, [&](int cell) {
|
||||
for (size_t j : edge_cells[cell]) {
|
||||
if (j < i + 2 || j >= first_j) continue;
|
||||
// Skip the (0, pn-1) pair: edges (0,1) and (pn-1,0) share node 0.
|
||||
if (i == 0 && j == pn - 1) continue;
|
||||
|
||||
const Point& aj = centers[path[j]];
|
||||
const Point& bj = centers[path[(j + 1) % pn]];
|
||||
|
||||
if (!bboxes_overlap(ai, bi, aj, bj)) continue;
|
||||
if (Geometry::segments_intersect(ai, bi, aj, bj))
|
||||
first_j = j;
|
||||
}
|
||||
});
|
||||
if (first_j != std::numeric_limits<size_t>::max())
|
||||
return {i, first_j};
|
||||
}
|
||||
return {std::numeric_limits<size_t>::max(), std::numeric_limits<size_t>::max()};
|
||||
};
|
||||
|
||||
// Process crossings one at a time: find first, reverse it, restart scan.
|
||||
// Cap iterations to prevent infinite loops on collinear/overlapping segments.
|
||||
int max_iters = static_cast<int>(pn * pn);
|
||||
bool improved = false;
|
||||
// Reversing between two segments that only touch or overlap along a line need not remove the intersection, so on
|
||||
// islands laid out on a regular grid (a tiled texture, an array of parts) the loop can cycle through the same
|
||||
// orderings until the pn * pn cap. Stop as soon as an ordering repeats; up to that point this is the same loop.
|
||||
std::unordered_set<uint64_t> seen_paths;
|
||||
const auto path_hash = [&path]() {
|
||||
uint64_t h = 1469598103934665603ull; // FNV-1a
|
||||
for (size_t idx : path)
|
||||
h = (h ^ uint64_t(idx)) * 1099511628211ull;
|
||||
return h;
|
||||
};
|
||||
seen_paths.insert(path_hash());
|
||||
while (max_iters-- > 0) {
|
||||
auto [ci, cj] = pn >= grid_min_size ? find_crossing_grid() : find_crossing();
|
||||
auto [ci, cj] = find_crossing();
|
||||
if (ci == std::numeric_limits<size_t>::max()) break;
|
||||
improved = true;
|
||||
std::reverse(path.begin() + ci + 1, path.begin() + cj + 1);
|
||||
if (!seen_paths.insert(path_hash()).second)
|
||||
break;
|
||||
}
|
||||
return improved;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <random>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <queue>
|
||||
#include <unordered_map>
|
||||
|
||||
@@ -1179,21 +1178,21 @@ std::optional<std::pair<size_t, size_t>> SeamPlacer::find_next_seam_in_layer(
|
||||
const size_t layer_idx, const float max_distance,
|
||||
const SeamPlacerImpl::SeamComparator &comparator) const {
|
||||
using namespace SeamPlacerImpl;
|
||||
// Find the best nearby point and the nearest one. A layer of a fine relief has tens of thousands of candidates within
|
||||
// the radius, so they are looked at as the search finds them rather than collected into a vector first.
|
||||
constexpr size_t none = std::numeric_limits<size_t>::max();
|
||||
size_t best_nearby_point_index = none;
|
||||
size_t nearest_point_index = none;
|
||||
visit_nearby_points(*layers[layer_idx].points_tree, projected_position, max_distance,
|
||||
[&layers, &comparator, &projected_position, layer_idx, &best_nearby_point_index, &nearest_point_index]
|
||||
(size_t nearby_point_index) {
|
||||
if (best_nearby_point_index == none) {
|
||||
// The first point found starts both, as the first of the collected ones did.
|
||||
best_nearby_point_index = nearest_point_index = nearby_point_index;
|
||||
std::vector<size_t> nearby_points_indices = find_nearby_points(*layers[layer_idx].points_tree, projected_position,
|
||||
max_distance);
|
||||
|
||||
if (nearby_points_indices.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
size_t best_nearby_point_index = nearby_points_indices[0];
|
||||
size_t nearest_point_index = nearby_points_indices[0];
|
||||
|
||||
// Now find best nearby point, nearest point, and corresponding indices
|
||||
for (const size_t &nearby_point_index : nearby_points_indices) {
|
||||
const SeamCandidate &point = layers[layer_idx].points[nearby_point_index];
|
||||
if (point.perimeter.finalized) {
|
||||
return; // skip over finalized perimeters, try to find some that is not finalized
|
||||
continue; // skip over finalized perimeters, try to find some that is not finalized
|
||||
}
|
||||
if (comparator.is_first_better(point, layers[layer_idx].points[best_nearby_point_index],
|
||||
projected_position.head<2>())
|
||||
@@ -1205,10 +1204,6 @@ std::optional<std::pair<size_t, size_t>> SeamPlacer::find_next_seam_in_layer(
|
||||
|| layers[layer_idx].points[nearest_point_index].perimeter.finalized) {
|
||||
nearest_point_index = nearby_point_index;
|
||||
}
|
||||
});
|
||||
|
||||
if (best_nearby_point_index == none) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const SeamCandidate &best_nearby_point = layers[layer_idx].points[best_nearby_point_index];
|
||||
|
||||
@@ -313,36 +313,6 @@ std::vector<size_t> find_nearby_points(const KDTreeIndirectType &kdtree, const P
|
||||
return visitor.result;
|
||||
}
|
||||
|
||||
// Visits the points within max_distance of center, in the order find_nearby_points() would collect them, and hands
|
||||
// each of them to `visitor_fn` instead of returning them all: a search over a dense set spends more on collecting the
|
||||
// points into a vector than on the search itself, and its caller usually keeps only a few of them.
|
||||
template<typename KDTreeIndirectType, typename PointType, typename VisitorFn>
|
||||
void visit_nearby_points(const KDTreeIndirectType &kdtree, const PointType ¢er,
|
||||
const typename KDTreeIndirectType::CoordType &max_distance, VisitorFn visitor_fn)
|
||||
{
|
||||
using CoordType = typename KDTreeIndirectType::CoordType;
|
||||
|
||||
struct Visitor {
|
||||
const KDTreeIndirectType &kdtree;
|
||||
const PointType center;
|
||||
const CoordType max_distance_squared;
|
||||
VisitorFn visitor_fn;
|
||||
|
||||
unsigned int operator()(size_t idx, size_t dimension) {
|
||||
auto dist = CoordType(0);
|
||||
for (size_t i = 0; i < KDTreeIndirectType::NumDimensions; ++i) {
|
||||
CoordType d = center[i] - kdtree.coordinate(idx, i);
|
||||
dist += d * d;
|
||||
}
|
||||
if (dist < max_distance_squared)
|
||||
visitor_fn(idx);
|
||||
return kdtree.descent_mask(center[dimension], max_distance_squared, idx, dimension);
|
||||
}
|
||||
} visitor { kdtree, center, max_distance * max_distance, visitor_fn };
|
||||
|
||||
kdtree.visit(visitor);
|
||||
}
|
||||
|
||||
template<typename KDTreeIndirectType, typename PointType>
|
||||
std::vector<size_t> find_nearby_points(const KDTreeIndirectType &kdtree, const PointType ¢er,
|
||||
const typename KDTreeIndirectType::CoordType& max_distance)
|
||||
|
||||
@@ -72,11 +72,10 @@ void LayerRegion::slices_to_fill_surfaces_clipped()
|
||||
by_surface[size_t(surface.surface_type)].emplace_back(&surface);
|
||||
// Trim surfaces by the fill_boundaries.
|
||||
this->fill_surfaces.surfaces.clear();
|
||||
const Polygons fill_boundaries = to_polygons(this->fill_expolygons);
|
||||
for (size_t surface_type = 0; surface_type < size_t(stCount); ++ surface_type) {
|
||||
const SurfacesPtr &this_surfaces = by_surface[surface_type];
|
||||
if (! this_surfaces.empty())
|
||||
this->fill_surfaces.append(intersection_ex_by_piece(to_expolygons(this_surfaces), fill_boundaries), SurfaceType(surface_type));
|
||||
this->fill_surfaces.append(intersection_ex(this_surfaces, this->fill_expolygons), SurfaceType(surface_type));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include "MutablePolygon.hpp"
|
||||
#include "format.hpp"
|
||||
|
||||
#include <numeric>
|
||||
#include <utility>
|
||||
#include <unordered_set>
|
||||
|
||||
@@ -1312,15 +1311,10 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
|
||||
}
|
||||
#endif // MM_SEGMENTATION_DEBUG_TOP_BOTTOM
|
||||
|
||||
// When the upper surface of an object is occluded, it should no longer be considered the upper surface.
|
||||
// Every (colour, layer) pair is trimmed on its own, so they all run at once: the painted faces of a finely
|
||||
// textured part project hundreds of thousands of triangles onto one layer, which used to be trimmed serially.
|
||||
// When the upper surface of an object is occluded, it should no longer be considered the upper surface
|
||||
{
|
||||
const size_t occluded_pairs = num_facets_states * layers.size();
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, occluded_pairs), [&](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t pair_idx = range.begin(); pair_idx < range.end(); ++pair_idx) {
|
||||
const size_t extruder_idx = pair_idx / layers.size();
|
||||
const size_t layer_idx = pair_idx % layers.size();
|
||||
for (size_t extruder_idx = 0; extruder_idx < num_facets_states; ++extruder_idx) {
|
||||
for (size_t layer_idx = 0; layer_idx < layers.size(); ++layer_idx) {
|
||||
if (!top_raw[extruder_idx].empty() && !top_raw[extruder_idx][layer_idx].empty() && layer_idx + 1 < layers.size()) {
|
||||
top_raw[extruder_idx][layer_idx] = diff(top_raw[extruder_idx][layer_idx], input_expolygons[layer_idx + 1]);
|
||||
}
|
||||
@@ -1328,7 +1322,7 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
|
||||
bottom_raw[extruder_idx][layer_idx] = diff(bottom_raw[extruder_idx][layer_idx], input_expolygons[layer_idx - 1]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::vector<ExPolygons>> triangles_by_color_bottom(num_facets_states);
|
||||
@@ -1384,62 +1378,13 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
|
||||
return out;
|
||||
};
|
||||
|
||||
// Projects a painted top or bottom face `ex` of layer `layer_idx` onto the shell layers below or above it (in
|
||||
// `shell_layers`, nearest first), one more perimeter in on each, stopping at the first layer where nothing is left.
|
||||
// Only the slices within the deepest offset of `ex` (three times that with the miter joins) decide the result, so the
|
||||
// work is done per tile of `ex`'s ExPolygons on the slices cut to the tile's box grown by that much: the same result, but
|
||||
// each ClipperLib call stays the size of a tile rather than of a layer cut through a fine relief, and the tiles run in
|
||||
// parallel.
|
||||
const auto project_to_shells = [&input_expolygons](const ExPolygons &ex, size_t layer_idx, const std::vector<size_t> &shell_layers,
|
||||
const LayerColorStat &stat, std::vector<ExPolygons> &dst, size_t dst_offset) {
|
||||
std::vector<float> offsets(shell_layers.size());
|
||||
float offset = 0.f;
|
||||
for (size_t i = 0; i < shell_layers.size(); ++i) {
|
||||
//BBS: offset width should be 2*spacing to avoid too narrow area which has overlap of wall line
|
||||
offset -= (stat.extrusion_spacing + stat.extrusion_width);
|
||||
offsets[i] = offset;
|
||||
}
|
||||
if (offsets.empty())
|
||||
return;
|
||||
const coord_t reach = coord_t(std::ceil(DefaultMiterLimit * std::abs(offsets.back()))) + 10 * SCALED_EPSILON;
|
||||
const std::vector<ClipperUtils::ExPolygonsTile> tiles = ClipperUtils::tile_expolygons(ex, 16);
|
||||
// [shell layer][tile]
|
||||
std::vector<std::vector<ExPolygons>> shells(shell_layers.size(), std::vector<ExPolygons>(tiles.size()));
|
||||
tbb::parallel_for(size_t(0), tiles.size(), [&](size_t tile_idx) {
|
||||
const ClipperUtils::ExPolygonsTile &tile = tiles[tile_idx];
|
||||
const BoundingBox bbox = tile.bbox.inflated(reach);
|
||||
ExPolygons tile_ex;
|
||||
tile_ex.reserve(tile.members.size());
|
||||
for (size_t i : tile.members)
|
||||
tile_ex.emplace_back(ex[i]);
|
||||
Polygons layer_slices_trimmed = ClipperUtils::clip_clipper_polygons_with_subject_bbox(input_expolygons[layer_idx], bbox);
|
||||
for (size_t i = 0; i < shell_layers.size() && ! layer_slices_trimmed.empty(); ++i) {
|
||||
const ExPolygons trimmed = intersection_ex(layer_slices_trimmed, ClipperUtils::clip_clipper_polygons_with_subject_bbox(input_expolygons[shell_layers[i]], bbox));
|
||||
shells[i][tile_idx] = opening_ex(intersection_ex(tile_ex, offset_ex(trimmed, offsets[i])), stat.small_region_threshold);
|
||||
layer_slices_trimmed = to_polygons(trimmed);
|
||||
}
|
||||
});
|
||||
for (size_t i = 0; i < shell_layers.size(); ++i) {
|
||||
bool empty = true;
|
||||
for (ExPolygons &shell : shells[i])
|
||||
if (! shell.empty()) {
|
||||
append(dst[shell_layers[i] + dst_offset], std::move(shell));
|
||||
empty = false;
|
||||
}
|
||||
if (empty)
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers, granularity), [&granularity, &num_layers, &num_facets_states, &layer_color_stat, &top_raw, &triangles_by_color_top,
|
||||
&throw_on_cancel_callback, &bottom_raw, &triangles_by_color_bottom, &project_to_shells,
|
||||
&throw_on_cancel_callback, &input_expolygons, &bottom_raw, &triangles_by_color_bottom,
|
||||
&shell_triangles_by_color_top, &shell_triangles_by_color_bottom](const tbb::blocked_range<size_t> &range) {
|
||||
size_t group_idx = range.begin() / granularity;
|
||||
size_t layer_idx_offset = (group_idx & 1) * num_layers;
|
||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
|
||||
// Each colour writes only its own vectors, so the colours run in parallel: a painted top or bottom face
|
||||
// projects onto a single layer, which otherwise did all of its colours on one thread.
|
||||
tbb::parallel_for(size_t(0), size_t(num_facets_states), [&](size_t color_idx) {
|
||||
for (size_t color_idx = 0; color_idx < num_facets_states; ++color_idx) {
|
||||
throw_on_cancel_callback();
|
||||
LayerColorStat stat = layer_color_stat(layer_idx, color_idx);
|
||||
if (std::vector<Polygons> &top = top_raw[color_idx]; ! top.empty() && ! top[layer_idx].empty())
|
||||
@@ -1448,10 +1393,18 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
|
||||
top_ex = opening_ex(top_ex, stat.small_region_threshold);
|
||||
if (! top_ex.empty()) {
|
||||
append(triangles_by_color_top[color_idx][layer_idx + layer_idx_offset], top_ex);
|
||||
std::vector<size_t> shell_layers;
|
||||
for (int last_idx = int(layer_idx) - 1; last_idx > std::max(int(layer_idx - stat.top_shell_layers), int(0)); --last_idx)
|
||||
shell_layers.emplace_back(size_t(last_idx));
|
||||
project_to_shells(top_ex, layer_idx, shell_layers, stat, shell_triangles_by_color_top[color_idx], layer_idx_offset);
|
||||
float offset = 0.f;
|
||||
ExPolygons layer_slices_trimmed = input_expolygons[layer_idx];
|
||||
for (int last_idx = int(layer_idx) - 1; last_idx > std::max(int(layer_idx - stat.top_shell_layers), int(0)); --last_idx) {
|
||||
//BBS: offset width should be 2*spacing to avoid too narrow area which has overlap of wall line
|
||||
//offset -= stat.extrusion_width ;
|
||||
offset -= (stat.extrusion_spacing + stat.extrusion_width);
|
||||
layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]);
|
||||
ExPolygons last = opening_ex(intersection_ex(top_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold);
|
||||
if (last.empty())
|
||||
break;
|
||||
append(shell_triangles_by_color_top[color_idx][last_idx + layer_idx_offset], std::move(last));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (std::vector<Polygons> &bottom = bottom_raw[color_idx]; ! bottom.empty() && ! bottom[layer_idx].empty())
|
||||
@@ -1460,13 +1413,21 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
|
||||
bottom_ex = opening_ex(bottom_ex, stat.small_region_threshold);
|
||||
if (! bottom_ex.empty()) {
|
||||
append(triangles_by_color_bottom[color_idx][layer_idx + layer_idx_offset], bottom_ex);
|
||||
std::vector<size_t> shell_layers;
|
||||
for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + stat.bottom_shell_layers, num_layers); ++last_idx)
|
||||
shell_layers.emplace_back(last_idx);
|
||||
project_to_shells(bottom_ex, layer_idx, shell_layers, stat, shell_triangles_by_color_bottom[color_idx], layer_idx_offset);
|
||||
float offset = 0.f;
|
||||
ExPolygons layer_slices_trimmed = input_expolygons[layer_idx];
|
||||
for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + stat.bottom_shell_layers, num_layers); ++last_idx) {
|
||||
//BBS: offset width should be 2*spacing to avoid too narrow area which has overlap of wall line
|
||||
//offset -= stat.extrusion_width;
|
||||
offset -= (stat.extrusion_spacing + stat.extrusion_width);
|
||||
layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]);
|
||||
ExPolygons last = opening_ex(intersection_ex(bottom_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold);
|
||||
if (last.empty())
|
||||
break;
|
||||
append(shell_triangles_by_color_bottom[color_idx][last_idx + layer_idx_offset], std::move(last));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1476,25 +1437,22 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
|
||||
&shell_triangles_by_color_top, &shell_triangles_by_color_bottom](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
|
||||
throw_on_cancel_callback();
|
||||
// The per-colour unions below are independent of each other, so they run in parallel (a painted top or
|
||||
// bottom face puts all of its colours on one layer); whatever combines the colours stays in colour order.
|
||||
const auto merge_colour_union = [&](size_t color_idx) {
|
||||
ExPolygons painted_exploys;
|
||||
for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx) {
|
||||
auto &self = triangles_by_color_merged[color_idx][layer_idx];
|
||||
append(self, std::move(triangles_by_color_bottom[color_idx][layer_idx]));
|
||||
append(self, std::move(triangles_by_color_bottom[color_idx][layer_idx + num_layers]));
|
||||
append(self, std::move(triangles_by_color_top[color_idx][layer_idx]));
|
||||
append(self, std::move(triangles_by_color_top[color_idx][layer_idx + num_layers]));
|
||||
self = union_ex(self);
|
||||
};
|
||||
tbb::parallel_for(size_t(0), triangles_by_color_merged.size(), merge_colour_union);
|
||||
|
||||
ExPolygons painted_exploys;
|
||||
for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx)
|
||||
append(painted_exploys, triangles_by_color_merged[color_idx][layer_idx]);
|
||||
append(painted_exploys, self);
|
||||
}
|
||||
|
||||
painted_exploys = union_ex(painted_exploys);
|
||||
|
||||
//BBS: merge the top and bottom shell layers
|
||||
tbb::parallel_for(size_t(0), triangles_by_color_merged.size(), [&](size_t color_idx) {
|
||||
for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx) {
|
||||
auto &self = triangles_by_color_merged[color_idx][layer_idx];
|
||||
|
||||
auto top_area = diff_ex(union_ex(shell_triangles_by_color_top[color_idx][layer_idx],
|
||||
@@ -1508,7 +1466,7 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
|
||||
append(self, top_area);
|
||||
append(self, bottom_area);
|
||||
self = union_ex(self);
|
||||
});
|
||||
}
|
||||
// Trim one region by the other if some of the regions overlap.
|
||||
ExPolygons painted_regions;
|
||||
for (size_t color_idx = 1; color_idx < triangles_by_color_merged.size(); ++color_idx) {
|
||||
@@ -1875,69 +1833,7 @@ static void remove_multiple_edges_in_vertices(MMU_Graph &graph, const std::vecto
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Finds the islands (layer ExPolygons) a region piece overlaps. A top or bottom region is projected from the neighbouring
|
||||
// layers and may reach past the island it belongs to, or over several islands.
|
||||
class IslandLocator
|
||||
{
|
||||
public:
|
||||
explicit IslandLocator(const ExPolygons &islands) : m_islands(islands)
|
||||
{
|
||||
m_bboxes.reserve(islands.size());
|
||||
for (const ExPolygon &island : islands) {
|
||||
m_bboxes.emplace_back(get_extents(island));
|
||||
m_extent.merge(m_bboxes.back());
|
||||
}
|
||||
if (!m_extent.defined)
|
||||
return;
|
||||
const Point size = m_extent.size();
|
||||
m_cell_w = std::max<coord_t>(1, size.x() / GRID + 1);
|
||||
m_cell_h = std::max<coord_t>(1, size.y() / GRID + 1);
|
||||
m_grid.assign(GRID * GRID, {});
|
||||
for (size_t i = 0; i < m_bboxes.size(); ++i)
|
||||
for_cells(m_bboxes[i], [&](int cell) { m_grid[cell].emplace_back(i); });
|
||||
}
|
||||
|
||||
void find(const ExPolygon &piece, std::vector<size_t> &out) const
|
||||
{
|
||||
out.clear();
|
||||
const BoundingBox bbox = get_extents(piece);
|
||||
if (!m_extent.defined || !m_extent.overlap(bbox))
|
||||
return;
|
||||
for_cells(bbox, [&](int cell) {
|
||||
for (size_t i : m_grid[cell])
|
||||
if (m_bboxes[i].overlap(bbox))
|
||||
out.emplace_back(i);
|
||||
});
|
||||
sort_remove_duplicates(out);
|
||||
if (out.size() > 1)
|
||||
out.erase(std::remove_if(out.begin(), out.end(), [&](size_t i) {
|
||||
const BoundingBox common(m_bboxes[i].min.cwiseMax(bbox.min), m_bboxes[i].max.cwiseMin(bbox.max));
|
||||
return intersection(ClipperUtils::clip_clipper_polygons_with_subject_bbox(piece, common.inflated(SCALED_EPSILON)),
|
||||
ClipperUtils::clip_clipper_polygons_with_subject_bbox(m_islands[i], common.inflated(SCALED_EPSILON))).empty();
|
||||
}), out.end());
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr int GRID = 64;
|
||||
template<typename Fn> void for_cells(const BoundingBox &bb, Fn &&fn) const
|
||||
{
|
||||
const int x0 = std::clamp(int((bb.min.x() - m_extent.min.x()) / m_cell_w), 0, GRID - 1), x1 = std::clamp(int((bb.max.x() - m_extent.min.x()) / m_cell_w), 0, GRID - 1);
|
||||
const int y0 = std::clamp(int((bb.min.y() - m_extent.min.y()) / m_cell_h), 0, GRID - 1), y1 = std::clamp(int((bb.max.y() - m_extent.min.y()) / m_cell_h), 0, GRID - 1);
|
||||
for (int y = y0; y <= y1; ++y)
|
||||
for (int x = x0; x <= x1; ++x)
|
||||
fn(y * GRID + x);
|
||||
}
|
||||
|
||||
const ExPolygons &m_islands;
|
||||
std::vector<BoundingBox> m_bboxes;
|
||||
BoundingBox m_extent;
|
||||
coord_t m_cell_w = 1, m_cell_h = 1;
|
||||
std::vector<std::vector<size_t>> m_grid;
|
||||
};
|
||||
|
||||
static std::vector<std::vector<ExPolygons>> merge_segmented_layers(const std::vector<ExPolygons> &input_expolygons,
|
||||
const std::vector<std::vector<ExPolygons>> &segmented_regions,
|
||||
static std::vector<std::vector<ExPolygons>> merge_segmented_layers(const std::vector<std::vector<ExPolygons>> &segmented_regions,
|
||||
std::vector<std::vector<ExPolygons>> &&top_and_bottom_layers,
|
||||
const size_t num_facets_states,
|
||||
const std::function<void()> &throw_on_cancel_callback)
|
||||
@@ -1948,91 +1844,33 @@ static std::vector<std::vector<ExPolygons>> merge_segmented_layers(const std::ve
|
||||
assert(!top_and_bottom_layers.size() || num_facets_states == top_and_bottom_layers.size());
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Merging segmented layers in parallel - Begin";
|
||||
// Every region of a layer is merged together with the regions of the islands it overlaps, and the islands are further
|
||||
// apart than the dimple removal below reaches, so this gives the same result as merging the layer at once. On a layer
|
||||
// cut through a fine relief every region shares thousands of hole contours with every other, and ClipperLib, splitting
|
||||
// and re-linking one huge polygon over and over, took anything up to half an hour for a layer; per island each operation
|
||||
// stays the size of the island, and the islands run in parallel.
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&](const tbb::blocked_range<size_t> &range) {
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&segmented_regions, &top_and_bottom_layers, &segmented_regions_merged, &num_facets_states, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
|
||||
assert(segmented_regions[layer_idx].size() == num_facets_states);
|
||||
throw_on_cancel_callback();
|
||||
// Group the islands joined by a region overlapping several of them; the last group takes the regions lying
|
||||
// outside every island.
|
||||
const ExPolygons &islands = input_expolygons[layer_idx];
|
||||
const IslandLocator locator(islands);
|
||||
std::vector<size_t> parent(islands.size() + 1);
|
||||
std::iota(parent.begin(), parent.end(), 0);
|
||||
const auto root = [&parent](size_t i) {
|
||||
while (parent[i] != i)
|
||||
i = parent[i] = parent[parent[i]];
|
||||
return i;
|
||||
};
|
||||
// Islands of every piece: side regions of colours 1.., then top/bottom regions of colours 0..
|
||||
std::vector<const ExPolygon *> pieces;
|
||||
for (size_t extruder_id = 1; extruder_id < num_facets_states; ++extruder_id)
|
||||
for (const ExPolygon &piece : segmented_regions[layer_idx][extruder_id])
|
||||
pieces.emplace_back(&piece);
|
||||
if (!top_and_bottom_layers.empty())
|
||||
for (size_t color_idx = 0; color_idx < num_facets_states; ++color_idx)
|
||||
for (const ExPolygon &piece : top_and_bottom_layers[color_idx][layer_idx])
|
||||
pieces.emplace_back(&piece);
|
||||
std::vector<std::vector<size_t>> overlapped(pieces.size());
|
||||
tbb::parallel_for(size_t(0), pieces.size(), [&](size_t i) { locator.find(*pieces[i], overlapped[i]); });
|
||||
std::vector<size_t> piece_island(pieces.size());
|
||||
for (size_t i = 0; i < pieces.size(); ++i) {
|
||||
piece_island[i] = overlapped[i].empty() ? islands.size() : overlapped[i].front();
|
||||
for (size_t island : overlapped[i])
|
||||
parent[root(island)] = root(piece_island[i]);
|
||||
}
|
||||
std::vector<size_t> bucket_of(parent.size(), size_t(-1));
|
||||
size_t num_buckets = 0;
|
||||
for (size_t i = 0; i < parent.size(); ++i)
|
||||
if (size_t &b = bucket_of[root(i)]; b == size_t(-1))
|
||||
b = num_buckets++;
|
||||
|
||||
// [bucket][colour]
|
||||
std::vector<std::vector<ExPolygons>> sides(num_buckets, std::vector<ExPolygons>(num_facets_states));
|
||||
std::vector<std::vector<ExPolygons>> tops(num_buckets, std::vector<ExPolygons>(num_facets_states));
|
||||
size_t piece_idx = 0;
|
||||
for (size_t extruder_id = 1; extruder_id < num_facets_states; ++extruder_id)
|
||||
for (const ExPolygon &piece : segmented_regions[layer_idx][extruder_id])
|
||||
sides[bucket_of[root(piece_island[piece_idx++])]][extruder_id].emplace_back(piece);
|
||||
if (!top_and_bottom_layers.empty())
|
||||
for (size_t color_idx = 0; color_idx < num_facets_states; ++color_idx)
|
||||
for (const ExPolygon &piece : top_and_bottom_layers[color_idx][layer_idx])
|
||||
tops[bucket_of[root(piece_island[piece_idx++])]][color_idx].emplace_back(piece);
|
||||
|
||||
// Side regions minus the top/bottom regions of every colour.
|
||||
std::vector<std::vector<ExPolygons>> merged(num_buckets, std::vector<ExPolygons>(num_facets_states));
|
||||
tbb::parallel_for(size_t(0), num_buckets, [&](size_t bucket) {
|
||||
Polygons tops_all;
|
||||
for (const ExPolygons &t : tops[bucket])
|
||||
polygons_append(tops_all, t);
|
||||
for (size_t extruder_id = 1; extruder_id < num_facets_states; ++extruder_id)
|
||||
if (!sides[bucket][extruder_id].empty())
|
||||
merged[bucket][extruder_id] = tops_all.empty() ? std::move(sides[bucket][extruder_id]) :
|
||||
diff_ex_by_piece(sides[bucket][extruder_id], tops_all);
|
||||
});
|
||||
|
||||
// Then this colour's top/bottom regions, with the dimples removed (#7235) when the layer has side regions left.
|
||||
// Zero is skipped because it is the default color of the volume
|
||||
for (size_t extruder_id = 1; extruder_id < num_facets_states; ++extruder_id) {
|
||||
if (top_and_bottom_layers.empty() || top_and_bottom_layers[extruder_id][layer_idx].empty()) {
|
||||
for (size_t bucket = 0; bucket < num_buckets; ++bucket)
|
||||
append(segmented_regions_merged[layer_idx][extruder_id - 1], std::move(merged[bucket][extruder_id]));
|
||||
continue;
|
||||
throw_on_cancel_callback();
|
||||
if (!segmented_regions[layer_idx][extruder_id].empty()) {
|
||||
ExPolygons segmented_regions_trimmed = segmented_regions[layer_idx][extruder_id];
|
||||
if (!top_and_bottom_layers.empty()) {
|
||||
for (const std::vector<ExPolygons> &top_and_bottom_by_extruder : top_and_bottom_layers) {
|
||||
if (!top_and_bottom_by_extruder[layer_idx].empty() && !segmented_regions_trimmed.empty()) {
|
||||
segmented_regions_trimmed = diff_ex(segmented_regions_trimmed, top_and_bottom_by_extruder[layer_idx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
segmented_regions_merged[layer_idx][extruder_id - 1] = std::move(segmented_regions_trimmed);
|
||||
}
|
||||
|
||||
if (!top_and_bottom_layers.empty() && !top_and_bottom_layers[extruder_id][layer_idx].empty()) {
|
||||
bool was_top_and_bottom_empty = segmented_regions_merged[layer_idx][extruder_id - 1].empty();
|
||||
append(segmented_regions_merged[layer_idx][extruder_id - 1], top_and_bottom_layers[extruder_id][layer_idx]);
|
||||
|
||||
// Remove dimples (#7235) appearing after merging side segmentation of the model with tops and bottoms painted layers.
|
||||
if (!was_top_and_bottom_empty)
|
||||
segmented_regions_merged[layer_idx][extruder_id - 1] = offset2_ex(union_ex(segmented_regions_merged[layer_idx][extruder_id - 1]), float(SCALED_EPSILON), -float(SCALED_EPSILON));
|
||||
}
|
||||
bool was_top_and_bottom_empty = true;
|
||||
for (size_t bucket = 0; bucket < num_buckets && was_top_and_bottom_empty; ++bucket)
|
||||
was_top_and_bottom_empty = merged[bucket][extruder_id].empty();
|
||||
tbb::parallel_for(size_t(0), num_buckets, [&](size_t bucket) {
|
||||
ExPolygons ®ion = merged[bucket][extruder_id];
|
||||
append(region, tops[bucket][extruder_id]);
|
||||
if (!was_top_and_bottom_empty && !region.empty())
|
||||
region = offset2_ex(union_ex(region), float(SCALED_EPSILON), -float(SCALED_EPSILON));
|
||||
});
|
||||
for (size_t bucket = 0; bucket < num_buckets; ++bucket)
|
||||
append(segmented_regions_merged[layer_idx][extruder_id - 1], std::move(merged[bucket][extruder_id]));
|
||||
}
|
||||
}
|
||||
}); // end of parallel_for
|
||||
@@ -2319,56 +2157,16 @@ std::vector<std::vector<ExPolygons>> segmentation_by_painting(const PrintObject
|
||||
|
||||
assert(!color_poly.empty());
|
||||
assert(!color_poly.front().empty());
|
||||
// Each island (an ExPolygon with its holes) is segmented on its own. Any point of an island is closer to
|
||||
// that island's contours than to any other island's - the way out crosses its own boundary first - so its
|
||||
// Voronoi cells, and with them its colour regions, depend on nothing else. A layer cut through a fine relief
|
||||
// has thousands of islands, and one Voronoi diagram over all of them degenerated into overlapping regions
|
||||
// that every boolean afterwards had to untangle. Per island the diagrams stay small and the islands run in
|
||||
// parallel; an island in a single colour needs no diagram at all.
|
||||
const ExPolygons &islands = input_expolygons[layer_idx];
|
||||
std::vector<std::pair<size_t, size_t>> island_contours(islands.size()); // [first, last) into color_poly
|
||||
{
|
||||
// The same order EdgeGrid::Grid::create() lists the contours in, and so colorize_contours().
|
||||
size_t idx = 0;
|
||||
for (size_t island_idx = 0; island_idx < islands.size(); ++island_idx) {
|
||||
const size_t first = idx;
|
||||
if (!islands[island_idx].contour.empty())
|
||||
++idx;
|
||||
for (const Polygon &hole : islands[island_idx].holes)
|
||||
if (!hole.empty())
|
||||
++idx;
|
||||
island_contours[island_idx] = {first, idx};
|
||||
}
|
||||
assert(idx == color_poly.size());
|
||||
}
|
||||
std::vector<std::vector<ExPolygons>> island_regions(islands.size());
|
||||
tbb::parallel_for(size_t(0), islands.size(), [&](size_t island_idx) {
|
||||
const auto [first, last] = island_contours[island_idx];
|
||||
if (first == last)
|
||||
return;
|
||||
const std::vector<ColoredLines> island_poly(color_poly.begin() + first, color_poly.begin() + last);
|
||||
std::vector<ExPolygons> ®ions = island_regions[island_idx];
|
||||
if (has_layer_only_one_color(island_poly)) {
|
||||
regions.assign(num_facets_states, ExPolygons());
|
||||
regions[size_t(island_poly.front().front().color)].emplace_back(islands[island_idx]);
|
||||
if (has_layer_only_one_color(color_poly)) {
|
||||
// If the whole layer is painted using the same color, it is not needed to construct a Voronoi diagram for the segmentation of this layer.
|
||||
segmented_regions[layer_idx][size_t(color_poly.front().front().color)] = input_expolygons[layer_idx];
|
||||
} else {
|
||||
MMU_Graph graph = build_graph(layer_idx, island_poly);
|
||||
remove_multiple_edges_in_vertices(graph, island_poly);
|
||||
MMU_Graph graph = build_graph(layer_idx, color_poly);
|
||||
remove_multiple_edges_in_vertices(graph, color_poly);
|
||||
graph.remove_nodes_with_one_arc();
|
||||
regions = extract_colored_segments(graph, num_facets_states);
|
||||
// The faces of one colour tile it without overlapping; merged here, where an island is small,
|
||||
// every later boolean gets a few regions instead of thousands of faces sharing their edges. An
|
||||
// island with many holes keeps its faces: merged, each colour would be one region with thousands
|
||||
// of holes, and subtracting from that is far slower than from the faces one at a time.
|
||||
if (island_poly.size() <= 64)
|
||||
for (ExPolygons &faces : regions)
|
||||
if (faces.size() > 1)
|
||||
faces = union_ex(faces);
|
||||
segmented_regions[layer_idx] = extract_colored_segments(graph, num_facets_states);
|
||||
//segmented_regions[layer_idx] = extract_colored_segments(color_poly, num_extruders, layer_idx);
|
||||
}
|
||||
});
|
||||
for (std::vector<ExPolygons> ®ions : island_regions)
|
||||
for (size_t color_idx = 0; color_idx < regions.size(); ++color_idx)
|
||||
append(segmented_regions[layer_idx][color_idx], std::move(regions[color_idx]));
|
||||
|
||||
#ifdef MM_SEGMENTATION_DEBUG_REGIONS
|
||||
export_regions_to_svg(debug_out_path("3-mm-regions-sides-%d-%d.svg", layer_idx, iRun), segmented_regions[layer_idx], input_expolygons[layer_idx]);
|
||||
@@ -2391,7 +2189,7 @@ std::vector<std::vector<ExPolygons>> segmentation_by_painting(const PrintObject
|
||||
throw_on_cancel_callback();
|
||||
}
|
||||
|
||||
std::vector<std::vector<ExPolygons>> segmented_regions_merged = merge_segmented_layers(input_expolygons, segmented_regions, std::move(top_and_bottom_layers), num_facets_states, throw_on_cancel_callback);
|
||||
std::vector<std::vector<ExPolygons>> segmented_regions_merged = merge_segmented_layers(segmented_regions, std::move(top_and_bottom_layers), num_facets_states, throw_on_cancel_callback);
|
||||
throw_on_cancel_callback();
|
||||
|
||||
#ifdef MM_SEGMENTATION_DEBUG_REGIONS
|
||||
|
||||
@@ -19,15 +19,11 @@ public:
|
||||
|
||||
MultiPoint() {}
|
||||
MultiPoint(const MultiPoint &other) : points(other.points) {}
|
||||
MultiPoint(MultiPoint &&other) noexcept : points(std::move(other.points)) {}
|
||||
MultiPoint(MultiPoint &&other) : points(std::move(other.points)) {}
|
||||
MultiPoint(std::initializer_list<Point> list) : points(list) {}
|
||||
explicit MultiPoint(const Points &_points) : points(_points) {}
|
||||
// Without it, the derived classes' move constructors passing std::move(points) here copied them, which
|
||||
// also means a moved-from Polygon or Polyline is now really empty where it used to silently keep its
|
||||
// points: a use-after-move anywhere in the tree that happened to work before now sees nothing.
|
||||
explicit MultiPoint(Points &&_points) noexcept : points(std::move(_points)) {}
|
||||
MultiPoint& operator=(const MultiPoint &other) { points = other.points; return *this; }
|
||||
MultiPoint& operator=(MultiPoint &&other) noexcept { points = std::move(other.points); return *this; }
|
||||
MultiPoint& operator=(MultiPoint &&other) { points = std::move(other.points); return *this; }
|
||||
virtual ~MultiPoint() = default;
|
||||
void scale(double factor);
|
||||
void scale(double factor_x, double factor_y);
|
||||
|
||||
@@ -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
|
||||
@@ -17,8 +17,6 @@
|
||||
#include <cassert>
|
||||
#include <unordered_set>
|
||||
#include <thread>
|
||||
#include <tbb/blocked_range.h>
|
||||
#include <tbb/parallel_for.h>
|
||||
#include "libslic3r/AABBTreeLines.hpp"
|
||||
#include "Print.hpp"
|
||||
static const int overhang_sampling_number = 6;
|
||||
@@ -2483,22 +2481,7 @@ void PerimeterGenerator::process_arachne()
|
||||
const bool only_one_wall_first_layer = this->config->only_one_wall_first_layer && has_bottom_shell_layers(*this->config);
|
||||
// we need to process each island separately because we might have different
|
||||
// extra perimeters for each one
|
||||
// Each island is independent up to its outputs, so they are generated in parallel - a layer split into thousands
|
||||
// of islands (e.g. by colour painting) otherwise ran on one thread - and the outputs are then committed in the
|
||||
// original island order, which is what the extra overhang perimeters (applied to the last island's loops and to
|
||||
// all fill surfaces so far) depend on.
|
||||
struct ArachneSurfaceResult
|
||||
{
|
||||
ExtrusionEntityCollection loops;
|
||||
bool has_loops = false;
|
||||
ExPolygons infill;
|
||||
ExPolygons no_overlap;
|
||||
};
|
||||
std::vector<ArachneSurfaceResult> results(all_surfaces.size());
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, all_surfaces.size()), [&](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t surface_idx = range.begin(); surface_idx < range.end(); ++surface_idx) {
|
||||
const Surface &surface = all_surfaces[surface_idx];
|
||||
ArachneSurfaceResult &result = results[surface_idx];
|
||||
for (const Surface& surface : all_surfaces) {
|
||||
coord_t bead_width_0 = ext_perimeter_spacing;
|
||||
// detect how many perimeters must be generated for this island
|
||||
int loop_number = this->config->wall_loops + surface.extra_perimeters - 1; // 0-indexed loops
|
||||
@@ -2762,10 +2745,10 @@ void PerimeterGenerator::process_arachne()
|
||||
// Get searching thresholds. For an external perimeter we take the external perimeter spacing/2 plus the internal perimeter spacing/2 and expand by the factor
|
||||
// rounding errors. When precise wall is enabled, the external perimeter full spacing is used.
|
||||
coord_t threshold_external = (apply_precise_outer_wall)
|
||||
// Precise outer wall: use the full external spacing
|
||||
// Precise outer wall ⇒ use “full external spacing”
|
||||
? ( this->ext_perimeter_flow.scaled_spacing()
|
||||
+ this->perimeter_flow.scaled_spacing()/2.0 )
|
||||
// Normal: half ext spacing plus half int spacing
|
||||
// Normal ⇒ half ext spacing + half int spacing
|
||||
: ( this->ext_perimeter_flow.scaled_spacing()/2.0
|
||||
+ this->perimeter_flow.scaled_spacing()/2.0 );
|
||||
|
||||
@@ -2855,8 +2838,7 @@ void PerimeterGenerator::process_arachne()
|
||||
this->config->overhang_reverse_internal_only);
|
||||
}
|
||||
defer_unsupported_loops(*this, extrusion_coll);
|
||||
result.loops = std::move(extrusion_coll);
|
||||
result.has_loops = true;
|
||||
this->loops->append(extrusion_coll);
|
||||
}
|
||||
|
||||
const coord_t spacing = (perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing;
|
||||
@@ -2899,7 +2881,9 @@ void PerimeterGenerator::process_arachne()
|
||||
if (!top_expolygons.empty()) {
|
||||
infill_exp = union_ex(infill_exp, offset_ex(top_expolygons, double(top_inset)));
|
||||
}
|
||||
result.infill = std::move(infill_exp);
|
||||
this->fill_surfaces->append(infill_exp, stInternal);
|
||||
|
||||
apply_extra_perimeters(infill_exp);
|
||||
|
||||
// BBS: get the no-overlap infill expolygons
|
||||
{
|
||||
@@ -2910,18 +2894,9 @@ void PerimeterGenerator::process_arachne()
|
||||
float(+min_perimeter_infill_spacing / 2.));
|
||||
if (!top_expolygons.empty())
|
||||
polyWithoutOverlap = union_ex(polyWithoutOverlap, top_expolygons);
|
||||
result.no_overlap = std::move(polyWithoutOverlap);
|
||||
this->fill_no_overlap->insert(this->fill_no_overlap->end(), polyWithoutOverlap.begin(), polyWithoutOverlap.end());
|
||||
}
|
||||
}
|
||||
});
|
||||
for (ArachneSurfaceResult &result : results) {
|
||||
if (result.has_loops)
|
||||
// Moved, not copied: append(const ExtrusionEntity &) clones the whole wall tree of the island.
|
||||
this->loops->append(std::move(result.loops));
|
||||
this->fill_surfaces->append(result.infill, stInternal);
|
||||
apply_extra_perimeters(result.infill);
|
||||
append(*this->fill_no_overlap, std::move(result.no_overlap));
|
||||
}
|
||||
}
|
||||
|
||||
bool PerimeterGeneratorLoop::is_internal_contour() const
|
||||
|
||||
@@ -27,7 +27,7 @@ public:
|
||||
explicit Polygon(const Points &points) : MultiPoint(points) {}
|
||||
Polygon(std::initializer_list<Point> points) : MultiPoint(points) {}
|
||||
Polygon(const Polygon &other) : MultiPoint(other.points) {}
|
||||
Polygon(Polygon &&other) noexcept : MultiPoint(std::move(other.points)) {}
|
||||
Polygon(Polygon &&other) : MultiPoint(std::move(other.points)) {}
|
||||
static Polygon new_scale(const std::vector<Vec2d> &points) {
|
||||
Polygon pgn;
|
||||
pgn.points.reserve(points.size());
|
||||
@@ -36,7 +36,7 @@ public:
|
||||
return pgn;
|
||||
}
|
||||
Polygon& operator=(const Polygon &other) { points = other.points; return *this; }
|
||||
Polygon& operator=(Polygon &&other) noexcept { points = std::move(other.points); return *this; }
|
||||
Polygon& operator=(Polygon &&other) { points = std::move(other.points); return *this; }
|
||||
|
||||
Point& operator[](Points::size_type idx) { return this->points[idx]; }
|
||||
const Point& operator[](Points::size_type idx) const { return this->points[idx]; }
|
||||
|
||||
@@ -20,7 +20,7 @@ class Polyline : public MultiPoint {
|
||||
public:
|
||||
Polyline() {};
|
||||
Polyline(const Polyline& other) : MultiPoint(other.points), fitting_result(other.fitting_result) {}
|
||||
Polyline(Polyline &&other) noexcept : MultiPoint(std::move(other.points)), fitting_result(std::move(other.fitting_result)) {}
|
||||
Polyline(Polyline &&other) : MultiPoint(std::move(other.points)), fitting_result(std::move(other.fitting_result)) {}
|
||||
Polyline(std::initializer_list<Point> list) : MultiPoint(list) {
|
||||
fitting_result.clear();
|
||||
}
|
||||
@@ -41,7 +41,7 @@ public:
|
||||
fitting_result = other.fitting_result;
|
||||
return *this;
|
||||
}
|
||||
Polyline& operator=(Polyline&& other) noexcept {
|
||||
Polyline& operator=(Polyline&& other) {
|
||||
points = std::move(other.points);
|
||||
fitting_result = std::move(other.fitting_result);
|
||||
return *this;
|
||||
|
||||
+236
-155
@@ -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
|
||||
@@ -506,10 +508,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 +624,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();
|
||||
}
|
||||
@@ -1669,6 +1671,169 @@ std::string PresetCollection::canonical_preset_name(const std::string &name, con
|
||||
return get_preset_canonical_name(parsed.bare, origin);
|
||||
}
|
||||
|
||||
PresetCollection::UserPresetLoad PresetCollection::resolve_user_preset(
|
||||
const boost::filesystem::path &file, const std::string &canonical_name,
|
||||
const PresetOrigin &load_origin, ForwardCompatibilitySubstitutionRule substitution_rule,
|
||||
const std::string &extruder_id_name, const std::string &extruder_variant_name,
|
||||
std::set<std::string> *key_set1, std::set<std::string> *key_set2) const
|
||||
{
|
||||
UserPresetLoad out;
|
||||
out.preset = Preset(m_type, canonical_name, false);
|
||||
Preset &preset = out.preset;
|
||||
preset.bundle_id = load_origin.bundle_id;
|
||||
preset.file = file.string();
|
||||
// Load the preset file, apply preset values on top of defaults.
|
||||
try {
|
||||
fs::path idx_path(preset.file);
|
||||
idx_path.replace_extension(".info");
|
||||
if (fs::exists(idx_path)) {
|
||||
out.info_file = idx_path.string();
|
||||
preset.load_info(out.info_file);
|
||||
}
|
||||
DynamicPrintConfig config;
|
||||
//BBS: change to json format
|
||||
//ConfigSubstitutions config_substitutions = config.load_from_ini(preset.file, substitution_rule);
|
||||
std::map<std::string, std::string> key_values;
|
||||
std::string reason;
|
||||
ConfigSubstitutions config_substitutions = config.load_from_json(preset.file, substitution_rule, key_values, reason);
|
||||
if (! config_substitutions.empty())
|
||||
out.substitutions.push_back({ preset.name, m_type, PresetConfigSubstitutions::Source::UserFile, preset.file, std::move(config_substitutions) });
|
||||
if (!reason.empty()) {
|
||||
out.discard_file = true;
|
||||
out.errors.push_back((boost::format("parse config %1% failed") % preset.file).str());
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string version_str = key_values[BBL_JSON_KEY_VERSION];
|
||||
boost::optional<Semver> version = Semver::parse(version_str);
|
||||
if (!version) return out;
|
||||
preset.version = *version;
|
||||
|
||||
if (key_values.find(BBL_JSON_KEY_FILAMENT_ID) != key_values.end())
|
||||
preset.filament_id = key_values[BBL_JSON_KEY_FILAMENT_ID];
|
||||
if (key_values.find(BBL_JSON_KEY_DESCRIPTION) != key_values.end())
|
||||
preset.description = key_values[BBL_JSON_KEY_DESCRIPTION];
|
||||
if (key_values.find(BBL_JSON_KEY_INSTANTIATION) != key_values.end())
|
||||
preset.is_visible = key_values[BBL_JSON_KEY_INSTANTIATION] != "false";
|
||||
|
||||
//Orca: find and use the inherit config as the base
|
||||
const Preset* inherit_preset = nullptr;
|
||||
ConfigOption* inherits_config = config.option(BBL_JSON_KEY_INHERITS);
|
||||
|
||||
// check inherits_config
|
||||
if (inherits_config) {
|
||||
ConfigOptionString * option_str = dynamic_cast<ConfigOptionString *> (inherits_config);
|
||||
std::string inherits_value = option_str->value;
|
||||
// Orca: try to find if the parent preset has been renamed
|
||||
inherit_preset = this->find_preset2(inherits_value);
|
||||
Preset::normalize_inherits(config, inherit_preset);
|
||||
}
|
||||
const Preset& default_preset = this->default_preset_for(config);
|
||||
if (inherit_preset) {
|
||||
preset.config = inherit_preset->config;
|
||||
preset.filament_id = inherit_preset->filament_id;
|
||||
extend_default_config_length(config, false, {});
|
||||
preset.config.update_diff_values_to_child_config(config, extruder_id_name, extruder_variant_name, *key_set1, *key_set2);
|
||||
}
|
||||
else {
|
||||
auto inherits_config2 = dynamic_cast<ConfigOptionString *>(inherits_config);
|
||||
if ((inherits_config2 && !inherits_config2->value.empty())) {
|
||||
out.errors.push_back((boost::format("can not find parent %1% for config %2%!") % inherits_config2->value % preset.file).str());
|
||||
return out;
|
||||
}
|
||||
// We support custom root preset now
|
||||
// Find a default preset for the config. The PrintPresetCollection provides different default preset based on the "printer_technology" field.
|
||||
preset.config = default_preset.config;
|
||||
preset.config.apply(std::move(config));
|
||||
extend_default_config_length(preset.config, true, default_preset.config);
|
||||
}
|
||||
|
||||
Preset::normalize(preset.config);
|
||||
// Report configuration fields, which are misplaced into a wrong group.
|
||||
std::string incorrect_keys = Preset::remove_invalid_keys(preset.config, default_preset.config);
|
||||
if (!incorrect_keys.empty())
|
||||
out.errors.push_back("Error in a preset file: The preset \"" + preset.file +
|
||||
"\" contains the following incorrect keys: " + incorrect_keys + ", which were removed");
|
||||
|
||||
if (preset.type == Preset::TYPE_FILAMENT && preset.is_user() && preset.inherits().empty()) {
|
||||
auto compatible_printers = dynamic_cast<ConfigOptionStrings *>(preset.config.option("compatible_printers", true));
|
||||
if (compatible_printers && compatible_printers->values.empty()) {
|
||||
size_t at_pos = canonical_name.find('@');
|
||||
if (at_pos != std::string::npos && at_pos + 1 < canonical_name.length()) {
|
||||
compatible_printers->values.push_back(canonical_name.substr(at_pos + 1));
|
||||
out.save_compatible_printers = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
preset.loaded = true;
|
||||
out.complete = true;
|
||||
} catch (const std::ifstream::failure &err) {
|
||||
out.discard_file = true;
|
||||
out.errors.push_back((boost::format("The user-config cannot be loaded: %1%. Reason: %2%") % preset.file % err.what()).str());
|
||||
//throw Slic3r::RuntimeError(std::string("The selected preset cannot be loaded: ") + preset.file + "\n\tReason: " + err.what());
|
||||
} catch (const std::runtime_error &err) {
|
||||
out.discard_file = true;
|
||||
out.errors.push_back((boost::format("Failed loading the user-config file: %1%. Reason: %2%") % preset.file % err.what()).str());
|
||||
//throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + preset.file + "\n\tReason: " + err.what());
|
||||
}
|
||||
out.install = true;
|
||||
return out;
|
||||
}
|
||||
|
||||
void PresetCollection::commit_user_preset(UserPresetLoad &&loaded, std::deque<Preset> &presets_loaded,
|
||||
PresetsConfigSubstitutions &substitutions,
|
||||
const std::function<void(Preset&)> &preset_loaded_fn,
|
||||
bool read_only)
|
||||
{
|
||||
Preset &preset = loaded.preset;
|
||||
if (! loaded.info_file.empty())
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " load info from: " << loaded.info_file << " and base_id: " << preset.base_id;
|
||||
append(substitutions, std::move(loaded.substitutions));
|
||||
for (const std::string &error : loaded.errors) {
|
||||
++m_errors;
|
||||
BOOST_LOG_TRIVIAL(error) << error;
|
||||
}
|
||||
if (loaded.discard_file && !read_only) {
|
||||
fs::path file_path(loaded.preset.file);
|
||||
if (fs::exists(file_path))
|
||||
fs::remove(file_path);
|
||||
file_path.replace_extension(".info");
|
||||
if (fs::exists(file_path))
|
||||
fs::remove(file_path);
|
||||
}
|
||||
if (!loaded.install)
|
||||
return;
|
||||
|
||||
if (loaded.complete) {
|
||||
if (loaded.save_compatible_printers) {
|
||||
// A filesystem error from the rewrite is counted, and the preset still loads.
|
||||
try {
|
||||
if (!read_only)
|
||||
preset.save(nullptr);
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " added compatible_printers for preset: " << preset.name;
|
||||
} catch (const std::runtime_error &err) {
|
||||
++m_errors;
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " can not write compatible_printers back to " << preset.file << ": " << err.what();
|
||||
}
|
||||
}
|
||||
//BBS: add some workaround for previous incorrect settings
|
||||
if ((!preset.setting_id.empty())&&(preset.setting_id == preset.base_id))
|
||||
preset.setting_id.clear();
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " load preset: " << preset.name << " and filament_id: " << preset.filament_id << " and base_id: " << preset.base_id;
|
||||
//BBS: add config related logs
|
||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(", preset type %1%, name %2%, path %3%, is_system %4%, is_default %5% is_visible %6%")%Preset::get_type_string(m_type) %preset.name %preset.file %preset.is_system %preset.is_default %preset.is_visible;
|
||||
// add alias for custom filament preset
|
||||
set_custom_preset_alias(preset);
|
||||
}
|
||||
|
||||
if (preset_loaded_fn != nullptr)
|
||||
preset_loaded_fn(preset);
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " load config successful and preset name is:" << preset.name;
|
||||
presets_loaded.emplace_back(std::move(preset));
|
||||
}
|
||||
|
||||
// Load all presets found in dir_path.
|
||||
// Throws an exception on error.
|
||||
void PresetCollection::load_presets(
|
||||
@@ -1706,6 +1871,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 +1888,26 @@ 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, UserPresetLoad &&loaded) {
|
||||
// Committing can remove an unreadable preset's file, and a filesystem error
|
||||
// there is reported without stopping the rest of the directory.
|
||||
try {
|
||||
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, read_only);
|
||||
} 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();
|
||||
@@ -2827,6 +2871,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 +3902,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -445,7 +445,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) {}
|
||||
@@ -801,6 +804,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 +879,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 +897,43 @@ protected:
|
||||
void set_custom_preset_alias(Preset &preset);
|
||||
|
||||
private:
|
||||
// One preset file read and flattened against the presets already in this
|
||||
// collection, before anything the collection shares has been touched.
|
||||
struct UserPresetLoad
|
||||
{
|
||||
Preset preset;
|
||||
// Joins the collection. A file that threw partway still joins it, without
|
||||
// the steps that did not run.
|
||||
bool install { false };
|
||||
// The whole of the load ran, so the preset is ready to be aliased.
|
||||
bool complete { false };
|
||||
// A filament preset that named no compatible printer and was given one from
|
||||
// its name, which commit writes back to its file.
|
||||
bool save_compatible_printers { false };
|
||||
// Unreadable, so commit removes it and its .info file.
|
||||
bool discard_file { false };
|
||||
// The .info file read beside the preset, which commit logs.
|
||||
std::string info_file;
|
||||
// Counted and logged by commit, in the order the directory listed the files.
|
||||
std::vector<std::string> errors;
|
||||
PresetsConfigSubstitutions substitutions;
|
||||
};
|
||||
|
||||
// Read and flatten one preset file. It reads only, and resolves against the presets
|
||||
// loaded before this pass, never another file of the same pass, so the files of a
|
||||
// pass are independent of each other.
|
||||
UserPresetLoad resolve_user_preset(const boost::filesystem::path &file, const std::string &canonical_name,
|
||||
const PresetOrigin &load_origin, ForwardCompatibilitySubstitutionRule substitution_rule,
|
||||
const std::string &extruder_id_name, const std::string &extruder_variant_name,
|
||||
std::set<std::string> *key_set1, std::set<std::string> *key_set2) const;
|
||||
|
||||
// Install one resolved preset. The collection, its alias maps, the error count
|
||||
// and the preset files on disk are touched here and only here.
|
||||
void commit_user_preset(UserPresetLoad &&loaded, std::deque<Preset> &presets_loaded,
|
||||
PresetsConfigSubstitutions &substitutions,
|
||||
const std::function<void(Preset&)> &preset_loaded_fn,
|
||||
bool read_only);
|
||||
|
||||
std::string canonical_preset_name(const std::string &name, const PresetOrigin &load_origin = PresetOrigin()) const;
|
||||
|
||||
// Comparator that sorts "Generic " prefixed presets before others, then alphabetically within each group.
|
||||
@@ -901,6 +945,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)
|
||||
|
||||
+555
-386
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();
|
||||
|
||||
@@ -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
|
||||
|
||||
+11
-5
@@ -2439,12 +2439,18 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
|
||||
if (m_default_region_config.precise_outer_wall && m_default_region_config.wall_sequence != WallSequence::InnerOuter)
|
||||
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
|
||||
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)) {
|
||||
// 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)) {
|
||||
|
||||
const std::string pa_model = m_config.adaptive_pressure_advance_model.get_at(extruder_id);
|
||||
const std::string pa_model = m_config.adaptive_pressure_advance_model.get_at(column);
|
||||
if (!pa_model.empty()) {
|
||||
std::string validation_error = AdaptivePAProcessor::validate_adaptive_pa_model(pa_model);
|
||||
if (!validation_error.empty()) {
|
||||
|
||||
@@ -9403,6 +9403,10 @@ 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;
|
||||
@@ -9505,6 +9509,13 @@ 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",
|
||||
@@ -10712,6 +10723,25 @@ 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,6 +884,11 @@ 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);
|
||||
|
||||
+166
-337
@@ -30,7 +30,6 @@
|
||||
|
||||
#include <cstddef>
|
||||
#include <float.h>
|
||||
#include <array>
|
||||
#include <iterator>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
@@ -43,7 +42,6 @@
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_invoke.h>
|
||||
#include <tbb/spin_mutex.h>
|
||||
#include <tbb/concurrent_unordered_set.h>
|
||||
|
||||
@@ -1666,9 +1664,7 @@ void PrintObject::detect_surfaces_type()
|
||||
bool interface_shells = ! spiral_mode && m_config.interface_shells.value;
|
||||
size_t num_layers = spiral_mode ? std::min(size_t(this->printing_region(0).config().bottom_shell_layers), m_layers.size()) : m_layers.size();
|
||||
|
||||
// The regions of a layer do not see each other here, and a layer cut through a fine relief takes far longer than the
|
||||
// others, so the regions run next to each other instead of one after another, each still over all layers.
|
||||
tbb::parallel_for(size_t(0), this->num_printing_regions(), [&](size_t region_id) {
|
||||
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << region_id << " in parallel - start";
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
for (Layer *layer : m_layers)
|
||||
@@ -1726,7 +1722,7 @@ void PrintObject::detect_surfaces_type()
|
||||
if (upper_layer) {
|
||||
ExPolygons upper_slices = interface_shells ?
|
||||
diff_ex(layerm_slices_surfaces, upper_layer->m_regions[region_id]->slices.surfaces, ApplySafetyOffset::Yes) :
|
||||
diff_ex_by_piece(layerm_slices_surfaces, to_polygons(upper_layer->lslices), ApplySafetyOffset::Yes);
|
||||
diff_ex(layerm_slices_surfaces, upper_layer->lslices, ApplySafetyOffset::Yes);
|
||||
surfaces_append(top, opening_ex(upper_slices, offset), stTop);
|
||||
} else {
|
||||
// if no upper layer, all surfaces of this one are solid
|
||||
@@ -1752,7 +1748,7 @@ void PrintObject::detect_surfaces_type()
|
||||
surfaces_append(
|
||||
bottom,
|
||||
opening_ex(
|
||||
diff_ex_by_piece(layerm_slices_surfaces, to_polygons(lower_layer->lslices), ApplySafetyOffset::Yes),
|
||||
diff_ex(layerm_slices_surfaces, lower_layer->lslices, ApplySafetyOffset::Yes),
|
||||
offset),
|
||||
surface_type_bottom_other);
|
||||
// if user requested internal shells, we need to identify surfaces
|
||||
@@ -1783,44 +1779,34 @@ void PrintObject::detect_surfaces_type()
|
||||
// and top surfaces; let's do an intersection to discover them and consider them
|
||||
// as bottom surfaces (to allow for bridge detection)
|
||||
if (! top.empty() && ! bottom.empty()) {
|
||||
const auto cracks = intersection_ex_by_piece(to_expolygons(top), to_polygons(bottom));
|
||||
const auto cracks = intersection_ex(top, bottom);
|
||||
if (!cracks.empty()) {
|
||||
if (lower_layer) { // Only detect small cracks for non-first layer, because first layer should always be bottom
|
||||
const float small_crack_threshold = -layerm->flow(frExternalPerimeter).scaled_width() * 1.5;
|
||||
|
||||
// Only the bottom surfaces near a crack can take part: one that contains it must contain its box,
|
||||
// and one whose box misses the grown crack is left unchanged by removing it. A layer cut through
|
||||
// a fine relief has thousands of both, which made this loop quadratic.
|
||||
for (const auto& crack : cracks) {
|
||||
if (offset_ex(crack, small_crack_threshold).empty()) {
|
||||
// For small cracks, if it's part of a large bottom surface, then it should be added to bottom as well
|
||||
const BoundingBox crack_bbox = get_extents(crack);
|
||||
if (std::any_of(bottom.begin(), bottom.end(), [&crack, &crack_bbox, small_crack_threshold](const Surface& s) {
|
||||
if (std::any_of(bottom.begin(), bottom.end(), [&crack, small_crack_threshold](const Surface& s) {
|
||||
const auto& se = s.expolygon;
|
||||
return get_extents(se).inflated(SCALED_EPSILON).contains(crack_bbox)
|
||||
&& diff_ex(crack, se, ApplySafetyOffset::Yes).empty()
|
||||
return diff_ex(crack, se, ApplySafetyOffset::Yes).empty()
|
||||
&& se.area() > crack.area() * 2
|
||||
&& !offset_ex(diff_ex(se, crack), small_crack_threshold).empty();
|
||||
})) continue;
|
||||
|
||||
// Crack too small, leave it as part of the top surface, remove it from bottom surfaces
|
||||
const ExPolygons grown_crack = offset_ex(crack, -small_crack_threshold);
|
||||
const BoundingBox grown_bbox = get_extents(grown_crack);
|
||||
Surfaces bot_tmp;
|
||||
for (auto& b : bottom) {
|
||||
if (get_extents(b.expolygon).overlap(grown_bbox))
|
||||
surfaces_append(bot_tmp, diff_ex(b.expolygon, grown_crack), b.surface_type);
|
||||
else
|
||||
bot_tmp.emplace_back(std::move(b));
|
||||
surfaces_append(bot_tmp, diff_ex(b.expolygon, offset_ex(crack, -small_crack_threshold)), b.surface_type);
|
||||
}
|
||||
bottom = std::move(bot_tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ExPolygons top_expolygons = to_expolygons(std::move(top));
|
||||
Polygons top_polygons = to_polygons(std::move(top));
|
||||
top.clear();
|
||||
surfaces_append(top, diff_ex_by_piece(top_expolygons, to_polygons(bottom)), stTop);
|
||||
surfaces_append(top, diff_ex(top_polygons, bottom), stTop);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1911,7 +1897,7 @@ void PrintObject::detect_surfaces_type()
|
||||
{
|
||||
Polygons topbottom = to_polygons(top);
|
||||
polygons_append(topbottom, to_polygons(bottom));
|
||||
surfaces_append(surfaces_out, diff_ex_by_piece(surfaces_prev_expolys, topbottom), stInternal);
|
||||
surfaces_append(surfaces_out, diff_ex(surfaces_prev_expolys, topbottom), stInternal);
|
||||
}
|
||||
|
||||
surfaces_append(surfaces_out, std::move(top));
|
||||
@@ -2088,31 +2074,29 @@ void PrintObject::detect_surfaces_type()
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
// ==============================================================================================================
|
||||
// === ORCA: End of second external bridge layer changes =======================================================
|
||||
// ==============================================================================================================
|
||||
|
||||
}); // for each this->print->region_count
|
||||
|
||||
// ==============================================================================================================
|
||||
// === ORCA: Interim workaround - for now the new stInternalAfterExternalBridge surfaace is re-classified ==============
|
||||
// === back to a bottom bridge. As a starting point, this improves bridging reliability as it extrudes ==========
|
||||
// === two external bridge layers. However, TODO: Implement a new surface type throughout the codebase ==========
|
||||
// ==============================================================================================================
|
||||
// Once all the regions have their second bridge layer, and before their slices are trimmed into fill surfaces below.
|
||||
if ((this->config().enable_extra_bridge_layer.value == eblApplyToAll) || (this->config().enable_extra_bridge_layer.value == eblExternalBridgeOnly)) {
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, m_layers.size()), [this](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer)
|
||||
for (LayerRegion *layerm : m_layers[idx_layer]->regions())
|
||||
for (Surface &s : layerm->slices.surfaces)
|
||||
if (s.surface_type == stInternalAfterExternalBridge)
|
||||
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) {
|
||||
tbb::parallel_for( tbb::blocked_range<size_t>(0, m_layers.size()), [this, region_id](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) {
|
||||
Surfaces &surfs = m_layers[idx_layer]->m_regions[region_id]->slices.surfaces;
|
||||
for (Surface &s : surfs) {
|
||||
if (s.surface_type == stInternalAfterExternalBridge) {
|
||||
s.surface_type = stBottomBridge;
|
||||
});
|
||||
m_print->throw_if_canceled();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
// ==============================================================================================================
|
||||
// === ORCA: End of second external bridge layer changes =======================================================
|
||||
// ==============================================================================================================
|
||||
|
||||
tbb::parallel_for(size_t(0), this->num_printing_regions(), [&](size_t region_id) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << region_id << " - clipping in parallel - start";
|
||||
// Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces.
|
||||
tbb::parallel_for(
|
||||
@@ -2129,7 +2113,7 @@ void PrintObject::detect_surfaces_type()
|
||||
});
|
||||
m_print->throw_if_canceled();
|
||||
BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << region_id << " - clipping in parallel - end";
|
||||
});
|
||||
} // for each this->print->region_count
|
||||
|
||||
// Mark the object to have the region slices classified (typed, which also means they are split based on whether they are supported, bridging, top layers etc.)
|
||||
m_typed_slices = true;
|
||||
@@ -2196,10 +2180,8 @@ void PrintObject::process_external_surfaces()
|
||||
BOOST_LOG_TRIVIAL(debug) << "Collecting surfaces covered with extrusions in parallel - end";
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces in parallel - start";
|
||||
// The regions of a layer do not see each other here, and a layer cut through a fine relief takes far longer than the
|
||||
// others, so the regions run next to each other instead of one after another, each still over all layers.
|
||||
tbb::parallel_for(size_t(0), this->num_printing_regions(), [this, &surfaces_covered](size_t region_id) {
|
||||
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces for region " << region_id << " in parallel - start";
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, m_layers.size()),
|
||||
[this, &surfaces_covered, region_id](const tbb::blocked_range<size_t>& range) {
|
||||
@@ -2214,9 +2196,9 @@ void PrintObject::process_external_surfaces()
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
m_print->throw_if_canceled();
|
||||
BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces in parallel - end";
|
||||
BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces for region " << region_id << " in parallel - end";
|
||||
}
|
||||
}
|
||||
|
||||
void PrintObject::discover_vertical_shells()
|
||||
@@ -2255,10 +2237,10 @@ void PrintObject::discover_vertical_shells()
|
||||
// The "ensure vertical wall thickness" feature is not applicable to any of the regions. Quit.
|
||||
return;
|
||||
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - start : cache top / bottom";
|
||||
// One layer per task: on a layer cut through a fine relief the unions below take far longer than elsewhere, and a
|
||||
// few such layers next to each other must not end up in one task.
|
||||
//FIXME Improve the heuristics for a grain size.
|
||||
size_t grain_size = std::max(num_layers / 16, size_t(1));
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, num_layers, 1),
|
||||
tbb::blocked_range<size_t>(0, num_layers, grain_size),
|
||||
[this, &cache_top_botom_regions](const tbb::blocked_range<size_t>& range) {
|
||||
const std::initializer_list<SurfaceType> surfaces_bottom { stBottom, stBottomBridge };
|
||||
const size_t num_regions = this->num_printing_regions();
|
||||
@@ -2266,38 +2248,26 @@ void PrintObject::discover_vertical_shells()
|
||||
m_print->throw_if_canceled();
|
||||
const Layer &layer = *m_layers[idx_layer];
|
||||
DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[idx_layer];
|
||||
const auto top_bottom_expansion = [&layer](size_t region_id) {
|
||||
return float(layer.m_regions[region_id]->flow(frSolidInfill).scaled_spacing()) * top_bottom_expansion_coeff;
|
||||
};
|
||||
// Simulate single set of perimeters over all merged regions.
|
||||
float perimeter_offset = 0.f;
|
||||
float perimeter_min_spacing = FLT_MAX;
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
static size_t debug_idx = 0;
|
||||
++ debug_idx;
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
// The top surfaces, the bottom surfaces and the holes are independent of each other.
|
||||
tbb::parallel_invoke(
|
||||
[&]() {
|
||||
for (size_t region_id = 0; region_id < num_regions; ++ region_id)
|
||||
append(cache.top_surfaces, offset(layer.m_regions[region_id]->slices.filter_by_type(stTop), top_bottom_expansion(region_id)));
|
||||
// append(cache.top_surfaces, offset(layerm.fill_surfaces.filter_by_type(stTop), top_bottom_expansion));
|
||||
// Save some computing time by reducing the number of polygons.
|
||||
cache.top_surfaces = union_(cache.top_surfaces);
|
||||
},
|
||||
[&]() {
|
||||
for (size_t region_id = 0; region_id < num_regions; ++ region_id)
|
||||
append(cache.bottom_surfaces, offset(layer.m_regions[region_id]->slices.filter_by_types(surfaces_bottom), top_bottom_expansion(region_id)));
|
||||
// append(cache.bottom_surfaces, offset(layerm.fill_surfaces.filter_by_types(surfaces_bottom), top_bottom_expansion));
|
||||
cache.bottom_surfaces = union_(cache.bottom_surfaces);
|
||||
},
|
||||
[&]() {
|
||||
// Simulate single set of perimeters over all merged regions.
|
||||
float perimeter_offset = 0.f;
|
||||
float perimeter_min_spacing = FLT_MAX;
|
||||
for (size_t region_id = 0; region_id < num_regions; ++ region_id) {
|
||||
const LayerRegion &layerm = *layer.m_regions[region_id];
|
||||
LayerRegion &layerm = *layer.m_regions[region_id];
|
||||
float top_bottom_expansion = float(layerm.flow(frSolidInfill).scaled_spacing()) * top_bottom_expansion_coeff;
|
||||
// Top surfaces.
|
||||
append(cache.top_surfaces, offset(layerm.slices.filter_by_type(stTop), top_bottom_expansion));
|
||||
// append(cache.top_surfaces, offset(layerm.fill_surfaces.filter_by_type(stTop), top_bottom_expansion));
|
||||
// Bottom surfaces.
|
||||
append(cache.bottom_surfaces, offset(layerm.slices.filter_by_types(surfaces_bottom), top_bottom_expansion));
|
||||
// append(cache.bottom_surfaces, offset(layerm.fill_surfaces.filter_by_types(surfaces_bottom), top_bottom_expansion));
|
||||
// Calculate the maximum perimeter offset as if the slice was extruded with a single extruder only.
|
||||
// First find the maxium number of perimeters per region slice.
|
||||
unsigned int perimeters = 0;
|
||||
for (const Surface &s : layerm.slices.surfaces)
|
||||
for (Surface &s : layerm.slices.surfaces)
|
||||
perimeters = std::max<unsigned int>(perimeters, s.extra_perimeters);
|
||||
perimeters += layerm.region().config().wall_loops.value;
|
||||
// Then calculate the infill offset.
|
||||
@@ -2310,6 +2280,9 @@ void PrintObject::discover_vertical_shells()
|
||||
}
|
||||
polygons_append(cache.holes, to_polygons(layerm.fill_expolygons));
|
||||
}
|
||||
// Save some computing time by reducing the number of polygons.
|
||||
cache.top_surfaces = union_(cache.top_surfaces);
|
||||
cache.bottom_surfaces = union_(cache.bottom_surfaces);
|
||||
// For a multi-material print, simulate perimeter / infill split as if only a single extruder has been used for the whole print.
|
||||
if (perimeter_offset > 0.) {
|
||||
// The layer.lslices are forced to merge by expanding them first.
|
||||
@@ -2325,32 +2298,106 @@ void PrintObject::discover_vertical_shells()
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
}
|
||||
cache.holes = union_(cache.holes);
|
||||
});
|
||||
}
|
||||
});
|
||||
m_print->throw_if_canceled();
|
||||
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - end : cache top / bottom";
|
||||
}
|
||||
|
||||
// With one top/bottom cache for all regions, the shell and hole accumulation in the loop below depends on nothing
|
||||
// region-specific but the shell settings and the external perimeter spacing, so a region sharing them with an earlier
|
||||
// one reuses its result instead of repeating it: that accumulation is a union over several layers of top/bottom
|
||||
// surfaces, and a multi-material print has a region per filament.
|
||||
using AccumulationKey = std::array<double, 5>;
|
||||
struct ShellAccumulation
|
||||
{
|
||||
AccumulationKey key;
|
||||
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) {
|
||||
const PrintRegion ®ion = this->printing_region(region_id);
|
||||
if (region.config().ensure_vertical_shell_thickness.value != evstAll )
|
||||
// This region will be handled by discover_horizontal_shells().
|
||||
continue;
|
||||
|
||||
//FIXME Improve the heuristics for a grain size.
|
||||
size_t grain_size = std::max(num_layers / 16, size_t(1));
|
||||
|
||||
if (! top_bottom_surfaces_all_regions) {
|
||||
// This is either a single material print, or a multi-material print and interface_shells are enabled, meaning that the vertical shell thickness
|
||||
// is calculated over a single material.
|
||||
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - start : cache top / bottom";
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, num_layers, grain_size),
|
||||
[this, region_id, &cache_top_botom_regions](const tbb::blocked_range<size_t>& range) {
|
||||
const std::initializer_list<SurfaceType> surfaces_bottom { stBottom, stBottomBridge };
|
||||
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
|
||||
m_print->throw_if_canceled();
|
||||
Layer &layer = *m_layers[idx_layer];
|
||||
LayerRegion &layerm = *layer.m_regions[region_id];
|
||||
float top_bottom_expansion = float(layerm.flow(frSolidInfill).scaled_spacing()) * top_bottom_expansion_coeff;
|
||||
// Top surfaces.
|
||||
auto &cache = cache_top_botom_regions[idx_layer];
|
||||
cache.top_surfaces = offset(layerm.slices.filter_by_type(stTop), top_bottom_expansion);
|
||||
// append(cache.top_surfaces, offset(layerm.fill_surfaces.filter_by_type(stTop), top_bottom_expansion));
|
||||
// Bottom surfaces.
|
||||
cache.bottom_surfaces = offset(layerm.slices.filter_by_types(surfaces_bottom), top_bottom_expansion);
|
||||
// append(cache.bottom_surfaces, offset(layerm.fill_surfaces.filter_by_types(surfaces_bottom), top_bottom_expansion));
|
||||
// Holes over all regions. Only collect them once, they are valid for all region_id iterations.
|
||||
if (cache.holes.empty()) {
|
||||
for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id)
|
||||
polygons_append(cache.holes, to_polygons(layer.regions()[region_id]->fill_expolygons));
|
||||
}
|
||||
}
|
||||
});
|
||||
m_print->throw_if_canceled();
|
||||
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - end : cache top / bottom";
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - start : ensure vertical wall thickness";
|
||||
grain_size = 1;
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, num_layers, grain_size),
|
||||
[this, region_id, &cache_top_botom_regions]
|
||||
(const tbb::blocked_range<size_t>& range) {
|
||||
// printf("discover_vertical_shells from %d to %d\n", range.begin(), range.end());
|
||||
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
|
||||
m_print->throw_if_canceled();
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
static size_t debug_idx = 0;
|
||||
++ debug_idx;
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
|
||||
Layer *layer = m_layers[idx_layer];
|
||||
LayerRegion *layerm = layer->m_regions[region_id];
|
||||
const PrintRegionConfig ®ion_config = layerm->region().config();
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
layerm->export_region_slices_to_svg_debug("3_discover_vertical_shells-initial");
|
||||
layerm->export_region_fill_surfaces_to_svg_debug("3_discover_vertical_shells-initial");
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
|
||||
Flow solid_infill_flow = layerm->flow(frSolidInfill);
|
||||
coord_t infill_line_spacing = solid_infill_flow.scaled_spacing();
|
||||
// Find a union of perimeters below / above this surface to guarantee a minimum shell thickness.
|
||||
Polygons shell;
|
||||
Polygons holes;
|
||||
};
|
||||
const auto accumulation_key = [](const PrintRegionConfig ®ion_config, const LayerRegion *layerm) {
|
||||
return AccumulationKey{ double(region_config.top_shell_layers.value), region_config.top_shell_thickness.value,
|
||||
double(region_config.bottom_shell_layers.value), region_config.bottom_shell_thickness.value,
|
||||
double(layerm->flow(frExternalPerimeter).scaled_spacing()) };
|
||||
};
|
||||
const auto accumulate_shell = [this, &cache_top_botom_regions](size_t idx_layer, const PrintRegionConfig ®ion_config,
|
||||
const LayerRegion *layerm, Polygons &shell, Polygons &holes) {
|
||||
const Layer *layer = m_layers[idx_layer];
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
ExPolygons shell_ex;
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
float min_perimeter_infill_spacing = float(infill_line_spacing) * 1.05f;
|
||||
#if 0
|
||||
// #ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
{
|
||||
Slic3r::SVG svg_cummulative(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d.svg", debug_idx), this->bounding_box());
|
||||
for (int n = (int)idx_layer - n_extra_bottom_layers; n <= (int)idx_layer + n_extra_top_layers; ++ n) {
|
||||
if (n < 0 || n >= (int)m_layers.size())
|
||||
continue;
|
||||
ExPolygons &expolys = m_layers[n]->perimeter_expolygons;
|
||||
for (size_t i = 0; i < expolys.size(); ++ i) {
|
||||
Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d-layer%d-expoly%d.svg", debug_idx, n, i), get_extents(expolys[i]));
|
||||
svg.draw(expolys[i]);
|
||||
svg.draw_outline(expolys[i].contour, "black", scale_(0.05));
|
||||
svg.draw_outline(expolys[i].holes, "blue", scale_(0.05));
|
||||
svg.Close();
|
||||
|
||||
svg_cummulative.draw(expolys[i]);
|
||||
svg_cummulative.draw_outline(expolys[i].contour, "black", scale_(0.05));
|
||||
svg_cummulative.draw_outline(expolys[i].holes, "blue", scale_(0.05));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
polygons_append(holes, cache_top_botom_regions[idx_layer].holes);
|
||||
auto combine_holes = [&holes](const Polygons &holes2) {
|
||||
if (holes.empty() || holes2.empty())
|
||||
@@ -2425,141 +2472,6 @@ void PrintObject::discover_vertical_shells()
|
||||
(i > ibottom || bottom_z - m_layers[i]->print_z < region_config.bottom_shell_thickness - EPSILON))
|
||||
combine_holes(cache_top_botom_regions[i].holes);
|
||||
}
|
||||
};
|
||||
std::vector<std::vector<ShellAccumulation>> shell_accumulations(top_bottom_surfaces_all_regions ? num_layers : 0);
|
||||
if (! shell_accumulations.empty()) {
|
||||
// Every (layer, key) pair is accumulated once, before the regions, so that nothing in the loop below is shared
|
||||
// between them and they can run next to each other.
|
||||
std::vector<std::array<size_t, 3>> todo; // layer, its slot, a region holding the key
|
||||
for (size_t idx_layer = 0; idx_layer < num_layers; ++ idx_layer) {
|
||||
std::vector<ShellAccumulation> &accumulations = shell_accumulations[idx_layer];
|
||||
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) {
|
||||
if (this->printing_region(region_id).config().ensure_vertical_shell_thickness.value != evstAll)
|
||||
continue;
|
||||
const LayerRegion *layerm = m_layers[idx_layer]->m_regions[region_id];
|
||||
const AccumulationKey key = accumulation_key(layerm->region().config(), layerm);
|
||||
if (std::none_of(accumulations.begin(), accumulations.end(), [&key](const ShellAccumulation &a) { return a.key == key; })) {
|
||||
todo.push_back({ idx_layer, accumulations.size(), region_id });
|
||||
accumulations.push_back({ key, {}, {} });
|
||||
}
|
||||
}
|
||||
}
|
||||
tbb::parallel_for(size_t(0), todo.size(), [this, &todo, &shell_accumulations, &accumulate_shell](size_t i) {
|
||||
m_print->throw_if_canceled();
|
||||
const LayerRegion *layerm = m_layers[todo[i][0]]->m_regions[todo[i][2]];
|
||||
ShellAccumulation &out = shell_accumulations[todo[i][0]][todo[i][1]];
|
||||
accumulate_shell(todo[i][0], layerm->region().config(), layerm, out.shell, out.holes);
|
||||
});
|
||||
m_print->throw_if_canceled();
|
||||
}
|
||||
|
||||
const auto process_region = [&](size_t region_id) {
|
||||
const PrintRegion ®ion = this->printing_region(region_id);
|
||||
if (region.config().ensure_vertical_shell_thickness.value != evstAll )
|
||||
// This region will be handled by discover_horizontal_shells().
|
||||
return;
|
||||
|
||||
//FIXME Improve the heuristics for a grain size.
|
||||
size_t grain_size = std::max(num_layers / 16, size_t(1));
|
||||
|
||||
if (! top_bottom_surfaces_all_regions) {
|
||||
// This is either a single material print, or a multi-material print and interface_shells are enabled, meaning that the vertical shell thickness
|
||||
// is calculated over a single material.
|
||||
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - start : cache top / bottom";
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, num_layers, grain_size),
|
||||
[this, region_id, &cache_top_botom_regions](const tbb::blocked_range<size_t>& range) {
|
||||
const std::initializer_list<SurfaceType> surfaces_bottom { stBottom, stBottomBridge };
|
||||
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
|
||||
m_print->throw_if_canceled();
|
||||
Layer &layer = *m_layers[idx_layer];
|
||||
LayerRegion &layerm = *layer.m_regions[region_id];
|
||||
float top_bottom_expansion = float(layerm.flow(frSolidInfill).scaled_spacing()) * top_bottom_expansion_coeff;
|
||||
// Top surfaces.
|
||||
auto &cache = cache_top_botom_regions[idx_layer];
|
||||
cache.top_surfaces = offset(layerm.slices.filter_by_type(stTop), top_bottom_expansion);
|
||||
// append(cache.top_surfaces, offset(layerm.fill_surfaces.filter_by_type(stTop), top_bottom_expansion));
|
||||
// Bottom surfaces.
|
||||
cache.bottom_surfaces = offset(layerm.slices.filter_by_types(surfaces_bottom), top_bottom_expansion);
|
||||
// append(cache.bottom_surfaces, offset(layerm.fill_surfaces.filter_by_types(surfaces_bottom), top_bottom_expansion));
|
||||
// Holes over all regions. Only collect them once, they are valid for all region_id iterations.
|
||||
if (cache.holes.empty()) {
|
||||
for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id)
|
||||
polygons_append(cache.holes, to_polygons(layer.regions()[region_id]->fill_expolygons));
|
||||
}
|
||||
}
|
||||
});
|
||||
m_print->throw_if_canceled();
|
||||
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - end : cache top / bottom";
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - start : ensure vertical wall thickness";
|
||||
grain_size = 1;
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, num_layers, grain_size),
|
||||
[this, region_id, &shell_accumulations, &accumulation_key, &accumulate_shell]
|
||||
(const tbb::blocked_range<size_t>& range) {
|
||||
// printf("discover_vertical_shells from %d to %d\n", range.begin(), range.end());
|
||||
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
|
||||
m_print->throw_if_canceled();
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
static size_t debug_idx = 0;
|
||||
++ debug_idx;
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
|
||||
Layer *layer = m_layers[idx_layer];
|
||||
LayerRegion *layerm = layer->m_regions[region_id];
|
||||
const PrintRegionConfig ®ion_config = layerm->region().config();
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
layerm->export_region_slices_to_svg_debug("3_discover_vertical_shells-initial");
|
||||
layerm->export_region_fill_surfaces_to_svg_debug("3_discover_vertical_shells-initial");
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
|
||||
Flow solid_infill_flow = layerm->flow(frSolidInfill);
|
||||
coord_t infill_line_spacing = solid_infill_flow.scaled_spacing();
|
||||
// Find a union of perimeters below / above this surface to guarantee a minimum shell thickness.
|
||||
Polygons shell;
|
||||
Polygons holes;
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
ExPolygons shell_ex;
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
float min_perimeter_infill_spacing = float(infill_line_spacing) * 1.05f;
|
||||
#if 0
|
||||
// #ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
{
|
||||
Slic3r::SVG svg_cummulative(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d.svg", debug_idx), this->bounding_box());
|
||||
for (int n = (int)idx_layer - n_extra_bottom_layers; n <= (int)idx_layer + n_extra_top_layers; ++ n) {
|
||||
if (n < 0 || n >= (int)m_layers.size())
|
||||
continue;
|
||||
ExPolygons &expolys = m_layers[n]->perimeter_expolygons;
|
||||
for (size_t i = 0; i < expolys.size(); ++ i) {
|
||||
Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d-layer%d-expoly%d.svg", debug_idx, n, i), get_extents(expolys[i]));
|
||||
svg.draw(expolys[i]);
|
||||
svg.draw_outline(expolys[i].contour, "black", scale_(0.05));
|
||||
svg.draw_outline(expolys[i].holes, "blue", scale_(0.05));
|
||||
svg.Close();
|
||||
|
||||
svg_cummulative.draw(expolys[i]);
|
||||
svg_cummulative.draw_outline(expolys[i].contour, "black", scale_(0.05));
|
||||
svg_cummulative.draw_outline(expolys[i].holes, "blue", scale_(0.05));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
const AccumulationKey key = accumulation_key(region_config, layerm);
|
||||
const ShellAccumulation *reused = shell_accumulations.empty() ? nullptr :
|
||||
[&]() -> const ShellAccumulation * {
|
||||
for (const ShellAccumulation &a : shell_accumulations[idx_layer])
|
||||
if (a.key == key)
|
||||
return &a;
|
||||
return nullptr;
|
||||
}();
|
||||
if (reused != nullptr) {
|
||||
shell = reused->shell;
|
||||
holes = reused->holes;
|
||||
} else
|
||||
accumulate_shell(idx_layer, region_config, layerm, shell, holes);
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
{
|
||||
Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-%d.svg", debug_idx), get_extents(shell));
|
||||
@@ -2653,8 +2565,11 @@ void PrintObject::discover_vertical_shells()
|
||||
Polygons object_volume;
|
||||
Polygons internal_volume;
|
||||
{
|
||||
if (idx_layer > 0 && idx_layer + 1 < m_layers.size())
|
||||
object_volume = to_polygons(intersection_ex_by_piece(m_layers[idx_layer - 1]->lslices, to_polygons(m_layers[idx_layer + 1]->lslices)));
|
||||
Polygons shrinked_bottom_slice = idx_layer > 0 ? to_polygons(m_layers[idx_layer - 1]->lslices) : Polygons{};
|
||||
Polygons shrinked_upper_slice = (idx_layer + 1) < m_layers.size() ?
|
||||
to_polygons(m_layers[idx_layer + 1]->lslices) :
|
||||
Polygons{};
|
||||
object_volume = intersection(shrinked_bottom_slice, shrinked_upper_slice);
|
||||
internal_volume = closing(polygonsInternal, SCALED_EPSILON);
|
||||
}
|
||||
|
||||
@@ -2665,34 +2580,15 @@ void PrintObject::discover_vertical_shells()
|
||||
// the in-model condition is there due to small sloping surfaces, e.g. top of the hull of the benchy
|
||||
// 2. the area does not fully cover an internal polygon
|
||||
// This is there mainly for a very thin parts, where the solid layers would be missing if the part area is quite small
|
||||
// Both tests below compare a small piece against the whole layer. Done literally, that is
|
||||
// quadratic in the number of pieces, which is what a layer split up by colour painting has,
|
||||
// so each is restricted to the part of the layer near the piece with an identical result:
|
||||
// object_volume is clipped to the piece's box, and only the internal polygons whose box meets
|
||||
// the expanded piece take part in the count, since the others pass through the difference
|
||||
// unchanged and add the same number to both sides of it.
|
||||
std::vector<BoundingBox> internal_bboxes;
|
||||
internal_bboxes.reserve(internal_volume.size());
|
||||
for (const Polygon &poly : internal_volume)
|
||||
internal_bboxes.emplace_back(get_extents(poly));
|
||||
regularized_shell.erase(std::remove_if(regularized_shell.begin(), regularized_shell.end(),
|
||||
[&internal_volume, &internal_bboxes, &min_perimeter_infill_spacing,
|
||||
[&internal_volume, &min_perimeter_infill_spacing,
|
||||
&object_volume](const ExPolygon &p) {
|
||||
const bool small = p.area() < min_perimeter_infill_spacing * scaled(1.5) ||
|
||||
return (p.area() < min_perimeter_infill_spacing * scaled(1.5) ||
|
||||
(p.area() < min_perimeter_infill_spacing * scaled(8.0) &&
|
||||
diff(to_polygons(p),
|
||||
ClipperUtils::clip_clipper_polygons_with_subject_bbox(
|
||||
object_volume, get_extents(p).inflated(SCALED_EPSILON)))
|
||||
.empty());
|
||||
if (!small)
|
||||
return false;
|
||||
const Polygons expanded = expand(to_polygons(p), min_perimeter_infill_spacing);
|
||||
const BoundingBox bbox = get_extents(expanded);
|
||||
Polygons nearby;
|
||||
for (size_t i = 0; i < internal_volume.size(); ++i)
|
||||
if (internal_bboxes[i].overlap(bbox))
|
||||
nearby.emplace_back(internal_volume[i]);
|
||||
return diff(nearby, expanded).size() >= nearby.size();
|
||||
diff(to_polygons(p), object_volume).empty())) &&
|
||||
diff(internal_volume,
|
||||
expand(to_polygons(p), min_perimeter_infill_spacing))
|
||||
.size() >= internal_volume.size();
|
||||
}),
|
||||
regularized_shell.end());
|
||||
}
|
||||
@@ -2714,9 +2610,8 @@ void PrintObject::discover_vertical_shells()
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
|
||||
// Trim the internal & internalvoid by the shell.
|
||||
const Polygons regularized_shell_polygons = to_polygons(regularized_shell);
|
||||
Slic3r::ExPolygons new_internal = diff_ex_by_piece(to_expolygons(layerm->fill_surfaces.filter_by_type(stInternal)), regularized_shell_polygons);
|
||||
Slic3r::ExPolygons new_internal_void = diff_ex_by_piece(to_expolygons(layerm->fill_surfaces.filter_by_type(stInternalVoid)), regularized_shell_polygons);
|
||||
Slic3r::ExPolygons new_internal = diff_ex(layerm->fill_surfaces.filter_by_type(stInternal), regularized_shell);
|
||||
Slic3r::ExPolygons new_internal_void = diff_ex(layerm->fill_surfaces.filter_by_type(stInternalVoid), regularized_shell);
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
{
|
||||
@@ -2743,15 +2638,7 @@ void PrintObject::discover_vertical_shells()
|
||||
layerm->export_region_fill_surfaces_to_svg_debug("3_discover_vertical_shells-final");
|
||||
}
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
}; // for each region
|
||||
if (top_bottom_surfaces_all_regions)
|
||||
// Nothing is shared between the regions, and a layer cut through a fine relief takes far longer than the others,
|
||||
// so they run next to each other instead of one after another.
|
||||
tbb::parallel_for(size_t(0), this->num_printing_regions(), process_region);
|
||||
else
|
||||
// Here every region fills the one top/bottom cache with its own surfaces first.
|
||||
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id)
|
||||
process_region(region_id);
|
||||
} // for each region
|
||||
} // void PrintObject::discover_vertical_shells()
|
||||
|
||||
// #define DEBUG_BRIDGE_OVER_INFILL
|
||||
@@ -3272,16 +3159,6 @@ void PrintObject::bridge_over_infill()
|
||||
vertical_lines[i].b = Point{x, y_max};
|
||||
}
|
||||
|
||||
// The vertical lines only span the bridged area's x range, so anchors entirely outside it can never be
|
||||
// hit. Leaving them out gives the same intersections without building a tree over the whole layer's
|
||||
// boundary for every bridge.
|
||||
const coord_t scan_x_min = bb_x.min.x();
|
||||
const coord_t scan_x_max = bb_x.min.x() + coord_t(n_vlines) * scan_spacing;
|
||||
anchors.erase(std::remove_if(anchors.begin(), anchors.end(),
|
||||
[scan_x_min, scan_x_max](const Line &l) {
|
||||
return std::max(l.a.x(), l.b.x()) < scan_x_min || std::min(l.a.x(), l.b.x()) > scan_x_max;
|
||||
}),
|
||||
anchors.end());
|
||||
auto anchors_and_walls_tree = AABBTreeLines::LinesDistancer<Line>{std::move(anchors)};
|
||||
auto bridged_area_tree = AABBTreeLines::LinesDistancer<Line>{to_lines(bridged_area)};
|
||||
|
||||
@@ -3526,63 +3403,28 @@ void PrintObject::bridge_over_infill()
|
||||
|
||||
std::vector<CandidateSurface> expanded_surfaces;
|
||||
expanded_surfaces.reserve(surfaces_by_layer[lidx].size());
|
||||
// The expanded fill boundary depends only on the bridging flow, and total_fill_area is not
|
||||
// modified below, so build it once per spacing rather than once per candidate. A layer split
|
||||
// into many candidates (e.g. by colour painting) otherwise repeats a layer-wide offset for each.
|
||||
std::map<coord_t, Polylines> boundary_by_spacing;
|
||||
// expansion_area is a clean, non-overlapping set, so uniting it with a bridge or cutting a bridge
|
||||
// out of it only changes the polygons near that bridge. The rest are passed through untouched
|
||||
// instead of being fed to ClipperLib with the whole layer again for every candidate.
|
||||
// Not `near`/`far`: the Windows headers still define those as macros, and they expand to
|
||||
// nothing, which turns the declaration below into an empty one.
|
||||
const auto split_near = [](const Polygons &polys, const BoundingBox &bbox, Polygons &rest) {
|
||||
Polygons nearby;
|
||||
for (const Polygon &p : polys)
|
||||
(get_extents(p).overlap(bbox) ? nearby : rest).emplace_back(p);
|
||||
return nearby;
|
||||
};
|
||||
for (const CandidateSurface &candidate : surfaces_by_layer[lidx]) {
|
||||
const auto ®ion_config = candidate.region->region().config();
|
||||
const bool turning_pattern = region_config.sparse_infill_pattern == ipHilbertCurve ||
|
||||
region_config.sparse_infill_pattern == ipOctagramSpiral;
|
||||
const Flow &flow = candidate.region->bridging_flow(frSolidInfill, true);
|
||||
Polygons area_to_be_bridge = expand(candidate.new_polys, flow.scaled_spacing());
|
||||
// deep_infill_area and internal_unsupported_area cover the whole layer; only their part under
|
||||
// this candidate can change the results, so they are clipped to its box first.
|
||||
if (!area_to_be_bridge.empty())
|
||||
area_to_be_bridge = intersection(area_to_be_bridge,
|
||||
ClipperUtils::clip_clipper_polygons_with_subject_bbox(
|
||||
deep_infill_area, get_extents(area_to_be_bridge).inflated(SCALED_EPSILON)));
|
||||
area_to_be_bridge = intersection(area_to_be_bridge, deep_infill_area);
|
||||
|
||||
area_to_be_bridge.erase(std::remove_if(area_to_be_bridge.begin(), area_to_be_bridge.end(),
|
||||
[&internal_unsupported_area](const Polygon &p) {
|
||||
return intersection({p}, ClipperUtils::clip_clipper_polygons_with_subject_bbox(
|
||||
internal_unsupported_area,
|
||||
get_extents(p).inflated(SCALED_EPSILON)))
|
||||
.empty();
|
||||
[internal_unsupported_area](const Polygon &p) {
|
||||
return intersection({p}, internal_unsupported_area).empty();
|
||||
}),
|
||||
area_to_be_bridge.end());
|
||||
|
||||
Polygons limiting_area = union_(area_to_be_bridge, expansion_area);
|
||||
|
||||
if (area_to_be_bridge.empty())
|
||||
continue;
|
||||
|
||||
Polygons limiting_area;
|
||||
const Polygons near_expansion = split_near(expansion_area, get_extents(area_to_be_bridge).inflated(SCALED_EPSILON),
|
||||
limiting_area);
|
||||
append(limiting_area, union_(area_to_be_bridge, near_expansion));
|
||||
|
||||
auto boundary_it = boundary_by_spacing.find(flow.scaled_spacing());
|
||||
if (boundary_it == boundary_by_spacing.end())
|
||||
boundary_it = boundary_by_spacing
|
||||
.emplace(flow.scaled_spacing(), to_polylines(expand(total_fill_area, 1.3 * flow.scaled_spacing())))
|
||||
.first;
|
||||
Polylines boundary_plines = boundary_it->second;
|
||||
Polylines boundary_plines = to_polylines(expand(total_fill_area, 1.3 * flow.scaled_spacing()));
|
||||
{
|
||||
// No offset here: flow.spacing() is in mm, so the expand(limiting_area, 0.3 * flow.spacing())
|
||||
// this used to be moved the outline by 0.135 scaled units - nothing beyond rounding - while
|
||||
// costing a whole-layer ClipperLib pass for every candidate. limiting_area is already a clean
|
||||
// union, so its own outline is the same boundary.
|
||||
Polylines limiting_plines = to_polylines(limiting_area);
|
||||
Polylines limiting_plines = to_polylines(expand(limiting_area, 0.3*flow.spacing()));
|
||||
boundary_plines.insert(boundary_plines.end(), limiting_plines.begin(), limiting_plines.end());
|
||||
}
|
||||
|
||||
@@ -3657,11 +3499,8 @@ void PrintObject::bridge_over_infill()
|
||||
{
|
||||
bool reconstruct = false;
|
||||
Polygons tmp_expanded_area = expand(bridging_area, 3.0 * flow.scaled_spacing());
|
||||
const BoundingBox tmp_expanded_bbox = get_extents(tmp_expanded_area);
|
||||
for (const CandidateSurface &s : expanded_surfaces) {
|
||||
// Surfaces whose boxes miss each other cannot intersect, which is most pairs on a busy layer.
|
||||
if (get_extents(s.new_polys).overlap(tmp_expanded_bbox) &&
|
||||
!intersection(s.new_polys, tmp_expanded_area).empty()) {
|
||||
if (!intersection(s.new_polys, tmp_expanded_area).empty()) {
|
||||
bridging_angle = s.bridge_angle;
|
||||
reconstruct = true;
|
||||
break;
|
||||
@@ -3685,20 +3524,10 @@ void PrintObject::bridge_over_infill()
|
||||
bridging_area = union_(bridging_area, construct_anchored_polygon(bridging_area, to_lines(boundary_plines), flow,
|
||||
bridging_angle, scan_spacing, true));
|
||||
}
|
||||
// Each of these meets one bridge with the whole layer, so the layer side is first cut down to the
|
||||
// bridge's box (and expansion_area split as above); the result is the same.
|
||||
if (!bridging_area.empty()) {
|
||||
const BoundingBox bridging_bbox = get_extents(bridging_area).inflated(SCALED_EPSILON);
|
||||
bridging_area = intersection(bridging_area, ClipperUtils::clip_clipper_polygons_with_subject_bbox(limiting_area, bridging_bbox));
|
||||
bridging_area = intersection(bridging_area, ClipperUtils::clip_clipper_polygons_with_subject_bbox(total_fill_area, bridging_bbox));
|
||||
bridging_area = diff(bridging_area, ClipperUtils::clip_clipper_polygons_with_subject_bbox(total_top_area, bridging_bbox));
|
||||
}
|
||||
if (!bridging_area.empty()) {
|
||||
Polygons kept;
|
||||
const Polygons cut = split_near(expansion_area, get_extents(bridging_area).inflated(SCALED_EPSILON), kept);
|
||||
append(kept, diff(cut, bridging_area));
|
||||
expansion_area = std::move(kept);
|
||||
}
|
||||
bridging_area = intersection(bridging_area, limiting_area);
|
||||
bridging_area = intersection(bridging_area, total_fill_area);
|
||||
bridging_area = diff(bridging_area, total_top_area);
|
||||
expansion_area = diff(expansion_area, bridging_area);
|
||||
|
||||
#ifdef DEBUG_BRIDGE_OVER_INFILL
|
||||
debug_draw(std::to_string(lidx) + "_" + std::to_string(cluster_idx) + "_" + std::to_string(job_idx) + "_" + "_expanded_bridging" + std::to_string(r),
|
||||
|
||||
@@ -929,9 +929,9 @@ public:
|
||||
::fread(&y, sizeof(coord_t), 1, file);
|
||||
poly.points.emplace_back(Point(x * scale, y * scale));
|
||||
}
|
||||
printf("Polygon %d, area: %lf\n", i, area(poly.points));
|
||||
if (which == -1 || which == i)
|
||||
m_support_polygons_deserialized.emplace_back(std::move(poly));
|
||||
printf("Polygon %d, area: %lf\n", i, area(poly.points));
|
||||
}
|
||||
::fread(&n_polygons, 4, 1, file);
|
||||
m_trimming_polygons_deserialized.reserve(n_polygons);
|
||||
|
||||
@@ -854,41 +854,11 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/)
|
||||
if (is_auto(stype) && config_detect_sharp_tails)
|
||||
{
|
||||
// BBS detect sharp tail
|
||||
// Each island is tested only against the lower islands whose box meets its own; overlaps() tries
|
||||
// every pair, which is quadratic in the island counts of the two layers.
|
||||
std::vector<BoundingBox> lower_bboxes;
|
||||
lower_bboxes.reserve(lower_polys.size());
|
||||
for (const ExPolygon &lower : lower_polys)
|
||||
lower_bboxes.emplace_back(get_extents(lower));
|
||||
for (const ExPolygon& expoly : curr_polys) {
|
||||
bool is_sharp_tail = false;
|
||||
// 1. nothing below
|
||||
// this is a sharp tail region if it's floating and non-ignorable
|
||||
const ExPolygons expanded = offset_ex(expoly, 0.1 * extrusion_width_scaled);
|
||||
const BoundingBox bbox = get_extents(expanded);
|
||||
ExPolygons lower_nearby;
|
||||
for (size_t i = 0; i < lower_polys.size(); ++i)
|
||||
if (lower_bboxes[i].overlap(bbox))
|
||||
lower_nearby.emplace_back(lower_polys[i]);
|
||||
// As overlaps(expanded, lower_nearby), with each lower island cut to the island's box first:
|
||||
// below a fine relief the lower layer is a few islands with thousands of holes, and the whole
|
||||
// of that boundary would otherwise be intersected once per island above.
|
||||
const auto overlaps_nearby = [&]() {
|
||||
for (const ExPolygon &a : expanded) {
|
||||
if (a.empty())
|
||||
continue;
|
||||
const BoundingBox a_bbox = get_extents(a);
|
||||
for (const ExPolygon &b : lower_nearby) {
|
||||
if (b.empty() || !get_extents(b).overlap(a_bbox))
|
||||
continue;
|
||||
const Polygons b_near = ClipperUtils::clip_clipper_polygons_with_subject_bbox(b, a_bbox.inflated(SCALED_EPSILON));
|
||||
if (!intersection_pl(to_polylines(b_near), a).empty() || b.contains(a.contour.points.front()))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
if (!overlaps_nearby()) {
|
||||
if (!overlaps(offset_ex(expoly, 0.1 * extrusion_width_scaled), lower_polys)) {
|
||||
is_sharp_tail = !offset_ex(expoly, -0.1 * extrusion_width_scaled).empty();
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ public:
|
||||
thickness(other.thickness), thickness_layers(other.thickness_layers),
|
||||
bridge_angle(other.bridge_angle), extra_perimeters(other.extra_perimeters)
|
||||
{};
|
||||
Surface(Surface &&rhs) noexcept
|
||||
Surface(Surface &&rhs)
|
||||
: surface_type(rhs.surface_type), expolygon(std::move(rhs.expolygon)),
|
||||
thickness(rhs.thickness), thickness_layers(rhs.thickness_layers),
|
||||
bridge_angle(rhs.bridge_angle), extra_perimeters(rhs.extra_perimeters)
|
||||
@@ -87,7 +87,7 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
Surface& operator=(Surface &&rhs) noexcept
|
||||
Surface& operator=(Surface &&rhs)
|
||||
{
|
||||
surface_type = rhs.surface_type;
|
||||
expolygon = std::move(rhs.expolygon);
|
||||
|
||||
+10
-1
@@ -256,10 +256,19 @@ extern bool is_gallery_file(const std::string& path, char const* type);
|
||||
extern bool is_shapes_dir(const std::string& dir);
|
||||
//BBS: add json support
|
||||
extern bool is_json_file(const std::string& path);
|
||||
// True if rel_path is relative, has no ".." component and, joined to root, still resolves inside it.
|
||||
// True if rel_path is relative, has no ".." component or embedded NUL and, joined to root, still resolves inside it.
|
||||
// Both '/' and '\\' are treated as separators on every platform, so an archive rejected on one OS
|
||||
// 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"); }
|
||||
|
||||
@@ -162,10 +162,10 @@ inline void append(std::vector<T, Alloc> &dest, std::vector<T, Alloc> &&src)
|
||||
{
|
||||
if (dest.empty())
|
||||
dest = std::move(src);
|
||||
else
|
||||
// insert() grows the capacity geometrically; reserving exactly the new size reallocated on every call, which
|
||||
// made appending piece by piece quadratic.
|
||||
dest.insert(dest.end(), std::make_move_iterator(src.begin()), std::make_move_iterator(src.end()));
|
||||
else {
|
||||
dest.reserve(dest.size() + src.size());
|
||||
std::move(std::begin(src), std::end(src), std::back_inserter(dest));
|
||||
}
|
||||
src.clear();
|
||||
src.shrink_to_fit();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
#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
|
||||
@@ -115,6 +118,66 @@ std::string decode_archive_entry_path(mz_zip_archive *zip, const mz_zip_archive_
|
||||
return decode_zip_unicode_path_extra_field(extra.substr(0, extra_size > 0 ? extra_size - 1 : 0), stat.m_filename);
|
||||
}
|
||||
|
||||
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,6 +11,8 @@ bool open_zip_writer(mz_zip_archive *zip, const std::string &fname_utf8);
|
||||
bool close_zip_reader(mz_zip_archive *zip);
|
||||
bool close_zip_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:
|
||||
|
||||
+39
-1
@@ -70,6 +70,7 @@
|
||||
#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>
|
||||
@@ -1093,6 +1094,9 @@ bool is_path_within_root(const std::string &rel_path, const boost::filesystem::p
|
||||
auto is_separator = [](char c) { return c == '/' || c == '\\'; };
|
||||
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]))
|
||||
@@ -1103,7 +1107,10 @@ bool is_path_within_root(const std::string &rel_path, const boost::filesystem::p
|
||||
}
|
||||
// Resolve against the canonical root so a symlink inside it cannot lead back out.
|
||||
try {
|
||||
const std::string root_str = boost::filesystem::weakly_canonical(root).string();
|
||||
std::string root_str = boost::filesystem::weakly_canonical(root).string();
|
||||
// A trailing separator on root would otherwise fail the prefix match below for every path.
|
||||
while (!root_str.empty() && (root_str.back() == '/' || root_str.back() == boost::filesystem::path::preferred_separator))
|
||||
root_str.pop_back();
|
||||
const std::string full_str = boost::filesystem::weakly_canonical(root / rel_path).string();
|
||||
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);
|
||||
@@ -1112,6 +1119,37 @@ bool is_path_within_root(const std::string &rel_path, const boost::filesystem::p
|
||||
}
|
||||
}
|
||||
|
||||
bool is_symlink_target_within_root(const std::string &link_rel_path, const std::string &target, const boost::filesystem::path &root)
|
||||
{
|
||||
if (target.empty() || target.front() == '/' || target.front() == '\\' || (target.size() > 1 && target[1] == ':'))
|
||||
return false;
|
||||
// A relative target without ".." only descends from the link's directory, so no chain of such links can leave root.
|
||||
const size_t sep = link_rel_path.find_last_of("/\\");
|
||||
return is_path_within_root((sep == std::string::npos ? std::string() : link_rel_path.substr(0, sep + 1)) + target, root);
|
||||
}
|
||||
|
||||
bool is_absolute_path_within_root(const boost::filesystem::path &path, const boost::filesystem::path &root)
|
||||
{
|
||||
const boost::filesystem::path rel = path.lexically_relative(root);
|
||||
return !rel.empty() && rel != "." && is_path_within_root(rel.string(), root);
|
||||
}
|
||||
|
||||
bool is_safe_to_open_file_name(const std::string &file_name)
|
||||
{
|
||||
// Formats that cannot carry macros or scripts. Legacy and OpenDocument office files, HTML and SVG are left out on purpose.
|
||||
static const std::vector<std::string> safe_extensions = {
|
||||
"jpg", "jpeg", "jfif", "pjpeg", "pjp", "png", "gif", "bmp", "webp", "tif", "tiff",
|
||||
"pdf", "txt", "md", "csv", "docx", "xlsx", "pptx",
|
||||
"stl", "obj", "3mf", "amf", "ply", "step", "stp", "iges", "igs", "dxf",
|
||||
"mp4", "mov", "webm"};
|
||||
// The name must end in the extension itself: Windows drops trailing dots and spaces and reads ':' as a stream separator.
|
||||
const size_t dot = file_name.find_last_of('.');
|
||||
if (dot == std::string::npos || file_name.find_first_of("/\\:") != std::string::npos)
|
||||
return false;
|
||||
const std::string extension = boost::algorithm::to_lower_copy(file_name.substr(dot + 1));
|
||||
return std::find(safe_extensions.begin(), safe_extensions.end(), extension) != safe_extensions.end();
|
||||
}
|
||||
|
||||
bool is_img_file(const std::string &path)
|
||||
{
|
||||
return boost::iends_with(path, ".png") || boost::iends_with(path, ".svg");
|
||||
|
||||
@@ -1019,14 +1019,9 @@ 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})
|
||||
target_link_libraries(libslic3r_gui ${GTK${SLIC3R_GTK}_LIBRARIES} fontconfig ${LIBSECRET_LIBRARIES} ${webkit2gtk_LIBRARIES} ${X11_LIBRARIES})
|
||||
|
||||
# Propagate GDK backend detection results as compile definitions so that
|
||||
# LinuxDisplayBackend.cpp can include the right GDK headers.
|
||||
|
||||
@@ -428,7 +428,7 @@ void AuFile::on_dclick(wxMouseEvent &evt)
|
||||
if (m_type == AddFileButton)
|
||||
return;
|
||||
else
|
||||
wxLaunchDefaultApplication(m_file_path.wstring(), 0);
|
||||
desktop_open_project_attachment(this, m_file_path);
|
||||
}
|
||||
|
||||
void AuFile::on_mouse_left_up(wxMouseEvent &evt)
|
||||
|
||||
@@ -171,14 +171,16 @@ void ConfigManipulation::check_adaptive_pressure_advance_model(DynamicPrintConfi
|
||||
return;
|
||||
|
||||
const auto* model = config->option<ConfigOptionStrings>("adaptive_pressure_advance_model");
|
||||
if (model == nullptr || model->values.empty())
|
||||
if (model == nullptr)
|
||||
return;
|
||||
|
||||
std::string raw_model;
|
||||
for (const std::string& chunk : model->values)
|
||||
raw_model += chunk;
|
||||
|
||||
std::string error = AdaptivePAProcessor::validate_adaptive_pa_model(raw_model);
|
||||
// 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;
|
||||
}
|
||||
if (!error.empty()) {
|
||||
wxString msg_text = _L("Adaptive Pressure Advance model validation failed:\n");
|
||||
msg_text += from_u8(error);
|
||||
|
||||
@@ -6546,8 +6546,11 @@ void GLCanvas3D::render_thumbnail_internal(ThumbnailData& thumbnail_data, const
|
||||
// glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("render_thumbnail: finished");
|
||||
|
||||
// Puts the canvas viewport back in place of the thumbnail one set above.
|
||||
wxGetApp().plater()->get_camera().apply_viewport();
|
||||
// Puts the canvas viewport back in place of the thumbnail one set above. The CLI renders
|
||||
// thumbnails with no application and no plater, so there is no canvas viewport to restore.
|
||||
if (wxTheApp != nullptr)
|
||||
if (Plater *plater = wxGetApp().plater(); plater != nullptr)
|
||||
plater->get_camera().apply_viewport();
|
||||
}
|
||||
|
||||
void GLCanvas3D::render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params,
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
#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
|
||||
@@ -27,11 +30,13 @@
|
||||
|
||||
#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 {
|
||||
|
||||
@@ -531,6 +536,11 @@ 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;
|
||||
@@ -635,4 +645,26 @@ void desktop_open_any_folder( const std::string& path )
|
||||
}
|
||||
|
||||
|
||||
bool desktop_open_project_attachment(wxWindow *parent, const boost::filesystem::path &path)
|
||||
{
|
||||
// The auxiliary path is UTF-8, which is what boost::filesystem reads a narrow string as.
|
||||
const boost::filesystem::path aux_root(wxGetApp().plater()->model().get_auxiliary_file_temp_path());
|
||||
boost::system::error_code ec;
|
||||
if (!is_absolute_path_within_root(path, aux_root) || !boost::filesystem::is_regular_file(path, ec))
|
||||
return false;
|
||||
|
||||
// Attachments come with the project and carry no download mark, so the desktop would open them without a warning.
|
||||
if (!is_safe_to_open_file_name(path.filename().string())) {
|
||||
MessageDialog dlg(parent,
|
||||
wxString::Format(_L("\"%s\" is not a plain document, image or model file. Opening it may run it as a "
|
||||
"program or script on this computer.\n\n"
|
||||
"Only open attachments from projects you trust. Open it anyway?"),
|
||||
from_path(path.filename())),
|
||||
_L("Open attachment"), wxICON_WARNING | wxYES_NO);
|
||||
if (dlg.ShowModal() != wxID_YES)
|
||||
return false;
|
||||
}
|
||||
return wxLaunchDefaultApplication(from_path(path), 0);
|
||||
}
|
||||
|
||||
} }
|
||||
|
||||
@@ -76,6 +76,9 @@ 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();
|
||||
@@ -83,6 +86,9 @@ 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
|
||||
|
||||
|
||||
@@ -1512,11 +1512,33 @@ int GUI_App::install_plugin(std::string name, std::string package_name, InstallP
|
||||
size_t n = mz_zip_reader_get_extra(&archive, stat.m_file_index, extra.data(), extra.size());
|
||||
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;
|
||||
boost::filesystem::create_directories(dest_path.parent_path());
|
||||
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 {
|
||||
if (fs::exists(dest_path)) {
|
||||
boost::filesystem::create_directories(dest_path.parent_path());
|
||||
// symlink_status so that an existing symlink, dangling or not, is replaced rather than written through.
|
||||
if (fs::exists(fs::symlink_status(dest_path))) {
|
||||
boost::system::error_code ec;
|
||||
fs::remove(dest_path, ec);
|
||||
if (ec) {
|
||||
@@ -1544,9 +1566,8 @@ int GUI_App::install_plugin(std::string name, std::string package_name, InstallP
|
||||
}
|
||||
mz_bool res = 0;
|
||||
#ifndef WIN32
|
||||
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);
|
||||
if (is_link) {
|
||||
res = 1;
|
||||
try {
|
||||
boost::filesystem::create_symlink(link, dest_path);
|
||||
} catch (const std::exception &e) {
|
||||
|
||||
+146
-212
@@ -1997,6 +1997,126 @@ bool MainFrame::can_reslice() const
|
||||
return (m_plater != nullptr) && !m_plater->model().objects.empty();
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Orca: stable key persisted by the "remember last print action" preference. Reordering PrintSelectType
|
||||
// must not silently remap a saved preference, so the key never derives from the enum value.
|
||||
const char* print_select_type_key(MainFrame::PrintSelectType type)
|
||||
{
|
||||
switch (type) {
|
||||
case MainFrame::ePrintAll: return "print_all";
|
||||
case MainFrame::ePrintPlate: return "print_plate";
|
||||
case MainFrame::eExportSlicedFile: return "export_sliced_file";
|
||||
case MainFrame::eExportAllSlicedFile: return "export_all_sliced_file";
|
||||
case MainFrame::eExportGcode: return "export_gcode";
|
||||
case MainFrame::eSendGcode: return "send_gcode";
|
||||
case MainFrame::eSendToPrinter: return "send_to_printer";
|
||||
case MainFrame::eSendToPrinterAll: return "send_to_printer_all";
|
||||
case MainFrame::ePrintMultiMachine: return "print_multi_machine";
|
||||
case MainFrame::eUploadGcode: break; // Orca: no dropdown entry, never selectable
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// Orca: single source for the print button and print dropdown labels
|
||||
wxString print_select_type_label(MainFrame::PrintSelectType type)
|
||||
{
|
||||
switch (type) {
|
||||
case MainFrame::ePrintAll: return _L("Print all");
|
||||
case MainFrame::ePrintPlate: return _L("Print plate");
|
||||
case MainFrame::eExportSlicedFile: return _L("Export plate sliced file");
|
||||
case MainFrame::eExportAllSlicedFile: return _L("Export all sliced file");
|
||||
case MainFrame::eExportGcode: return _L("Export G-code file");
|
||||
case MainFrame::eSendGcode: return _L_CONTEXT("Print", "Verb");
|
||||
case MainFrame::eSendToPrinter: return _L("Send");
|
||||
case MainFrame::eSendToPrinterAll: return _L("Send all");
|
||||
case MainFrame::ePrintMultiMachine: return _L("Send to Multi-device");
|
||||
case MainFrame::eUploadGcode: break; // Orca: no dropdown entry, never selectable
|
||||
}
|
||||
return _L("Print plate");
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::vector<MainFrame::PrintSelectType> MainFrame::available_print_actions() const
|
||||
{
|
||||
std::vector<PrintSelectType> actions;
|
||||
const auto preset_bundle = wxGetApp().preset_bundle;
|
||||
const bool use_printer_agents = wxGetApp().app_config->get_bool("use_printer_agents");
|
||||
|
||||
if (preset_bundle && !preset_bundle->is_bbl_vendor() && !use_printer_agents) {
|
||||
// ThirdParty actions
|
||||
actions.push_back(eSendGcode);
|
||||
// Orca: when the printer accepts a .gcode.3mf (the "Support 3MF as gcode" option),
|
||||
// also offer exporting the sliced .gcode.3mf bundle
|
||||
const auto* use_3mf_opt = preset_bundle->printers.get_edited_preset().config.option<ConfigOptionBool>("use_3mf");
|
||||
if (use_3mf_opt != nullptr && use_3mf_opt->value)
|
||||
actions.push_back(eExportSlicedFile);
|
||||
actions.push_back(eExportGcode);
|
||||
return actions;
|
||||
}
|
||||
|
||||
// Orca Slicer actions
|
||||
bool support_send = true;
|
||||
bool support_print_all = true;
|
||||
if (preset_bundle && !preset_bundle->use_bbl_network() && !use_printer_agents) {
|
||||
support_send = false; // All 3rd print hosts do not have the send options
|
||||
|
||||
const auto& cfg = preset_bundle->printers.get_edited_preset().config;
|
||||
const auto* host_type_opt = cfg.option<ConfigOptionEnum<PrintHostType>>("host_type");
|
||||
// Only simply print support uploading all plates
|
||||
support_print_all = host_type_opt != nullptr && host_type_opt->value == PrintHostType::htSimplyPrint;
|
||||
}
|
||||
|
||||
actions.push_back(ePrintPlate);
|
||||
if (support_print_all)
|
||||
actions.push_back(ePrintAll);
|
||||
if (support_send) {
|
||||
actions.push_back(eSendToPrinter);
|
||||
actions.push_back(eSendToPrinterAll);
|
||||
}
|
||||
if (enable_multi_machine)
|
||||
actions.push_back(ePrintMultiMachine);
|
||||
actions.push_back(eExportSlicedFile);
|
||||
actions.push_back(eExportAllSlicedFile);
|
||||
actions.push_back(eExportGcode);
|
||||
return actions;
|
||||
}
|
||||
|
||||
void MainFrame::select_print_action(PrintSelectType select_type)
|
||||
{
|
||||
m_print_btn->SetLabel(print_select_type_label(select_type));
|
||||
m_print_select = select_type;
|
||||
remember_print_select(select_type);
|
||||
m_print_enable = get_enable_print_status();
|
||||
m_print_btn->Enable(m_print_enable);
|
||||
this->Layout();
|
||||
fit_tab_labels(); // ORCA on label change
|
||||
}
|
||||
|
||||
void MainFrame::remember_print_select(PrintSelectType select_type)
|
||||
{
|
||||
if (!wxGetApp().app_config->get_bool("remember_print_action"))
|
||||
return;
|
||||
// AppConfig is marked dirty here and flushed by the regular autosave
|
||||
wxGetApp().app_config->set("last_print_action", print_select_type_key(select_type));
|
||||
}
|
||||
|
||||
bool MainFrame::get_remembered_print_select(PrintSelectType& out) const
|
||||
{
|
||||
if (!wxGetApp().app_config->get_bool("remember_print_action"))
|
||||
return false;
|
||||
const std::string saved = wxGetApp().app_config->get("last_print_action");
|
||||
if (saved.empty())
|
||||
return false;
|
||||
// Orca: only restore an action the current printer actually offers in the dropdown
|
||||
for (PrintSelectType type : available_print_actions()) {
|
||||
if (saved == print_select_type_key(type)) {
|
||||
out = type;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
wxBoxSizer* MainFrame::create_side_tools()
|
||||
{
|
||||
enable_multi_machine = wxGetApp().is_enable_multi_machine();
|
||||
@@ -2016,6 +2136,14 @@ wxBoxSizer* MainFrame::create_side_tools()
|
||||
m_print_btn = new SideButton(print_panel, _L("Print plate"), "");
|
||||
m_print_option_btn = new SideButton(print_panel, "", "sidebutton_dropdown", 0, 14);
|
||||
|
||||
// Orca: restore the last used print/export action if the user opted to remember it
|
||||
PrintSelectType remembered_print_select;
|
||||
if (get_remembered_print_select(remembered_print_select)) {
|
||||
m_print_select = remembered_print_select;
|
||||
m_print_btn->SetLabel(print_select_type_label(remembered_print_select));
|
||||
fit_tab_labels(); // ORCA on label change
|
||||
}
|
||||
|
||||
auto slice_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
slice_sizer->Add(m_slice_option_btn, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, FromDIP(1));
|
||||
slice_sizer->Add(m_slice_btn, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(1));
|
||||
@@ -2178,193 +2306,15 @@ wxBoxSizer* MainFrame::create_side_tools()
|
||||
m_print_option_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event)
|
||||
{
|
||||
SidePopup* p = new SidePopup(this);
|
||||
|
||||
if (wxGetApp().preset_bundle
|
||||
&& !wxGetApp().preset_bundle->is_bbl_vendor()
|
||||
&& !wxGetApp().app_config->get_bool("use_printer_agents")) {
|
||||
// ThirdParty Buttons
|
||||
SideButton* export_gcode_btn = new SideButton(p, _L("Export G-code file"), "");
|
||||
export_gcode_btn->SetCornerRadius(0);
|
||||
export_gcode_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
|
||||
m_print_btn->SetLabel(_L("Export G-code file"));
|
||||
m_print_select = eExportGcode;
|
||||
m_print_enable = get_enable_print_status();
|
||||
m_print_btn->Enable(m_print_enable);
|
||||
this->Layout();
|
||||
fit_tab_labels(); // ORCA on label change
|
||||
for (PrintSelectType type : available_print_actions()) {
|
||||
SideButton* btn = new SideButton(p, print_select_type_label(type), "");
|
||||
btn->SetCornerRadius(0);
|
||||
btn->Bind(wxEVT_BUTTON, [this, p, type](wxCommandEvent&) {
|
||||
select_print_action(type);
|
||||
p->Dismiss();
|
||||
});
|
||||
|
||||
// upload and print
|
||||
SideButton* send_gcode_btn = new SideButton(p, _L_CONTEXT("Print", "Verb"), "");
|
||||
send_gcode_btn->SetCornerRadius(0);
|
||||
send_gcode_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
|
||||
m_print_btn->SetLabel(_L_CONTEXT("Print", "Verb"));
|
||||
m_print_select = eSendGcode;
|
||||
m_print_enable = get_enable_print_status();
|
||||
m_print_btn->Enable(m_print_enable);
|
||||
this->Layout();
|
||||
fit_tab_labels(); // ORCA on label change
|
||||
p->Dismiss();
|
||||
});
|
||||
|
||||
p->append_button(send_gcode_btn);
|
||||
|
||||
// Orca: when the printer accepts a .gcode.3mf (the "Support 3MF as gcode" option),
|
||||
// also offer exporting the sliced .gcode.3mf bundle
|
||||
const auto& printer_config = wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
||||
const auto* use_3mf_opt = printer_config.option<ConfigOptionBool>("use_3mf");
|
||||
if (use_3mf_opt != nullptr && use_3mf_opt->value) {
|
||||
SideButton* export_sliced_file_btn = new SideButton(p, _L("Export plate sliced file"), "");
|
||||
export_sliced_file_btn->SetCornerRadius(0);
|
||||
export_sliced_file_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
|
||||
m_print_btn->SetLabel(_L("Export plate sliced file"));
|
||||
m_print_select = eExportSlicedFile;
|
||||
m_print_enable = get_enable_print_status();
|
||||
m_print_btn->Enable(m_print_enable);
|
||||
this->Layout();
|
||||
fit_tab_labels(); // ORCA on label change
|
||||
p->Dismiss();
|
||||
});
|
||||
p->append_button(export_sliced_file_btn);
|
||||
p->append_button(btn);
|
||||
}
|
||||
|
||||
p->append_button(export_gcode_btn);
|
||||
}
|
||||
else {
|
||||
//Orca Slicer Buttons
|
||||
SideButton* print_plate_btn = new SideButton(p, _L("Print plate"), "");
|
||||
print_plate_btn->SetCornerRadius(0);
|
||||
|
||||
SideButton* send_to_printer_btn = new SideButton(p, _L("Send"), "");
|
||||
send_to_printer_btn->SetCornerRadius(0);
|
||||
|
||||
SideButton* export_sliced_file_btn = new SideButton(p, _L("Export plate sliced file"), "");
|
||||
export_sliced_file_btn->SetCornerRadius(0);
|
||||
|
||||
SideButton* export_all_sliced_file_btn = new SideButton(p, _L("Export all sliced file"), "");
|
||||
export_all_sliced_file_btn->SetCornerRadius(0);
|
||||
|
||||
print_plate_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
|
||||
m_print_btn->SetLabel(_L("Print plate"));
|
||||
m_print_select = ePrintPlate;
|
||||
m_print_enable = get_enable_print_status();
|
||||
m_print_btn->Enable(m_print_enable);
|
||||
this->Layout();
|
||||
fit_tab_labels(); // ORCA on label change
|
||||
p->Dismiss();
|
||||
});
|
||||
|
||||
SideButton* print_all_btn = new SideButton(p, _L("Print all"), "");
|
||||
print_all_btn->SetCornerRadius(0);
|
||||
print_all_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
|
||||
m_print_btn->SetLabel(_L("Print all"));
|
||||
m_print_select = ePrintAll;
|
||||
m_print_enable = get_enable_print_status();
|
||||
m_print_btn->Enable(m_print_enable);
|
||||
this->Layout();
|
||||
fit_tab_labels(); // ORCA on label change
|
||||
p->Dismiss();
|
||||
});
|
||||
|
||||
send_to_printer_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
|
||||
m_print_btn->SetLabel(_L("Send"));
|
||||
m_print_select = eSendToPrinter;
|
||||
m_print_enable = get_enable_print_status();
|
||||
m_print_btn->Enable(m_print_enable);
|
||||
this->Layout();
|
||||
fit_tab_labels(); // ORCA on label change
|
||||
p->Dismiss();
|
||||
});
|
||||
|
||||
SideButton* send_to_printer_all_btn = new SideButton(p, _L("Send all"), "");
|
||||
send_to_printer_all_btn->SetCornerRadius(0);
|
||||
send_to_printer_all_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
|
||||
m_print_btn->SetLabel(_L("Send all"));
|
||||
m_print_select = eSendToPrinterAll;
|
||||
m_print_enable = get_enable_print_status();
|
||||
m_print_btn->Enable(m_print_enable);
|
||||
this->Layout();
|
||||
fit_tab_labels(); // ORCA on label change
|
||||
p->Dismiss();
|
||||
});
|
||||
|
||||
export_sliced_file_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
|
||||
m_print_btn->SetLabel(_L("Export plate sliced file"));
|
||||
m_print_select = eExportSlicedFile;
|
||||
m_print_enable = get_enable_print_status();
|
||||
m_print_btn->Enable(m_print_enable);
|
||||
this->Layout();
|
||||
fit_tab_labels(); // ORCA on label change
|
||||
p->Dismiss();
|
||||
});
|
||||
|
||||
export_all_sliced_file_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
|
||||
m_print_btn->SetLabel(_L("Export all sliced file"));
|
||||
m_print_select = eExportAllSlicedFile;
|
||||
m_print_enable = get_enable_print_status();
|
||||
m_print_btn->Enable(m_print_enable);
|
||||
this->Layout();
|
||||
fit_tab_labels(); // ORCA on label change
|
||||
p->Dismiss();
|
||||
});
|
||||
|
||||
bool support_send = true;
|
||||
bool support_print_all = true;
|
||||
|
||||
const auto preset_bundle = wxGetApp().preset_bundle;
|
||||
if (preset_bundle) {
|
||||
if (preset_bundle->use_bbl_network() || wxGetApp().app_config->get_bool("use_printer_agents")) {
|
||||
// BBL network support everything
|
||||
} else {
|
||||
support_send = false; // All 3rd print hosts do not have the send options
|
||||
|
||||
auto cfg = preset_bundle->printers.get_edited_preset().config;
|
||||
const auto host_type = cfg.option<ConfigOptionEnum<PrintHostType>>("host_type")->value;
|
||||
|
||||
// Only simply print support uploading all plates
|
||||
support_print_all = host_type == PrintHostType::htSimplyPrint;
|
||||
}
|
||||
}
|
||||
|
||||
p->append_button(print_plate_btn);
|
||||
if (support_print_all) {
|
||||
p->append_button(print_all_btn);
|
||||
}
|
||||
if (support_send) {
|
||||
p->append_button(send_to_printer_btn);
|
||||
p->append_button(send_to_printer_all_btn);
|
||||
}
|
||||
if (enable_multi_machine) {
|
||||
SideButton* print_multi_machine_btn = new SideButton(p, _L("Send to Multi-device"), "");
|
||||
print_multi_machine_btn->SetCornerRadius(0);
|
||||
print_multi_machine_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
|
||||
m_print_btn->SetLabel(_L("Send to Multi-device"));
|
||||
m_print_select = ePrintMultiMachine;
|
||||
m_print_enable = get_enable_print_status();
|
||||
m_print_btn->Enable(m_print_enable);
|
||||
this->Layout();
|
||||
fit_tab_labels(); // ORCA on label change
|
||||
p->Dismiss();
|
||||
});
|
||||
p->append_button(print_multi_machine_btn);
|
||||
}
|
||||
p->append_button(export_sliced_file_btn);
|
||||
p->append_button(export_all_sliced_file_btn);
|
||||
SideButton* export_gcode_btn = new SideButton(p, _L("Export G-code file"), "");
|
||||
export_gcode_btn->SetCornerRadius(0);
|
||||
export_gcode_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
|
||||
m_print_btn->SetLabel(_L("Export G-code file"));
|
||||
m_print_select = eExportGcode;
|
||||
m_print_enable = get_enable_print_status();
|
||||
m_print_btn->Enable(m_print_enable);
|
||||
this->Layout();
|
||||
fit_tab_labels(); // ORCA on label change
|
||||
p->Dismiss();
|
||||
});
|
||||
p->append_button(export_gcode_btn);
|
||||
}
|
||||
|
||||
p->Popup(m_print_btn);
|
||||
}
|
||||
);
|
||||
@@ -4224,38 +4174,22 @@ void MainFrame::on_config_changed(DynamicPrintConfig* config) const
|
||||
|
||||
void MainFrame::set_print_button_to_default(PrintSelectType select_type)
|
||||
{
|
||||
if (select_type == PrintSelectType::ePrintPlate) {
|
||||
m_print_btn->SetLabel(_L("Print plate"));
|
||||
m_print_select = ePrintPlate;
|
||||
// Orca: keep the user's remembered print/export action instead of resetting it to the computed
|
||||
// default. get_remembered_print_select() already rejects anything this printer does not offer.
|
||||
PrintSelectType remembered;
|
||||
if (get_remembered_print_select(remembered))
|
||||
select_type = remembered;
|
||||
|
||||
if (select_type == eUploadGcode)
|
||||
return; // unsupported: no dropdown entry exists for this action
|
||||
|
||||
m_print_btn->SetLabel(print_select_type_label(select_type));
|
||||
m_print_select = select_type;
|
||||
// get_enable_print_status() already applies can_send_gcode() to the actions that need it
|
||||
if (m_print_enable)
|
||||
m_print_enable = get_enable_print_status();
|
||||
m_print_btn->Enable(m_print_enable);
|
||||
this->Layout();
|
||||
} else if (select_type == PrintSelectType::eSendGcode) {
|
||||
m_print_btn->SetLabel(_L_CONTEXT("Print", "Verb"));
|
||||
m_print_select = eSendGcode;
|
||||
if (m_print_enable)
|
||||
m_print_enable = get_enable_print_status() && can_send_gcode();
|
||||
m_print_btn->Enable(m_print_enable);
|
||||
this->Layout();
|
||||
} else if (select_type == PrintSelectType::eExportGcode) {
|
||||
m_print_btn->SetLabel(_L("Export G-code file"));
|
||||
m_print_select = eExportGcode;
|
||||
if (m_print_enable)
|
||||
m_print_enable = get_enable_print_status() && can_send_gcode();
|
||||
m_print_btn->Enable(m_print_enable);
|
||||
this->Layout();
|
||||
} else if (select_type == PrintSelectType::eExportSlicedFile) {
|
||||
m_print_btn->SetLabel(_L("Export plate sliced file"));
|
||||
m_print_select = eExportSlicedFile;
|
||||
if (m_print_enable)
|
||||
m_print_enable = get_enable_print_status();
|
||||
m_print_btn->Enable(m_print_enable);
|
||||
this->Layout();
|
||||
} else {
|
||||
// unsupport
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void MainFrame::add_to_recent_projects(const wxString& filename)
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "GUI_Utils.hpp"
|
||||
#include "Event.hpp"
|
||||
@@ -430,6 +431,14 @@ public:
|
||||
// Propagate changed configuration from the Tab to the Plater and save changes to the AppConfig
|
||||
void on_config_changed(DynamicPrintConfig* cfg) const ;
|
||||
void set_print_button_to_default(PrintSelectType select_type);
|
||||
// Orca: the print/export actions the current printer offers, in the order the dropdown lists them.
|
||||
// The dropdown is built from this, and a remembered action is only restored if it appears here.
|
||||
std::vector<PrintSelectType> available_print_actions() const;
|
||||
// Orca: apply an action picked from the print dropdown to the print button
|
||||
void select_print_action(PrintSelectType select_type);
|
||||
// Orca: remember the user's preferred print/export action across sessions (see "remember_print_action" preference)
|
||||
void remember_print_select(PrintSelectType select_type);
|
||||
bool get_remembered_print_select(PrintSelectType& out) const;
|
||||
|
||||
bool can_save() const;
|
||||
bool can_save_as() const;
|
||||
|
||||
@@ -98,10 +98,7 @@ void MarkdownTip::LoadStyle()
|
||||
ph /= "tooltip/styled.html";
|
||||
_data_dir = false;
|
||||
}
|
||||
auto url = ph.string();
|
||||
std::replace(url.begin(), url.end(), '\\', '/');
|
||||
url = "file:///" + url;
|
||||
_tipView->LoadURL(from_u8(url));
|
||||
_tipView->LoadURL(file_url_from_path(ph));
|
||||
_lastTip.clear();
|
||||
}
|
||||
|
||||
|
||||
+28
-16
@@ -286,6 +286,30 @@ 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);
|
||||
@@ -3672,7 +3696,7 @@ void Sidebar::update_all_preset_comboboxes()
|
||||
wxString url = from_u8(PrintHost::get_print_host_webui(&cfg));
|
||||
wxString apikey;
|
||||
if(url.empty())
|
||||
url = wxString::Format("file://%s/web/orca/missing_connection.html", from_u8(resources_dir()));
|
||||
url = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/orca/missing_connection.html");
|
||||
else {
|
||||
const auto host_type = cfg.option<ConfigOptionEnum<PrintHostType>>("host_type")->value;
|
||||
if (cfg.has("printhost_apikey") && (host_type != htSimplyPrint))
|
||||
@@ -17064,11 +17088,7 @@ void Plater::calib_input_shaping_freq(const Calib_Params& params)
|
||||
set_config_values<double, ConfigOptionFloatsNullable>(print_config, "default_jerk", 0);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
zero_pressure_advance_where_disabled(filament_config);
|
||||
|
||||
printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false});
|
||||
printer_config->set_key_value("input_shaping_emit", new ConfigOptionBool{false});
|
||||
@@ -17130,11 +17150,7 @@ void Plater::calib_input_shaping_damp(const Calib_Params& params)
|
||||
set_config_values<double, ConfigOptionFloatsNullable>(print_config, "default_jerk", 0);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
zero_pressure_advance_where_disabled(filament_config);
|
||||
|
||||
printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false});
|
||||
printer_config->set_key_value("input_shaping_emit", new ConfigOptionBool{false});
|
||||
@@ -17196,11 +17212,7 @@ void Plater::Calib_Cornering(const Calib_Params& params)
|
||||
set_config_values<double, ConfigOptionFloatsNullable>(print_config, "default_jerk", 0);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
zero_pressure_advance_where_disabled(filament_config);
|
||||
|
||||
printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false});
|
||||
printer_config->set_key_value("input_shaping_emit", new ConfigOptionBool{true});
|
||||
|
||||
@@ -1831,6 +1831,9 @@ void PreferencesDialog::create_items()
|
||||
auto item_mix_print_high_low_temperature = create_item_checkbox(_L("Remove mixed temperature restriction"), _L("With this option enabled, you can print materials with a large temperature difference together."), "enable_high_low_temp_mixed_printing");
|
||||
g_sizer->Add(item_mix_print_high_low_temperature);
|
||||
|
||||
auto item_remember_print_action = create_item_checkbox(_L("Remember last print action"), _L("If enabled, OrcaSlicer will remember the last selected option in the print button's dropdown (e.g. Print, Export plate sliced file, Export G-code file) and use it as the default on next startup."), "remember_print_action");
|
||||
g_sizer->Add(item_remember_print_action);
|
||||
|
||||
//// CONTROL > Camera
|
||||
g_sizer->Add(create_item_title(_L("Camera")), 1, wxEXPAND);
|
||||
|
||||
|
||||
@@ -57,9 +57,7 @@ PrivacyUpdateDialog::PrivacyUpdateDialog(wxWindow* parent, wxWindowID id, const
|
||||
|
||||
fs::path ph(resources_dir());
|
||||
ph /= "tooltip/privacyupdate.html";
|
||||
m_host_url = ph.string();
|
||||
std::replace(m_host_url.begin(), m_host_url.end(), '\\', '/');
|
||||
m_host_url = "file:///" + m_host_url;
|
||||
m_host_url = into_u8(file_url_from_path(ph));
|
||||
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 = wxString::Format("file://%s/web/model/index.html", from_u8(resources_dir()));
|
||||
m_project_home_url = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/model/index.html");
|
||||
wxString strlang = wxGetApp().current_language_code_safe();
|
||||
if (strlang != "")
|
||||
m_project_home_url = wxString::Format("file://%s/web/model/index.html?lang=%s", from_u8(resources_dir()), strlang);
|
||||
m_project_home_url += "?lang=" + strlang;
|
||||
|
||||
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
@@ -293,10 +293,8 @@ 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 (fs::exists(path)) {
|
||||
wxLaunchDefaultApplication(path.wstring(), 0);
|
||||
}
|
||||
if (!desktop_open_project_attachment(this, path))
|
||||
BOOST_LOG_TRIVIAL(warning) << "open_3mf_accessory: not opening " << decode_path;
|
||||
}
|
||||
}
|
||||
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://" + (boost::filesystem::path (resources_dir()) / "web/guide/0/index.html").string());
|
||||
m_vebview_release_note->LoadURL(file_url_from_path(boost::filesystem::path(resources_dir()) / "web/guide/0/index.html"));
|
||||
|
||||
SetMinSize(GetSize());
|
||||
SetMaxSize(GetSize());
|
||||
|
||||
+20
-19
@@ -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");
|
||||
optgroup->append_single_option_line("pressure_advance", "material_flow_ratio_and_pressure_advance#pressure-advance");
|
||||
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);
|
||||
|
||||
// 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");
|
||||
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");
|
||||
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);
|
||||
|
||||
Option option = optgroup->get_option("adaptive_pressure_advance_model");
|
||||
Option option = optgroup->get_option("adaptive_pressure_advance_model", 0);
|
||||
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 == "adaptive_pressure_advance_model")
|
||||
if (opt_key.substr(0, opt_key.find('#')) == "adaptive_pressure_advance_model")
|
||||
m_config_manipulation.check_adaptive_pressure_advance_model(&filament_config);
|
||||
|
||||
on_value_change(opt_key, value);
|
||||
@@ -4829,8 +4829,11 @@ void TabFilament::toggle_options()
|
||||
}
|
||||
if (m_active_page->title() == L("Filament"))
|
||||
{
|
||||
bool pa = m_config->opt_bool("enable_pressure_advance", 0);
|
||||
toggle_option("pressure_advance", pa);
|
||||
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);
|
||||
|
||||
//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;
|
||||
@@ -4858,12 +4861,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);
|
||||
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);
|
||||
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);
|
||||
|
||||
bool is_pellet_printer = printer_cfg.opt_bool("pellet_modded_printer");
|
||||
toggle_line("pellet_flow_coefficient", is_pellet_printer);
|
||||
@@ -4871,8 +4874,6 @@ 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);
|
||||
@@ -5205,7 +5206,6 @@ 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,6 +5720,7 @@ 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);
|
||||
@@ -5731,7 +5732,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 && opt_key.find_first_of("nozzle_diameter") != std::string::npos)
|
||||
if (is_SEMM && m_extruders_count > 1 && boost::starts_with(opt_key, "nozzle_diameter"))
|
||||
{
|
||||
SuppressBackgroundProcessingUpdate sbpu;
|
||||
const double new_nd = boost::any_cast<double>(value);
|
||||
|
||||
@@ -31,14 +31,15 @@ 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,9 +38,7 @@ DownPluginFrame::DownPluginFrame(GUI_App *pGUI) : wxDialog((wxWindow *) (pGUI->m
|
||||
|
||||
// set the frame icon
|
||||
wxBoxSizer *topsizer = new wxBoxSizer(wxVERTICAL);
|
||||
wxString TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/6/index.html").make_preferred().string());
|
||||
|
||||
TargetUrl = "file://" + TargetUrl;
|
||||
wxString TargetUrl = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/guide/6/index.html");
|
||||
|
||||
// Create the webview
|
||||
m_browser = WebView::CreateWebView(this, TargetUrl);
|
||||
|
||||
@@ -218,37 +218,38 @@ 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() );
|
||||
wxString TargetUrl = from_u8( (boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=1").make_preferred().string() );
|
||||
const wxString guide_url = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/guide/0/index.html");
|
||||
wxString TargetUrl = guide_url + "?target=1";
|
||||
//wxLogMessage("GUIDE: webpage_2 %s", TargetUrl.mb_str());
|
||||
|
||||
if (startpage == BBL_WELCOME){
|
||||
SetTitle(_L("Setup Wizard"));
|
||||
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=1").make_preferred().string());
|
||||
TargetUrl = guide_url + "?target=1";
|
||||
} else if (startpage == BBL_REGION) {
|
||||
SetTitle(_L("Setup Wizard"));
|
||||
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=11").make_preferred().string());
|
||||
TargetUrl = guide_url + "?target=11";
|
||||
} else if (startpage == BBL_MODELS) {
|
||||
SetTitle(_L("Setup Wizard"));
|
||||
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=21").make_preferred().string());
|
||||
TargetUrl = guide_url + "?target=21";
|
||||
} else if (startpage == BBL_FILAMENTS) {
|
||||
SetTitle(_L("Setup Wizard"));
|
||||
|
||||
int nSize = m_ProfileJson["model"].size();
|
||||
|
||||
if (nSize>0)
|
||||
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=22").make_preferred().string());
|
||||
TargetUrl = guide_url + "?target=22";
|
||||
else
|
||||
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=21").make_preferred().string());
|
||||
TargetUrl = guide_url + "?target=21";
|
||||
} else if (startpage == BBL_FILAMENT_ONLY) {
|
||||
SetTitle("");
|
||||
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=23").make_preferred().string());
|
||||
TargetUrl = guide_url + "?target=23";
|
||||
} else if (startpage == BBL_MODELS_ONLY) {
|
||||
SetTitle("");
|
||||
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=24").make_preferred().string());
|
||||
TargetUrl = guide_url + "?target=24";
|
||||
}
|
||||
else {
|
||||
SetTitle(_L("Setup Wizard"));
|
||||
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=21").make_preferred().string());
|
||||
TargetUrl = guide_url + "?target=21";
|
||||
}
|
||||
|
||||
wxString strlang = wxGetApp().current_language_code_safe();
|
||||
@@ -256,7 +257,6 @@ 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"] = cover_path.string();
|
||||
entry["cover"] = into_u8(file_url_from_path(cover_path));
|
||||
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,26 +1461,18 @@ 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;
|
||||
@@ -1732,7 +1724,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"] = cover_path.string();
|
||||
OneModel["cover"] = into_u8(file_url_from_path(cover_path));
|
||||
|
||||
OneModel["nozzle_selected"] = "";
|
||||
|
||||
|
||||
@@ -599,7 +599,7 @@ void ZUserLogin::OnScriptResponseMessage(wxCommandEvent &WXUNUSED(evt))
|
||||
|
||||
bool ZUserLogin::ShowErrorPage()
|
||||
{
|
||||
wxString ErrortUrl = from_u8((boost::filesystem::path(resources_dir()) / "web\\login\\error.html").make_preferred().string());
|
||||
wxString ErrortUrl = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/login/error.html");
|
||||
load_url(ErrortUrl);
|
||||
|
||||
return true;
|
||||
|
||||
@@ -36,10 +36,10 @@ namespace GUI {
|
||||
WebViewPanel::WebViewPanel(wxWindow *parent)
|
||||
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
|
||||
{
|
||||
wxString url = wxString::Format("file://%s/web/homepage/index.html", from_u8(resources_dir()));
|
||||
wxString url = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/homepage/index.html");
|
||||
wxString strlang = wxGetApp().current_language_code_safe();
|
||||
if (strlang != "")
|
||||
url = wxString::Format("file://%s/web/homepage/index.html?lang=%s", from_u8(resources_dir()), strlang);
|
||||
url += "?lang=" + strlang;
|
||||
|
||||
wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "MultiNozzleSync.hpp"
|
||||
|
||||
#include "../GUI.hpp"
|
||||
#include "../GUI_App.hpp"
|
||||
#include "../I18N.hpp"
|
||||
#include "../Plater.hpp"
|
||||
@@ -21,8 +22,6 @@
|
||||
#include <set>
|
||||
|
||||
#include <wx/choice.h>
|
||||
#include <wx/filename.h>
|
||||
#include <wx/filesys.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
|
||||
@@ -641,8 +640,7 @@ 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";
|
||||
wxFileName fn(wxString::FromUTF8(filepath.string()));
|
||||
wxString url = wxFileSystem::FileNameToURL(fn);
|
||||
wxString url = file_url_from_path(filepath);
|
||||
m_web_view->LoadURL(url);
|
||||
|
||||
auto sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
@@ -48,20 +48,19 @@ constexpr char ORCA_BRIDGE_JS[] = R"JS(
|
||||
|
||||
wxString bootstrap_url()
|
||||
{
|
||||
return wxString("file://") + from_u8((boost::filesystem::path(resources_dir()) / BOOTSTRAP_PAGE).make_preferred().string());
|
||||
return file_url_from_path(boost::filesystem::path(resources_dir()) / BOOTSTRAP_PAGE);
|
||||
}
|
||||
|
||||
wxString content_base_url()
|
||||
{
|
||||
const std::string dir = (boost::filesystem::path(resources_dir()) / "web").make_preferred().string();
|
||||
return wxString("file://") + from_u8(dir) + "/";
|
||||
return file_url_from_path(boost::filesystem::path(resources_dir()) / "web") + "/";
|
||||
}
|
||||
|
||||
bool is_content_url(const wxString& 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();
|
||||
// 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());
|
||||
}
|
||||
|
||||
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 = from_u8((boost::filesystem::path(resources_dir()) / resource_path).make_preferred().string());
|
||||
wxString target_url = file_url_from_path(boost::filesystem::path(resources_dir()) / resource_path);
|
||||
|
||||
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 wxString("file://") + target_url;
|
||||
return 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 = wxFileSystem::FileNameToURL(fn);
|
||||
wxString url = file_url_from_path(filepath);
|
||||
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();
|
||||
|
||||
@@ -274,13 +274,4 @@ 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,7 +40,6 @@ 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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user