mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-29 20:01:26 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2c856a37e | ||
|
|
40028f7b61 | ||
|
|
88f5905c29 | ||
|
|
f8e531f07b | ||
|
|
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
|
2. Every key in `filament_options_with_variant` ([the full list](#the-four-key-sets)) that reaches the
|
||||||
preset, whether written, included or inherited, is resized to its variant count: write it at exactly
|
preset, whether written, included or inherited, is resized to its variant count: write it at exactly
|
||||||
that width, or leave it out. Keys outside the set
|
that width, or leave it out. Keys outside the set
|
||||||
(`filament_type`, plate temperatures, `fan_max_speed`, `slow_down_min_speed`, `pressure_advance`,
|
(`filament_type`, plate temperatures, `fan_max_speed`, `slow_down_min_speed`, …) are never addressed by variant index; the preset contributes their first value however wide a
|
||||||
…) are never addressed by variant index; the preset contributes their first value however wide a
|
|
||||||
file writes them.
|
file writes them.
|
||||||
3. Cover every variant the material is meant to print on across its `compatible_printers`. Leave out
|
3. Cover every variant the material is meant to print on across its `compatible_printers`. Leave out
|
||||||
a variant deliberately when the material should not be tuned for it (e.g. a TPU High Flow variant
|
a variant deliberately when the material should not be tuned for it (e.g. a TPU High Flow variant
|
||||||
@@ -455,7 +454,7 @@ no set: the variant-length resize leaves it alone, and only the pair rebuild of
|
|||||||
last for contrast; it is not a variant set.
|
last for contrast; it is not a variant set.
|
||||||
|
|
||||||
`check` and `fix-variant` read the four sets from `PrintConfig.cpp` on every run, so they follow the
|
`check` and `fix-variant` read the four sets from `PrintConfig.cpp` on every run, so they follow the
|
||||||
engine. The lists below are from the 2026-09-25 checkout; regenerate them from the repository root
|
engine. The lists below are from the 2026-09-29 checkout; regenerate them from the repository root
|
||||||
before relying on them (the recipe strips comments, since an initializer can carry a commented-out entry):
|
before relying on them (the recipe strips comments, since an initializer can carry a commented-out entry):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -478,7 +477,7 @@ EOF
|
|||||||
|
|
||||||
**`print_options_with_variant`**, process, stride 1 (45): `bridge_acceleration`, `bridge_speed`, `default_acceleration`, `default_jerk`, `default_junction_deviation`, `enable_overhang_speed`, `gap_infill_speed`, `infill_jerk`, `initial_layer_acceleration`, `initial_layer_infill_speed`, `initial_layer_jerk`, `initial_layer_speed`, `initial_layer_travel_acceleration`, `initial_layer_travel_jerk`, `initial_layer_travel_speed`, `inner_wall_acceleration`, `inner_wall_jerk`, `inner_wall_speed`, `internal_bridge_speed`, `internal_solid_infill_acceleration`, `internal_solid_infill_speed`, `outer_wall_acceleration`, `outer_wall_jerk`, `outer_wall_speed`, `overhang_1_4_speed`, `overhang_2_4_speed`, `overhang_3_4_speed`, `overhang_4_4_speed`, `print_extruder_id`, `print_extruder_variant`, `slowdown_for_curled_perimeters`, `small_perimeter_speed`, `small_perimeter_threshold`, `sparse_infill_acceleration`, `sparse_infill_speed`, `support_interface_speed`, `support_speed`, `top_solid_infill_flow_ratio`, `top_surface_acceleration`, `top_surface_jerk`, `top_surface_speed`, `travel_acceleration`, `travel_jerk`, `travel_speed`, `travel_speed_z`
|
**`print_options_with_variant`**, process, stride 1 (45): `bridge_acceleration`, `bridge_speed`, `default_acceleration`, `default_jerk`, `default_junction_deviation`, `enable_overhang_speed`, `gap_infill_speed`, `infill_jerk`, `initial_layer_acceleration`, `initial_layer_infill_speed`, `initial_layer_jerk`, `initial_layer_speed`, `initial_layer_travel_acceleration`, `initial_layer_travel_jerk`, `initial_layer_travel_speed`, `inner_wall_acceleration`, `inner_wall_jerk`, `inner_wall_speed`, `internal_bridge_speed`, `internal_solid_infill_acceleration`, `internal_solid_infill_speed`, `outer_wall_acceleration`, `outer_wall_jerk`, `outer_wall_speed`, `overhang_1_4_speed`, `overhang_2_4_speed`, `overhang_3_4_speed`, `overhang_4_4_speed`, `print_extruder_id`, `print_extruder_variant`, `slowdown_for_curled_perimeters`, `small_perimeter_speed`, `small_perimeter_threshold`, `sparse_infill_acceleration`, `sparse_infill_speed`, `support_interface_speed`, `support_speed`, `top_solid_infill_flow_ratio`, `top_surface_acceleration`, `top_surface_jerk`, `top_surface_speed`, `travel_acceleration`, `travel_jerk`, `travel_speed`, `travel_speed_z`
|
||||||
|
|
||||||
**`filament_options_with_variant`**, filament, stride 1 (48): `activate_air_filtration`, `activate_air_filtration_during_print`, `activate_air_filtration_on_completion`, `complete_print_exhaust_fan_speed`, `during_print_exhaust_fan_speed`, `filament_adaptive_volumetric_speed`, `filament_cooling_before_tower`, `filament_deretraction_speed`, `filament_extruder_variant`, `filament_flow_ratio`, `filament_flush_temp`, `filament_flush_volumetric_speed`, `filament_ironing_flow`, `filament_ironing_inset`, `filament_ironing_spacing`, `filament_ironing_speed`, `filament_long_retractions_when_cut`, `filament_max_volumetric_speed`, `filament_pre_cooling_temperature`, `filament_pre_cooling_temperature_nc`, `filament_preheat_temperature_delta`, `filament_ramming_travel_time`, `filament_ramming_travel_time_nc`, `filament_ramming_volumetric_speed`, `filament_ramming_volumetric_speed_nc`, `filament_retract_after_wipe`, `filament_retract_before_wipe`, `filament_retract_length_nc`, `filament_retract_length_toolchange`, `filament_retract_lift_above`, `filament_retract_lift_below`, `filament_retract_lift_enforce`, `filament_retract_restart_extra`, `filament_retract_restart_extra_toolchange`, `filament_retract_when_changing_layer`, `filament_retraction_distances_when_cut`, `filament_retraction_length`, `filament_retraction_minimum_travel`, `filament_retraction_speed`, `filament_wipe`, `filament_wipe_distance`, `filament_z_hop`, `filament_z_hop_types`, `long_retractions_when_ec`, `nozzle_temperature`, `nozzle_temperature_initial_layer`, `retraction_distances_when_ec`, `volumetric_speed_coefficients`
|
**`filament_options_with_variant`**, filament, stride 1 (54): `activate_air_filtration`, `activate_air_filtration_during_print`, `activate_air_filtration_on_completion`, `adaptive_pressure_advance`, `adaptive_pressure_advance_bridges`, `adaptive_pressure_advance_model`, `adaptive_pressure_advance_overhangs`, `complete_print_exhaust_fan_speed`, `during_print_exhaust_fan_speed`, `enable_pressure_advance`, `filament_adaptive_volumetric_speed`, `filament_cooling_before_tower`, `filament_deretraction_speed`, `filament_extruder_variant`, `filament_flow_ratio`, `filament_flush_temp`, `filament_flush_volumetric_speed`, `filament_ironing_flow`, `filament_ironing_inset`, `filament_ironing_spacing`, `filament_ironing_speed`, `filament_long_retractions_when_cut`, `filament_max_volumetric_speed`, `filament_pre_cooling_temperature`, `filament_pre_cooling_temperature_nc`, `filament_preheat_temperature_delta`, `filament_ramming_travel_time`, `filament_ramming_travel_time_nc`, `filament_ramming_volumetric_speed`, `filament_ramming_volumetric_speed_nc`, `filament_retract_after_wipe`, `filament_retract_before_wipe`, `filament_retract_length_nc`, `filament_retract_length_toolchange`, `filament_retract_lift_above`, `filament_retract_lift_below`, `filament_retract_lift_enforce`, `filament_retract_restart_extra`, `filament_retract_restart_extra_toolchange`, `filament_retract_when_changing_layer`, `filament_retraction_distances_when_cut`, `filament_retraction_length`, `filament_retraction_minimum_travel`, `filament_retraction_speed`, `filament_wipe`, `filament_wipe_distance`, `filament_z_hop`, `filament_z_hop_types`, `long_retractions_when_ec`, `nozzle_temperature`, `nozzle_temperature_initial_layer`, `pressure_advance`, `retraction_distances_when_ec`, `volumetric_speed_coefficients`
|
||||||
|
|
||||||
**`printer_extruder_options`**, machine, one value per extruder, not a variant set (8):
|
**`printer_extruder_options`**, machine, one value per extruder, not a variant set (8):
|
||||||
`default_nozzle_volume_type`, `extruder_max_nozzle_count`, `extruder_printable_area`,
|
`default_nozzle_volume_type`, `extruder_max_nozzle_count`, `extruder_printable_area`,
|
||||||
|
|||||||
@@ -244,10 +244,10 @@ preset for a 0.2 nozzle, explicitly revisit flow limits; do not infer a pressure
|
|||||||
required direction of change, from diameter alone.
|
required direction of change, from diameter alone.
|
||||||
|
|
||||||
On a printer with extruder variants, a filament tunes these per variant too:
|
On a printer with extruder variants, a filament tunes these per variant too:
|
||||||
`filament_max_volumetric_speed`, `filament_flow_ratio`, `nozzle_temperature` and the retraction
|
`filament_max_volumetric_speed`, `filament_flow_ratio`, `nozzle_temperature`, pressure advance and the
|
||||||
overrides carry one value per variant of `filament_extruder_variant` (Standard, High Flow, …). The
|
retraction overrides carry one value per variant of `filament_extruder_variant` (Standard, High Flow, …). The
|
||||||
exact key set is [`filament_options_with_variant`](extruder-variants.md#the-four-key-sets);
|
exact key set is [`filament_options_with_variant`](extruder-variants.md#the-four-key-sets);
|
||||||
`slow_down_min_speed` and `pressure_advance` are not in it. Keep every such array at exactly that width, even where the
|
`slow_down_min_speed` and `fan_max_speed` are not in it. Keep every such array at exactly that width, even where the
|
||||||
setting does not differ per variant, and measure the High Flow variant rather than copying Standard
|
setting does not differ per variant, and measure the High Flow variant rather than copying Standard
|
||||||
([extruder-variants.md](extruder-variants.md#filament)).
|
([extruder-variants.md](extruder-variants.md#filament)).
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "OrcaFilamentLibrary",
|
"name": "OrcaFilamentLibrary",
|
||||||
"version": "02.04.00.15",
|
"version": "02.04.00.16",
|
||||||
"force_update": "0",
|
"force_update": "0",
|
||||||
"description": "Orca Filament Library",
|
"description": "Orca Filament Library",
|
||||||
"filament_list": [
|
"filament_list": [
|
||||||
|
|||||||
@@ -111,6 +111,7 @@
|
|||||||
"1"
|
"1"
|
||||||
],
|
],
|
||||||
"enable_pressure_advance": [
|
"enable_pressure_advance": [
|
||||||
|
"0",
|
||||||
"0"
|
"0"
|
||||||
],
|
],
|
||||||
"eng_plate_temp": [
|
"eng_plate_temp": [
|
||||||
@@ -316,6 +317,7 @@
|
|||||||
"2"
|
"2"
|
||||||
],
|
],
|
||||||
"pressure_advance": [
|
"pressure_advance": [
|
||||||
|
"0.02",
|
||||||
"0.02"
|
"0.02"
|
||||||
],
|
],
|
||||||
"reduce_fan_stop_start_freq": [
|
"reduce_fan_stop_start_freq": [
|
||||||
|
|||||||
+2
@@ -111,6 +111,7 @@
|
|||||||
"1"
|
"1"
|
||||||
],
|
],
|
||||||
"enable_pressure_advance": [
|
"enable_pressure_advance": [
|
||||||
|
"0",
|
||||||
"0"
|
"0"
|
||||||
],
|
],
|
||||||
"eng_plate_temp": [
|
"eng_plate_temp": [
|
||||||
@@ -316,6 +317,7 @@
|
|||||||
"2"
|
"2"
|
||||||
],
|
],
|
||||||
"pressure_advance": [
|
"pressure_advance": [
|
||||||
|
"0.02",
|
||||||
"0.02"
|
"0.02"
|
||||||
],
|
],
|
||||||
"reduce_fan_stop_start_freq": [
|
"reduce_fan_stop_start_freq": [
|
||||||
|
|||||||
@@ -111,6 +111,7 @@
|
|||||||
"1"
|
"1"
|
||||||
],
|
],
|
||||||
"enable_pressure_advance": [
|
"enable_pressure_advance": [
|
||||||
|
"0",
|
||||||
"0"
|
"0"
|
||||||
],
|
],
|
||||||
"eng_plate_temp": [
|
"eng_plate_temp": [
|
||||||
@@ -316,6 +317,7 @@
|
|||||||
"2"
|
"2"
|
||||||
],
|
],
|
||||||
"pressure_advance": [
|
"pressure_advance": [
|
||||||
|
"0.02",
|
||||||
"0.02"
|
"0.02"
|
||||||
],
|
],
|
||||||
"reduce_fan_stop_start_freq": [
|
"reduce_fan_stop_start_freq": [
|
||||||
|
|||||||
+2
@@ -111,6 +111,7 @@
|
|||||||
"1"
|
"1"
|
||||||
],
|
],
|
||||||
"enable_pressure_advance": [
|
"enable_pressure_advance": [
|
||||||
|
"0",
|
||||||
"0"
|
"0"
|
||||||
],
|
],
|
||||||
"eng_plate_temp": [
|
"eng_plate_temp": [
|
||||||
@@ -316,6 +317,7 @@
|
|||||||
"2"
|
"2"
|
||||||
],
|
],
|
||||||
"pressure_advance": [
|
"pressure_advance": [
|
||||||
|
"0.02",
|
||||||
"0.02"
|
"0.02"
|
||||||
],
|
],
|
||||||
"reduce_fan_stop_start_freq": [
|
"reduce_fan_stop_start_freq": [
|
||||||
|
|||||||
+2
@@ -111,6 +111,7 @@
|
|||||||
"1"
|
"1"
|
||||||
],
|
],
|
||||||
"enable_pressure_advance": [
|
"enable_pressure_advance": [
|
||||||
|
"0",
|
||||||
"0"
|
"0"
|
||||||
],
|
],
|
||||||
"eng_plate_temp": [
|
"eng_plate_temp": [
|
||||||
@@ -316,6 +317,7 @@
|
|||||||
"2"
|
"2"
|
||||||
],
|
],
|
||||||
"pressure_advance": [
|
"pressure_advance": [
|
||||||
|
"0.02",
|
||||||
"0.02"
|
"0.02"
|
||||||
],
|
],
|
||||||
"reduce_fan_stop_start_freq": [
|
"reduce_fan_stop_start_freq": [
|
||||||
|
|||||||
+2
@@ -111,6 +111,7 @@
|
|||||||
"1"
|
"1"
|
||||||
],
|
],
|
||||||
"enable_pressure_advance": [
|
"enable_pressure_advance": [
|
||||||
|
"0",
|
||||||
"0"
|
"0"
|
||||||
],
|
],
|
||||||
"eng_plate_temp": [
|
"eng_plate_temp": [
|
||||||
@@ -316,6 +317,7 @@
|
|||||||
"2"
|
"2"
|
||||||
],
|
],
|
||||||
"pressure_advance": [
|
"pressure_advance": [
|
||||||
|
"0.02",
|
||||||
"0.02"
|
"0.02"
|
||||||
],
|
],
|
||||||
"reduce_fan_stop_start_freq": [
|
"reduce_fan_stop_start_freq": [
|
||||||
|
|||||||
@@ -111,6 +111,7 @@
|
|||||||
"1"
|
"1"
|
||||||
],
|
],
|
||||||
"enable_pressure_advance": [
|
"enable_pressure_advance": [
|
||||||
|
"0",
|
||||||
"0"
|
"0"
|
||||||
],
|
],
|
||||||
"eng_plate_temp": [
|
"eng_plate_temp": [
|
||||||
@@ -316,6 +317,7 @@
|
|||||||
"2"
|
"2"
|
||||||
],
|
],
|
||||||
"pressure_advance": [
|
"pressure_advance": [
|
||||||
|
"0.02",
|
||||||
"0.02"
|
"0.02"
|
||||||
],
|
],
|
||||||
"reduce_fan_stop_start_freq": [
|
"reduce_fan_stop_start_freq": [
|
||||||
|
|||||||
@@ -111,6 +111,7 @@
|
|||||||
"1"
|
"1"
|
||||||
],
|
],
|
||||||
"enable_pressure_advance": [
|
"enable_pressure_advance": [
|
||||||
|
"0",
|
||||||
"0"
|
"0"
|
||||||
],
|
],
|
||||||
"eng_plate_temp": [
|
"eng_plate_temp": [
|
||||||
@@ -316,6 +317,7 @@
|
|||||||
"2"
|
"2"
|
||||||
],
|
],
|
||||||
"pressure_advance": [
|
"pressure_advance": [
|
||||||
|
"0.02",
|
||||||
"0.02"
|
"0.02"
|
||||||
],
|
],
|
||||||
"reduce_fan_stop_start_freq": [
|
"reduce_fan_stop_start_freq": [
|
||||||
|
|||||||
+2
@@ -111,6 +111,7 @@
|
|||||||
"1"
|
"1"
|
||||||
],
|
],
|
||||||
"enable_pressure_advance": [
|
"enable_pressure_advance": [
|
||||||
|
"0",
|
||||||
"0"
|
"0"
|
||||||
],
|
],
|
||||||
"eng_plate_temp": [
|
"eng_plate_temp": [
|
||||||
@@ -316,6 +317,7 @@
|
|||||||
"2"
|
"2"
|
||||||
],
|
],
|
||||||
"pressure_advance": [
|
"pressure_advance": [
|
||||||
|
"0.02",
|
||||||
"0.02"
|
"0.02"
|
||||||
],
|
],
|
||||||
"reduce_fan_stop_start_freq": [
|
"reduce_fan_stop_start_freq": [
|
||||||
|
|||||||
+2
@@ -111,6 +111,7 @@
|
|||||||
"1"
|
"1"
|
||||||
],
|
],
|
||||||
"enable_pressure_advance": [
|
"enable_pressure_advance": [
|
||||||
|
"0",
|
||||||
"0"
|
"0"
|
||||||
],
|
],
|
||||||
"eng_plate_temp": [
|
"eng_plate_temp": [
|
||||||
@@ -316,6 +317,7 @@
|
|||||||
"2"
|
"2"
|
||||||
],
|
],
|
||||||
"pressure_advance": [
|
"pressure_advance": [
|
||||||
|
"0.02",
|
||||||
"0.02"
|
"0.02"
|
||||||
],
|
],
|
||||||
"reduce_fan_stop_start_freq": [
|
"reduce_fan_stop_start_freq": [
|
||||||
|
|||||||
+2
@@ -111,6 +111,7 @@
|
|||||||
"1"
|
"1"
|
||||||
],
|
],
|
||||||
"enable_pressure_advance": [
|
"enable_pressure_advance": [
|
||||||
|
"0",
|
||||||
"0"
|
"0"
|
||||||
],
|
],
|
||||||
"eng_plate_temp": [
|
"eng_plate_temp": [
|
||||||
@@ -316,6 +317,7 @@
|
|||||||
"1"
|
"1"
|
||||||
],
|
],
|
||||||
"pressure_advance": [
|
"pressure_advance": [
|
||||||
|
"0.02",
|
||||||
"0.02"
|
"0.02"
|
||||||
],
|
],
|
||||||
"reduce_fan_stop_start_freq": [
|
"reduce_fan_stop_start_freq": [
|
||||||
|
|||||||
+2
@@ -111,6 +111,7 @@
|
|||||||
"1"
|
"1"
|
||||||
],
|
],
|
||||||
"enable_pressure_advance": [
|
"enable_pressure_advance": [
|
||||||
|
"0",
|
||||||
"0"
|
"0"
|
||||||
],
|
],
|
||||||
"eng_plate_temp": [
|
"eng_plate_temp": [
|
||||||
@@ -316,6 +317,7 @@
|
|||||||
"2"
|
"2"
|
||||||
],
|
],
|
||||||
"pressure_advance": [
|
"pressure_advance": [
|
||||||
|
"0.02",
|
||||||
"0.02"
|
"0.02"
|
||||||
],
|
],
|
||||||
"reduce_fan_stop_start_freq": [
|
"reduce_fan_stop_start_freq": [
|
||||||
|
|||||||
+2
@@ -111,6 +111,7 @@
|
|||||||
"1"
|
"1"
|
||||||
],
|
],
|
||||||
"enable_pressure_advance": [
|
"enable_pressure_advance": [
|
||||||
|
"0",
|
||||||
"0"
|
"0"
|
||||||
],
|
],
|
||||||
"eng_plate_temp": [
|
"eng_plate_temp": [
|
||||||
@@ -316,6 +317,7 @@
|
|||||||
"0"
|
"0"
|
||||||
],
|
],
|
||||||
"pressure_advance": [
|
"pressure_advance": [
|
||||||
|
"0.02",
|
||||||
"0.02"
|
"0.02"
|
||||||
],
|
],
|
||||||
"reduce_fan_stop_start_freq": [
|
"reduce_fan_stop_start_freq": [
|
||||||
|
|||||||
@@ -111,6 +111,7 @@
|
|||||||
"1"
|
"1"
|
||||||
],
|
],
|
||||||
"enable_pressure_advance": [
|
"enable_pressure_advance": [
|
||||||
|
"0",
|
||||||
"0"
|
"0"
|
||||||
],
|
],
|
||||||
"eng_plate_temp": [
|
"eng_plate_temp": [
|
||||||
@@ -316,6 +317,7 @@
|
|||||||
"2"
|
"2"
|
||||||
],
|
],
|
||||||
"pressure_advance": [
|
"pressure_advance": [
|
||||||
|
"0.02",
|
||||||
"0.02"
|
"0.02"
|
||||||
],
|
],
|
||||||
"reduce_fan_stop_start_freq": [
|
"reduce_fan_stop_start_freq": [
|
||||||
|
|||||||
@@ -111,6 +111,7 @@
|
|||||||
"1"
|
"1"
|
||||||
],
|
],
|
||||||
"enable_pressure_advance": [
|
"enable_pressure_advance": [
|
||||||
|
"0",
|
||||||
"0"
|
"0"
|
||||||
],
|
],
|
||||||
"eng_plate_temp": [
|
"eng_plate_temp": [
|
||||||
@@ -316,6 +317,7 @@
|
|||||||
"2"
|
"2"
|
||||||
],
|
],
|
||||||
"pressure_advance": [
|
"pressure_advance": [
|
||||||
|
"0.02",
|
||||||
"0.02"
|
"0.02"
|
||||||
],
|
],
|
||||||
"reduce_fan_stop_start_freq": [
|
"reduce_fan_stop_start_freq": [
|
||||||
|
|||||||
+2
@@ -111,6 +111,7 @@
|
|||||||
"1"
|
"1"
|
||||||
],
|
],
|
||||||
"enable_pressure_advance": [
|
"enable_pressure_advance": [
|
||||||
|
"0",
|
||||||
"0"
|
"0"
|
||||||
],
|
],
|
||||||
"eng_plate_temp": [
|
"eng_plate_temp": [
|
||||||
@@ -316,6 +317,7 @@
|
|||||||
"2"
|
"2"
|
||||||
],
|
],
|
||||||
"pressure_advance": [
|
"pressure_advance": [
|
||||||
|
"0.02",
|
||||||
"0.02"
|
"0.02"
|
||||||
],
|
],
|
||||||
"reduce_fan_stop_start_freq": [
|
"reduce_fan_stop_start_freq": [
|
||||||
|
|||||||
+2
@@ -111,6 +111,7 @@
|
|||||||
"1"
|
"1"
|
||||||
],
|
],
|
||||||
"enable_pressure_advance": [
|
"enable_pressure_advance": [
|
||||||
|
"0",
|
||||||
"0"
|
"0"
|
||||||
],
|
],
|
||||||
"eng_plate_temp": [
|
"eng_plate_temp": [
|
||||||
@@ -316,6 +317,7 @@
|
|||||||
"2"
|
"2"
|
||||||
],
|
],
|
||||||
"pressure_advance": [
|
"pressure_advance": [
|
||||||
|
"0.02",
|
||||||
"0.02"
|
"0.02"
|
||||||
],
|
],
|
||||||
"reduce_fan_stop_start_freq": [
|
"reduce_fan_stop_start_freq": [
|
||||||
|
|||||||
@@ -111,6 +111,7 @@
|
|||||||
"1"
|
"1"
|
||||||
],
|
],
|
||||||
"enable_pressure_advance": [
|
"enable_pressure_advance": [
|
||||||
|
"0",
|
||||||
"0"
|
"0"
|
||||||
],
|
],
|
||||||
"eng_plate_temp": [
|
"eng_plate_temp": [
|
||||||
@@ -316,6 +317,7 @@
|
|||||||
"2"
|
"2"
|
||||||
],
|
],
|
||||||
"pressure_advance": [
|
"pressure_advance": [
|
||||||
|
"0.02",
|
||||||
"0.02"
|
"0.02"
|
||||||
],
|
],
|
||||||
"reduce_fan_stop_start_freq": [
|
"reduce_fan_stop_start_freq": [
|
||||||
|
|||||||
@@ -111,6 +111,7 @@
|
|||||||
"1"
|
"1"
|
||||||
],
|
],
|
||||||
"enable_pressure_advance": [
|
"enable_pressure_advance": [
|
||||||
|
"0",
|
||||||
"0"
|
"0"
|
||||||
],
|
],
|
||||||
"eng_plate_temp": [
|
"eng_plate_temp": [
|
||||||
@@ -316,6 +317,7 @@
|
|||||||
"0"
|
"0"
|
||||||
],
|
],
|
||||||
"pressure_advance": [
|
"pressure_advance": [
|
||||||
|
"0.02",
|
||||||
"0.02"
|
"0.02"
|
||||||
],
|
],
|
||||||
"reduce_fan_stop_start_freq": [
|
"reduce_fan_stop_start_freq": [
|
||||||
|
|||||||
+2
@@ -111,6 +111,7 @@
|
|||||||
"1"
|
"1"
|
||||||
],
|
],
|
||||||
"enable_pressure_advance": [
|
"enable_pressure_advance": [
|
||||||
|
"0",
|
||||||
"0"
|
"0"
|
||||||
],
|
],
|
||||||
"eng_plate_temp": [
|
"eng_plate_temp": [
|
||||||
@@ -316,6 +317,7 @@
|
|||||||
"2"
|
"2"
|
||||||
],
|
],
|
||||||
"pressure_advance": [
|
"pressure_advance": [
|
||||||
|
"0.02",
|
||||||
"0.02"
|
"0.02"
|
||||||
],
|
],
|
||||||
"reduce_fan_stop_start_freq": [
|
"reduce_fan_stop_start_freq": [
|
||||||
|
|||||||
@@ -111,6 +111,7 @@
|
|||||||
"1"
|
"1"
|
||||||
],
|
],
|
||||||
"enable_pressure_advance": [
|
"enable_pressure_advance": [
|
||||||
|
"0",
|
||||||
"0"
|
"0"
|
||||||
],
|
],
|
||||||
"eng_plate_temp": [
|
"eng_plate_temp": [
|
||||||
@@ -316,6 +317,7 @@
|
|||||||
"2"
|
"2"
|
||||||
],
|
],
|
||||||
"pressure_advance": [
|
"pressure_advance": [
|
||||||
|
"0.02",
|
||||||
"0.02"
|
"0.02"
|
||||||
],
|
],
|
||||||
"reduce_fan_stop_start_freq": [
|
"reduce_fan_stop_start_freq": [
|
||||||
|
|||||||
+2
@@ -111,6 +111,7 @@
|
|||||||
"1"
|
"1"
|
||||||
],
|
],
|
||||||
"enable_pressure_advance": [
|
"enable_pressure_advance": [
|
||||||
|
"0",
|
||||||
"0"
|
"0"
|
||||||
],
|
],
|
||||||
"eng_plate_temp": [
|
"eng_plate_temp": [
|
||||||
@@ -316,6 +317,7 @@
|
|||||||
"2"
|
"2"
|
||||||
],
|
],
|
||||||
"pressure_advance": [
|
"pressure_advance": [
|
||||||
|
"0.02",
|
||||||
"0.02"
|
"0.02"
|
||||||
],
|
],
|
||||||
"reduce_fan_stop_start_freq": [
|
"reduce_fan_stop_start_freq": [
|
||||||
|
|||||||
+2
@@ -111,6 +111,7 @@
|
|||||||
"1"
|
"1"
|
||||||
],
|
],
|
||||||
"enable_pressure_advance": [
|
"enable_pressure_advance": [
|
||||||
|
"0",
|
||||||
"0"
|
"0"
|
||||||
],
|
],
|
||||||
"eng_plate_temp": [
|
"eng_plate_temp": [
|
||||||
@@ -316,6 +317,7 @@
|
|||||||
"2"
|
"2"
|
||||||
],
|
],
|
||||||
"pressure_advance": [
|
"pressure_advance": [
|
||||||
|
"0.02",
|
||||||
"0.02"
|
"0.02"
|
||||||
],
|
],
|
||||||
"reduce_fan_stop_start_freq": [
|
"reduce_fan_stop_start_freq": [
|
||||||
|
|||||||
+2
@@ -111,6 +111,7 @@
|
|||||||
"1"
|
"1"
|
||||||
],
|
],
|
||||||
"enable_pressure_advance": [
|
"enable_pressure_advance": [
|
||||||
|
"0",
|
||||||
"0"
|
"0"
|
||||||
],
|
],
|
||||||
"eng_plate_temp": [
|
"eng_plate_temp": [
|
||||||
@@ -316,6 +317,7 @@
|
|||||||
"2"
|
"2"
|
||||||
],
|
],
|
||||||
"pressure_advance": [
|
"pressure_advance": [
|
||||||
|
"0.02",
|
||||||
"0.02"
|
"0.02"
|
||||||
],
|
],
|
||||||
"reduce_fan_stop_start_freq": [
|
"reduce_fan_stop_start_freq": [
|
||||||
|
|||||||
+2
@@ -111,6 +111,7 @@
|
|||||||
"1"
|
"1"
|
||||||
],
|
],
|
||||||
"enable_pressure_advance": [
|
"enable_pressure_advance": [
|
||||||
|
"0",
|
||||||
"0"
|
"0"
|
||||||
],
|
],
|
||||||
"eng_plate_temp": [
|
"eng_plate_temp": [
|
||||||
@@ -316,6 +317,7 @@
|
|||||||
"2"
|
"2"
|
||||||
],
|
],
|
||||||
"pressure_advance": [
|
"pressure_advance": [
|
||||||
|
"0.02",
|
||||||
"0.02"
|
"0.02"
|
||||||
],
|
],
|
||||||
"reduce_fan_stop_start_freq": [
|
"reduce_fan_stop_start_freq": [
|
||||||
|
|||||||
+2
@@ -111,6 +111,7 @@
|
|||||||
"1"
|
"1"
|
||||||
],
|
],
|
||||||
"enable_pressure_advance": [
|
"enable_pressure_advance": [
|
||||||
|
"0",
|
||||||
"0"
|
"0"
|
||||||
],
|
],
|
||||||
"eng_plate_temp": [
|
"eng_plate_temp": [
|
||||||
@@ -316,6 +317,7 @@
|
|||||||
"2"
|
"2"
|
||||||
],
|
],
|
||||||
"pressure_advance": [
|
"pressure_advance": [
|
||||||
|
"0.02",
|
||||||
"0.02"
|
"0.02"
|
||||||
],
|
],
|
||||||
"reduce_fan_stop_start_freq": [
|
"reduce_fan_stop_start_freq": [
|
||||||
|
|||||||
+129
-30
@@ -214,9 +214,9 @@ function ShowModelInfo( pModel )
|
|||||||
|
|
||||||
SendWXDebugInfo("Model Name: "+sModelName);
|
SendWXDebugInfo("Model Name: "+sModelName);
|
||||||
|
|
||||||
$('#ModelName').html(sModelName);
|
$('#ModelName').text(sModelName);
|
||||||
$('#ModelName').attr('title',sModelName);
|
$('#ModelName').attr('title',sModelName);
|
||||||
$('#ModelAuthorName').html(sModelAuthor);
|
$('#ModelAuthorName').text(sModelAuthor);
|
||||||
|
|
||||||
switch(UploadType)
|
switch(UploadType)
|
||||||
{
|
{
|
||||||
@@ -268,7 +268,7 @@ function ShowModelInfo( pModel )
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#Model_Desc').html( html_decode(sModelDesc) );
|
$('#Model_Desc').empty().append( SanitizeDescHtml( html_decode(sModelDesc) ) );
|
||||||
|
|
||||||
let ModelPreviewList=pModel.preview_img;
|
let ModelPreviewList=pModel.preview_img;
|
||||||
let TotalPreview=ModelPreviewList.length;
|
let TotalPreview=ModelPreviewList.length;
|
||||||
@@ -281,16 +281,15 @@ function ShowModelInfo( pModel )
|
|||||||
|
|
||||||
if(TotalPreview>0)
|
if(TotalPreview>0)
|
||||||
{
|
{
|
||||||
let htmlPreview='';
|
$('#ModelPreviewList').empty();
|
||||||
for(let pn=0;pn<TotalPreview;pn++)
|
for(let pn=0;pn<TotalPreview;pn++)
|
||||||
{
|
{
|
||||||
//let FTmpPath=decodeURIComponent(ModelPreviewList[pn]);
|
//let FTmpPath=decodeURIComponent(ModelPreviewList[pn]);
|
||||||
let FTmpPath=ModelPreviewList[pn]['filepath'];
|
let FTmpPath=ModelPreviewList[pn]['filepath'];
|
||||||
|
|
||||||
htmlPreview+='<div class="swiper-slide"><img class="Model_PrevImg" src="'+FTmpPath+'" /></div>';
|
$('#ModelPreviewList').append( $('<div class="swiper-slide"></div>').append( $('<img class="Model_PrevImg" />').attr('src',FTmpPath) ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#ModelPreviewList').html(htmlPreview);
|
|
||||||
$('#Model_Preview_Image').viewer({
|
$('#Model_Preview_Image').viewer({
|
||||||
title: false,
|
title: false,
|
||||||
fullsreen: false,
|
fullsreen: false,
|
||||||
@@ -410,7 +409,8 @@ function ConstructFileHtml( ID, pItem )
|
|||||||
{
|
{
|
||||||
let fTotal=pItem.length;
|
let fTotal=pItem.length;
|
||||||
|
|
||||||
let strHtml='';
|
let pBoard=$('#'+ID+' .FileListBoard');
|
||||||
|
pBoard.empty();
|
||||||
for( let f=0;f<fTotal;f++ )
|
for( let f=0;f<fTotal;f++ )
|
||||||
{
|
{
|
||||||
let pOne=pItem[f];
|
let pOne=pItem[f];
|
||||||
@@ -443,39 +443,139 @@ function ConstructFileHtml( ID, pItem )
|
|||||||
ImgPath='img/default.png';
|
ImgPath='img/default.png';
|
||||||
}
|
}
|
||||||
|
|
||||||
//Add html
|
//Add html. File names come from the 3MF, so build the nodes rather than concatenating markup.
|
||||||
|
let pIconImg=$('<img />').attr('src',ImgPath);
|
||||||
|
let pMenu=$('<div class="FileMenu"><img src="img/s.svg" /></div>');
|
||||||
if( strClass!='ImageIcon' )
|
if( strClass!='ImageIcon' )
|
||||||
{
|
{
|
||||||
strHtml+='<div class="FileItem">'+
|
pMenu.on('click', function(){ OnClickOpenFile(tPath); });
|
||||||
' <div class="'+strClass+'"><img src="'+ImgPath+'" /></div>'+
|
|
||||||
' <div class="FileText">'+
|
|
||||||
' <div class="FileName">'+tName+'</div>'+
|
|
||||||
' </div>'+
|
|
||||||
' <div class="FileMenu" onClick="OnClickOpenFile(\''+tPath+'\')"><img src="img/s.svg" /></div>'+
|
|
||||||
'</div>';
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ImgID++;
|
ImgID++;
|
||||||
let TmpImgID="AF"+ImgID;
|
let TmpImgID="AF"+ImgID;
|
||||||
|
|
||||||
strHtml+='<div class="FileItem">'+
|
pIconImg.attr('id',TmpImgID);
|
||||||
' <div class="'+strClass+'"><img id="'+TmpImgID+'" src="'+ImgPath+'" /></div>'+
|
pMenu.on('click', function(){ OnClickOpenImage(TmpImgID); });
|
||||||
' <div class="FileText">'+
|
|
||||||
' <div class="FileName">'+tName+'</div>'+
|
|
||||||
' </div>'+
|
|
||||||
' <div class="FileMenu" onClick="OnClickOpenImage(\''+TmpImgID+'\')"><img src="img/s.svg" /></div>'+
|
|
||||||
'</div>';
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$('#'+ID+' .FileListBoard').html(strHtml);
|
let pFileItem=$('<div class="FileItem"></div>');
|
||||||
|
pFileItem.append( $('<div></div>').addClass(strClass).append(pIconImg) );
|
||||||
|
pFileItem.append( $('<div class="FileText"></div>').append( $('<div class="FileName"></div>').text(tName).attr('title',tName) ) );
|
||||||
|
pFileItem.append( pMenu );
|
||||||
|
pBoard.append( pFileItem );
|
||||||
|
}
|
||||||
|
|
||||||
if( fTotal>0 )
|
if( fTotal>0 )
|
||||||
$('#'+ID).show();
|
$('#'+ID).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Descriptions are untrusted 3MF metadata that may carry rich-text HTML (e.g. from MakerWorld).
|
||||||
|
// Rebuild them from an inert parse, keeping only plain formatting tags and http(s) links and images.
|
||||||
|
var DescAllowedTags=['P','BR','B','STRONG','I','EM','U','S','STRIKE','DEL','INS','SUB','SUP','SMALL','MARK',
|
||||||
|
'Q','ABBR','KBD','WBR','H1','H2','H3','H4','H5','H6','UL','OL','LI','DL','DT','DD','BLOCKQUOTE','PRE','CODE',
|
||||||
|
'HR','SPAN','DIV','FIGURE','FIGCAPTION','TABLE','CAPTION','THEAD','TBODY','TFOOT','TR','TH','TD','A','IMG'];
|
||||||
|
// Plain attributes kept per tag; the numeric ones must be plain non-negative integers.
|
||||||
|
var DescAllowedAttrs={'IMG':['alt','title','width','height'],'TD':['colspan','rowspan'],'TH':['colspan','rowspan'],'OL':['start']};
|
||||||
|
var DescNumericAttrs=['width','height','colspan','rowspan','start'];
|
||||||
|
// Dropped together with their content; any other unknown tag is unwrapped to its children.
|
||||||
|
var DescDroppedTags=['SCRIPT','STYLE','TEMPLATE','NOSCRIPT','TEXTAREA','TITLE','IFRAME','FRAME','OBJECT','EMBED','SVG','MATH'];
|
||||||
|
|
||||||
|
function IsHttpUrl( strUrl )
|
||||||
|
{
|
||||||
|
// The scheme must be written as is, so nothing the URL parser would strip can precede or split it.
|
||||||
|
if( typeof strUrl!='string' || !/^https?:/i.test(strUrl) )
|
||||||
|
return false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
let sProtocol=new URL(strUrl).protocol;
|
||||||
|
return sProtocol=='http:' || sProtocol=='https:';
|
||||||
|
}
|
||||||
|
catch(e)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Images load as soon as the page opens, so only https sources are kept: no plain-http requests to the local network.
|
||||||
|
function IsHttpsUrl( strUrl )
|
||||||
|
{
|
||||||
|
return IsHttpUrl(strUrl) && new URL(strUrl).protocol=='https:';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Embedded YouTube players become a plain link to the video.
|
||||||
|
function GetYouTubeEmbedUrl( pNode )
|
||||||
|
{
|
||||||
|
let sSrc=pNode.getAttribute('src');
|
||||||
|
if( !IsHttpUrl(sSrc) )
|
||||||
|
return null;
|
||||||
|
let pUrl=new URL(sSrc);
|
||||||
|
return ( pUrl.origin=='https://www.youtube.com' && pUrl.pathname.indexOf('/embed/')==0 ) ? pUrl.href : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function CopyDescNodes( pSrc, pDst )
|
||||||
|
{
|
||||||
|
for( let pNode=pSrc.firstChild;pNode!=null;pNode=pNode.nextSibling )
|
||||||
|
{
|
||||||
|
if( pNode.nodeType==Node.TEXT_NODE )
|
||||||
|
{
|
||||||
|
pDst.appendChild( document.createTextNode(pNode.nodeValue) );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if( pNode.nodeType!=Node.ELEMENT_NODE )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
let sTag=pNode.nodeName.toUpperCase();
|
||||||
|
if( sTag=='IFRAME' )
|
||||||
|
{
|
||||||
|
let sVideoUrl=GetYouTubeEmbedUrl(pNode);
|
||||||
|
if( sVideoUrl!=null )
|
||||||
|
{
|
||||||
|
let pLink=document.createElement('A');
|
||||||
|
pLink.setAttribute('href',sVideoUrl);
|
||||||
|
pLink.textContent=sVideoUrl;
|
||||||
|
pDst.appendChild(pLink);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if( $.inArray(sTag,DescDroppedTags)>=0 )
|
||||||
|
continue;
|
||||||
|
if( $.inArray(sTag,DescAllowedTags)<0 )
|
||||||
|
{
|
||||||
|
CopyDescNodes(pNode,pDst);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pElem=document.createElement(sTag);
|
||||||
|
if( sTag=='A' && IsHttpUrl(pNode.getAttribute('href')) )
|
||||||
|
pElem.setAttribute('href',pNode.getAttribute('href'));
|
||||||
|
else if( sTag=='IMG' )
|
||||||
|
{
|
||||||
|
if( !IsHttpsUrl(pNode.getAttribute('src')) )
|
||||||
|
continue;
|
||||||
|
pElem.setAttribute('src',pNode.getAttribute('src'));
|
||||||
|
}
|
||||||
|
$.each( DescAllowedAttrs[sTag]||[], function(i,sAttr){
|
||||||
|
let sValue=pNode.getAttribute(sAttr);
|
||||||
|
if( sValue!=null && ( $.inArray(sAttr,DescNumericAttrs)<0 || /^\d+$/.test(sValue) ) )
|
||||||
|
pElem.setAttribute(sAttr,sValue);
|
||||||
|
});
|
||||||
|
CopyDescNodes(pNode,pElem);
|
||||||
|
pDst.appendChild(pElem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function SanitizeDescHtml( strHtml )
|
||||||
|
{
|
||||||
|
let pFragment=document.createDocumentFragment();
|
||||||
|
// A DOMParser document is inert: it runs no scripts and loads no resources.
|
||||||
|
let pDoc=new DOMParser().parseFromString(strHtml,'text/html');
|
||||||
|
if( pDoc && pDoc.body )
|
||||||
|
CopyDescNodes(pDoc.body,pFragment);
|
||||||
|
return pFragment;
|
||||||
|
}
|
||||||
|
|
||||||
function ShowProfilelInfo( pProfile )
|
function ShowProfilelInfo( pProfile )
|
||||||
{
|
{
|
||||||
//==========Profile Info==========
|
//==========Profile Info==========
|
||||||
@@ -483,10 +583,10 @@ function ShowProfilelInfo( pProfile )
|
|||||||
let sProfileAuthor=decodeURIComponent(pProfile.author);
|
let sProfileAuthor=decodeURIComponent(pProfile.author);
|
||||||
let sProfileDesc=decodeURIComponent(pProfile.description);
|
let sProfileDesc=decodeURIComponent(pProfile.description);
|
||||||
|
|
||||||
$('#ProfileName').html(sProfileName);
|
$('#ProfileName').text(sProfileName);
|
||||||
$('#ProfileAuthor').html(sProfileAuthor);
|
$('#ProfileAuthor').text(sProfileAuthor);
|
||||||
|
|
||||||
$('#Profile_Desc').html( html_decode(sProfileDesc) );
|
$('#Profile_Desc').empty().append( SanitizeDescHtml( html_decode(sProfileDesc) ) );
|
||||||
|
|
||||||
let ProfilePreviewList=pProfile.preview_img;
|
let ProfilePreviewList=pProfile.preview_img;
|
||||||
let TotalPreview=ProfilePreviewList.length;
|
let TotalPreview=ProfilePreviewList.length;
|
||||||
@@ -499,15 +599,14 @@ function ShowProfilelInfo( pProfile )
|
|||||||
|
|
||||||
if(TotalPreview>0)
|
if(TotalPreview>0)
|
||||||
{
|
{
|
||||||
let htmlPreview='';
|
$('#ProfilePreviewList').empty();
|
||||||
for(let pn=0;pn<TotalPreview;pn++)
|
for(let pn=0;pn<TotalPreview;pn++)
|
||||||
{
|
{
|
||||||
let FTmpPath=ProfilePreviewList[pn]['filepath'];
|
let FTmpPath=ProfilePreviewList[pn]['filepath'];
|
||||||
|
|
||||||
htmlPreview+='<div class="swiper-slide"><img class="Model_PrevImg" src="'+FTmpPath+'" /></div>';
|
$('#ProfilePreviewList').append( $('<div class="swiper-slide"></div>').append( $('<img class="Model_PrevImg" />').attr('src',FTmpPath) ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#ProfilePreviewList').html(htmlPreview);
|
|
||||||
$('#Profile_Preview_Image').viewer({
|
$('#Profile_Preview_Image').viewer({
|
||||||
title: false,
|
title: false,
|
||||||
fullsreen: false,
|
fullsreen: false,
|
||||||
|
|||||||
@@ -3476,6 +3476,14 @@ int CLI::run(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
new_variant_counts = old_variant_counts;
|
new_variant_counts = old_variant_counts;
|
||||||
//filament_variant_count = old_variant_counts;
|
//filament_variant_count = old_variant_counts;
|
||||||
|
//ORCA: lay the per-variant options out one value per variant of the current filaments before each
|
||||||
|
// loaded filament replaces its own variants, including an option only a loaded filament
|
||||||
|
// defines, which otherwise starts as a single default value and never reaches the others.
|
||||||
|
for (const DynamicPrintConfig &config : load_filaments_config)
|
||||||
|
for (const std::string &opt_key : filament_options_with_variant)
|
||||||
|
if (opt_key != "filament_extruder_variant" && config.has(opt_key))
|
||||||
|
m_print_config.option(opt_key, true);
|
||||||
|
normalize_filament_values_to_variants(m_print_config);
|
||||||
for (int index = 0; index < load_filaments_config.size(); index++) {
|
for (int index = 0; index < load_filaments_config.size(); index++) {
|
||||||
DynamicPrintConfig& config = load_filaments_config[index];
|
DynamicPrintConfig& config = load_filaments_config[index];
|
||||||
int filament_index = load_filaments_index[index];
|
int filament_index = load_filaments_index[index];
|
||||||
@@ -3647,6 +3655,25 @@ int CLI::run(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//ORCA: a per-variant option the loaded filament does not define keeps the values of the
|
||||||
|
// variants the filament already had, and a variant new to it takes its first one's.
|
||||||
|
const int old_start = old_start_indice[filament_index - 1];
|
||||||
|
std::vector<int> kept_variant_indice = new_variant_indice;
|
||||||
|
for (int &i : kept_variant_indice)
|
||||||
|
if (i < 0)
|
||||||
|
i = old_start;
|
||||||
|
for (const std::string &opt_key : filament_options_with_variant) {
|
||||||
|
if (config.has(opt_key))
|
||||||
|
continue;
|
||||||
|
auto *opt_vec_dst = dynamic_cast<ConfigOptionVectorBase *>(m_print_config.option(opt_key));
|
||||||
|
if (opt_vec_dst == nullptr || opt_vec_dst->size() < size_t(old_start + old_variant_count))
|
||||||
|
continue;
|
||||||
|
// set_with_restore_2() pads its source in place
|
||||||
|
std::unique_ptr<ConfigOption> old_values(opt_vec_dst->clone());
|
||||||
|
opt_vec_dst->set_with_restore_2(static_cast<ConfigOptionVectorBase *>(old_values.get()), kept_variant_indice, old_start,
|
||||||
|
old_variant_count, true);
|
||||||
|
}
|
||||||
|
|
||||||
//update the old index
|
//update the old index
|
||||||
if (old_variant_count != new_variant_count)
|
if (old_variant_count != new_variant_count)
|
||||||
{
|
{
|
||||||
@@ -4081,6 +4108,9 @@ int CLI::run(int argc, char **argv)
|
|||||||
if (printer_technology == ptFFF) {
|
if (printer_technology == ptFFF) {
|
||||||
fff_print_config.apply(m_print_config, true);
|
fff_print_config.apply(m_print_config, true);
|
||||||
m_print_config.apply(fff_print_config, true);
|
m_print_config.apply(fff_print_config, true);
|
||||||
|
//ORCA: an option no preset or project defines has just come in as its single default value, and a
|
||||||
|
// command line override may hold one value per filament.
|
||||||
|
normalize_filament_values_to_variants(m_print_config);
|
||||||
} else {
|
} else {
|
||||||
boost::nowide::cerr << "invalid printer_technology " << std::endl;
|
boost::nowide::cerr << "invalid printer_technology " << std::endl;
|
||||||
record_exit_reson(outfile_dir, CLI_INVALID_PRINTER_TECH, 0, cli_errors[CLI_INVALID_PRINTER_TECH], sliced_info);
|
record_exit_reson(outfile_dir, CLI_INVALID_PRINTER_TECH, 0, cli_errors[CLI_INVALID_PRINTER_TECH], sliced_info);
|
||||||
|
|||||||
+49
-69
@@ -1497,12 +1497,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
|||||||
check_add_eol(toolchange_gcode_str);
|
check_add_eol(toolchange_gcode_str);
|
||||||
|
|
||||||
// SoftFever: set new PA for new filament
|
// SoftFever: set new PA for new filament
|
||||||
if (gcodegen.config().enable_pressure_advance.get_at(new_filament_id)) {
|
gcode += gcodegen.set_filament_pressure_advance(new_filament_id);
|
||||||
gcode += gcodegen.writer().set_pressure_advance(gcodegen.config().pressure_advance.get_at(new_filament_id));
|
|
||||||
// Orca: Adaptive PA
|
|
||||||
// Reset Adaptive PA processor last PA value
|
|
||||||
gcodegen.m_pa_processor->resetPreviousPA(gcodegen.config().pressure_advance.get_at(new_filament_id));
|
|
||||||
}
|
|
||||||
|
|
||||||
// A phony move to the end position at the wipe tower.
|
// A phony move to the end position at the wipe tower.
|
||||||
gcodegen.writer().travel_to_xy((end_pos + plate_origin_2d).cast<double>());
|
gcodegen.writer().travel_to_xy((end_pos + plate_origin_2d).cast<double>());
|
||||||
@@ -1826,12 +1821,8 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
|||||||
check_add_eol(toolchange_gcode_str);
|
check_add_eol(toolchange_gcode_str);
|
||||||
|
|
||||||
// SoftFever: set new PA for new filament
|
// SoftFever: set new PA for new filament
|
||||||
if (new_extruder_id != -1 && gcodegen.config().enable_pressure_advance.get_at(new_extruder_id)) {
|
if (new_extruder_id != -1)
|
||||||
gcode += gcodegen.writer().set_pressure_advance(gcodegen.config().pressure_advance.get_at(new_extruder_id));
|
gcode += gcodegen.set_filament_pressure_advance(new_extruder_id);
|
||||||
// Orca: Adaptive PA
|
|
||||||
// Reset Adaptive PA processor last PA value
|
|
||||||
gcodegen.m_pa_processor->resetPreviousPA(gcodegen.config().pressure_advance.get_at(new_extruder_id));
|
|
||||||
}
|
|
||||||
|
|
||||||
// A phony move to the end position at the wipe tower.
|
// A phony move to the end position at the wipe tower.
|
||||||
gcodegen.writer().travel_to_xy((end_pos + plate_origin_2d).cast<double>());
|
gcodegen.writer().travel_to_xy((end_pos + plate_origin_2d).cast<double>());
|
||||||
@@ -3232,7 +3223,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
|||||||
m_cooling_buffer->set_current_extruder(initial_extruder_id, extruder_id);
|
m_cooling_buffer->set_current_extruder(initial_extruder_id, extruder_id);
|
||||||
|
|
||||||
// Orca: Initialise AdaptivePA processor filter
|
// Orca: Initialise AdaptivePA processor filter
|
||||||
m_pa_processor = std::make_unique<AdaptivePAProcessor>(*this, tool_ordering.all_extruders());
|
m_pa_processor = std::make_unique<AdaptivePAProcessor>(*this);
|
||||||
|
|
||||||
// Update output variables after the extruders were initialized.
|
// Update output variables after the extruders were initialized.
|
||||||
m_placeholder_parser_integration.init(m_writer);
|
m_placeholder_parser_integration.init(m_writer);
|
||||||
@@ -3797,12 +3788,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Orca: add missing PA settings for initial filament
|
// Orca: add missing PA settings for initial filament
|
||||||
if (m_config.enable_pressure_advance.get_at(initial_non_support_extruder_id)) {
|
file.write(set_filament_pressure_advance(initial_non_support_extruder_id));
|
||||||
file.write(m_writer.set_pressure_advance(m_config.pressure_advance.get_at(initial_non_support_extruder_id)));
|
|
||||||
// Orca: Adaptive PA
|
|
||||||
// Reset Adaptive PA processor last PA value
|
|
||||||
m_pa_processor->resetPreviousPA(m_config.pressure_advance.get_at(initial_non_support_extruder_id));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//flush FanMover buffer to avoid modifying the start gcode if it's manual.
|
//flush FanMover buffer to avoid modifying the start gcode if it's manual.
|
||||||
@@ -8390,12 +8376,16 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
|||||||
|
|
||||||
// Orca: Dynamic PA
|
// Orca: Dynamic PA
|
||||||
// If adaptive PA is enabled, by default evaluate PA on all extrusion moves
|
// If adaptive PA is enabled, by default evaluate PA on all extrusion moves
|
||||||
|
// The PA settings and the PA_CHANGE tags use the filament config index of the filament's extruder variant
|
||||||
|
const unsigned int pa_config_index = (unsigned int) get_filament_config_index(m_writer.filament()->id());
|
||||||
|
const bool adaptive_pa = m_config.adaptive_pressure_advance.get_at(pa_config_index) && m_config.enable_pressure_advance.get_at(pa_config_index);
|
||||||
|
const bool adaptive_pa_overhangs = adaptive_pa && m_config.adaptive_pressure_advance_overhangs.get_at(pa_config_index);
|
||||||
bool is_pa_calib = m_curr_print->calib_mode() == CalibMode::Calib_PA_Line ||
|
bool is_pa_calib = m_curr_print->calib_mode() == CalibMode::Calib_PA_Line ||
|
||||||
m_curr_print->calib_mode() == CalibMode::Calib_PA_Pattern ||
|
m_curr_print->calib_mode() == CalibMode::Calib_PA_Pattern ||
|
||||||
m_curr_print->calib_mode() == CalibMode::Calib_PA_Tower;
|
m_curr_print->calib_mode() == CalibMode::Calib_PA_Tower;
|
||||||
bool evaluate_adaptive_pa = false;
|
bool evaluate_adaptive_pa = false;
|
||||||
bool role_change = (m_last_extrusion_role != path.role());
|
bool role_change = (m_last_extrusion_role != path.role());
|
||||||
if (!is_pa_calib && FILAMENT_CONFIG(adaptive_pressure_advance) && FILAMENT_CONFIG(enable_pressure_advance)) {
|
if (!is_pa_calib && adaptive_pa) {
|
||||||
evaluate_adaptive_pa = true;
|
evaluate_adaptive_pa = true;
|
||||||
// If we have already emmited a PA change because the m_multi_flow_segment_path_pa_set is set
|
// If we have already emmited a PA change because the m_multi_flow_segment_path_pa_set is set
|
||||||
// skip re-issuing the PA change tag.
|
// skip re-issuing the PA change tag.
|
||||||
@@ -8478,7 +8468,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
|||||||
// Post processor flag generation code segment when option to emit only at role changes is enabled
|
// Post processor flag generation code segment when option to emit only at role changes is enabled
|
||||||
// Variables published to the post processor:
|
// Variables published to the post processor:
|
||||||
// 1) Tag to trigger a PA evaluation (because a role change was identified and the user has requested dynamic PA adjustments)
|
// 1) Tag to trigger a PA evaluation (because a role change was identified and the user has requested dynamic PA adjustments)
|
||||||
// 2) Current extruder ID (to identify the PA model for the currently used extruder)
|
// 2) Current filament config index (to identify the PA model for the currently used filament and its extruder variant)
|
||||||
// 3) mm3_per_mm value (to then multiply by the final model print speed after slowdown for cooling is applied)
|
// 3) mm3_per_mm value (to then multiply by the final model print speed after slowdown for cooling is applied)
|
||||||
// 4) the current acceleration (to pass to the model for evaluation)
|
// 4) the current acceleration (to pass to the model for evaluation)
|
||||||
// 5) whether this is an external perimeter (for future use)
|
// 5) whether this is an external perimeter (for future use)
|
||||||
@@ -8489,7 +8479,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
|||||||
if (m_multi_flow_segment_path_average_mm3_per_mm > 0) {
|
if (m_multi_flow_segment_path_average_mm3_per_mm > 0) {
|
||||||
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
|
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
|
||||||
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
|
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
|
||||||
m_writer.filament()->id(),
|
pa_config_index,
|
||||||
m_multi_flow_segment_path_average_mm3_per_mm,
|
m_multi_flow_segment_path_average_mm3_per_mm,
|
||||||
acceleration_i,
|
acceleration_i,
|
||||||
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
|
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
|
||||||
@@ -8502,7 +8492,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
|||||||
// to issue a zero flow PA change command for this
|
// to issue a zero flow PA change command for this
|
||||||
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
|
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
|
||||||
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
|
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
|
||||||
m_writer.filament()->id(),
|
pa_config_index,
|
||||||
_mm3_per_mm,
|
_mm3_per_mm,
|
||||||
acceleration_i,
|
acceleration_i,
|
||||||
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
|
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
|
||||||
@@ -8610,11 +8600,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
|||||||
// or a flow change, so emit the flag to evaluate PA for the upcomming extrusion
|
// or a flow change, so emit the flag to evaluate PA for the upcomming extrusion
|
||||||
// Emit tag before new speed is set so the post processor reads the next speed immediately and uses it.
|
// Emit tag before new speed is set so the post processor reads the next speed immediately and uses it.
|
||||||
// Dont emit tag if it has just already been emitted from a role change above
|
// Dont emit tag if it has just already been emitted from a role change above
|
||||||
if(_mm3_per_mm >0 &&
|
if(_mm3_per_mm >0 && adaptive_pa_overhangs && !evaluate_adaptive_pa){
|
||||||
FILAMENT_CONFIG(adaptive_pressure_advance) &&
|
|
||||||
FILAMENT_CONFIG(enable_pressure_advance) &&
|
|
||||||
FILAMENT_CONFIG(adaptive_pressure_advance_overhangs) &&
|
|
||||||
!evaluate_adaptive_pa){
|
|
||||||
if(writer().get_current_speed() > F){ // Ramping down speed - use overhang logic where the minimum speed is used between current and upcoming extrusion
|
if(writer().get_current_speed() > F){ // Ramping down speed - use overhang logic where the minimum speed is used between current and upcoming extrusion
|
||||||
if(m_config.gcode_comments){
|
if(m_config.gcode_comments){
|
||||||
sprintf(buf, "; Ramp down-non-variable\n");
|
sprintf(buf, "; Ramp down-non-variable\n");
|
||||||
@@ -8622,7 +8608,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
|||||||
}
|
}
|
||||||
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
|
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
|
||||||
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
|
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
|
||||||
m_writer.filament()->id(),
|
pa_config_index,
|
||||||
_mm3_per_mm,
|
_mm3_per_mm,
|
||||||
acceleration_i,
|
acceleration_i,
|
||||||
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
|
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
|
||||||
@@ -8637,7 +8623,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
|||||||
}
|
}
|
||||||
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
|
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
|
||||||
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
|
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
|
||||||
m_writer.filament()->id(),
|
pa_config_index,
|
||||||
_mm3_per_mm,
|
_mm3_per_mm,
|
||||||
acceleration_i,
|
acceleration_i,
|
||||||
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
|
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
|
||||||
@@ -8843,10 +8829,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
|||||||
// ORCA: Adaptive PA code segment when adjusting PA within the same feature
|
// ORCA: Adaptive PA code segment when adjusting PA within the same feature
|
||||||
// There is a speed change or flow change so emit the flag to evaluate PA for the upcomming extrusion
|
// There is a speed change or flow change so emit the flag to evaluate PA for the upcomming extrusion
|
||||||
// Emit tag before new speed is set so the post processor reads the next speed immediately and uses it.
|
// Emit tag before new speed is set so the post processor reads the next speed immediately and uses it.
|
||||||
if(_mm3_per_mm >0 &&
|
if(_mm3_per_mm >0 && adaptive_pa_overhangs){
|
||||||
EXTRUDER_CONFIG(adaptive_pressure_advance) &&
|
|
||||||
EXTRUDER_CONFIG(enable_pressure_advance) &&
|
|
||||||
EXTRUDER_CONFIG(adaptive_pressure_advance_overhangs) ){
|
|
||||||
if(last_set_speed > new_speed){ // Ramping down speed - use overhang logic where the minimum speed is used between current and upcoming extrusion
|
if(last_set_speed > new_speed){ // Ramping down speed - use overhang logic where the minimum speed is used between current and upcoming extrusion
|
||||||
if(m_config.gcode_comments) {
|
if(m_config.gcode_comments) {
|
||||||
sprintf(buf, "; Ramp up-variable\n");
|
sprintf(buf, "; Ramp up-variable\n");
|
||||||
@@ -8854,7 +8837,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
|||||||
}
|
}
|
||||||
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
|
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
|
||||||
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
|
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
|
||||||
m_writer.filament()->id(),
|
pa_config_index,
|
||||||
_mm3_per_mm,
|
_mm3_per_mm,
|
||||||
acceleration_i,
|
acceleration_i,
|
||||||
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
|
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
|
||||||
@@ -8869,7 +8852,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
|||||||
}
|
}
|
||||||
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
|
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
|
||||||
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
|
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
|
||||||
m_writer.filament()->id(),
|
pa_config_index,
|
||||||
_mm3_per_mm,
|
_mm3_per_mm,
|
||||||
acceleration_i,
|
acceleration_i,
|
||||||
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
|
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
|
||||||
@@ -9422,33 +9405,29 @@ void GCode::update_placeholder_parser_with_variant_params()
|
|||||||
if (num_filaments == 0)
|
if (num_filaments == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Helpers: remap config arrays from variant index space to filament_id index space.
|
// Helper: remap config arrays from variant index space to filament_id index space.
|
||||||
// After remapping, gcode templates can use param[filament_id] directly.
|
// After remapping, gcode templates can use param[filament_id] directly.
|
||||||
auto remap_floats_by_filament = [&](const auto &src) {
|
auto remap_by_filament = [&](const auto &src) {
|
||||||
std::vector<double> dst(num_filaments);
|
std::decay_t<decltype(src.values)> dst(num_filaments);
|
||||||
for (size_t i = 0; i < num_filaments; ++i)
|
|
||||||
dst[i] = src.get_at(get_filament_config_index(i));
|
|
||||||
return dst;
|
|
||||||
};
|
|
||||||
auto remap_ints_by_filament = [&](const auto &src) {
|
|
||||||
std::vector<int> dst(num_filaments);
|
|
||||||
for (size_t i = 0; i < num_filaments; ++i)
|
for (size_t i = 0; i < num_filaments; ++i)
|
||||||
dst[i] = src.get_at(get_filament_config_index(i));
|
dst[i] = src.get_at(get_filament_config_index(i));
|
||||||
return dst;
|
return dst;
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- filament_options_with_variant: gcode indexes by filament_id ---
|
// --- filament_options_with_variant: gcode indexes by filament_id ---
|
||||||
this->placeholder_parser().set("filament_max_volumetric_speed", new ConfigOptionFloats(remap_floats_by_filament(m_config.filament_max_volumetric_speed)));
|
this->placeholder_parser().set("filament_max_volumetric_speed", new ConfigOptionFloats(remap_by_filament(m_config.filament_max_volumetric_speed)));
|
||||||
this->placeholder_parser().set("filament_pre_cooling_temperature", new ConfigOptionInts(remap_ints_by_filament(m_config.filament_pre_cooling_temperature)));
|
this->placeholder_parser().set("filament_pre_cooling_temperature", new ConfigOptionInts(remap_by_filament(m_config.filament_pre_cooling_temperature)));
|
||||||
this->placeholder_parser().set("filament_pre_cooling_temperature_nc", new ConfigOptionInts(remap_ints_by_filament(m_config.filament_pre_cooling_temperature_nc)));
|
this->placeholder_parser().set("filament_pre_cooling_temperature_nc", new ConfigOptionInts(remap_by_filament(m_config.filament_pre_cooling_temperature_nc)));
|
||||||
this->placeholder_parser().set("filament_cooling_before_tower", new ConfigOptionFloats(remap_floats_by_filament(m_config.filament_cooling_before_tower)));
|
this->placeholder_parser().set("filament_cooling_before_tower", new ConfigOptionFloats(remap_by_filament(m_config.filament_cooling_before_tower)));
|
||||||
this->placeholder_parser().set("nozzle_temperature_initial_layer", new ConfigOptionInts(remap_ints_by_filament(m_config.nozzle_temperature_initial_layer)));
|
this->placeholder_parser().set("nozzle_temperature_initial_layer", new ConfigOptionInts(remap_by_filament(m_config.nozzle_temperature_initial_layer)));
|
||||||
this->placeholder_parser().set("nozzle_temperature", new ConfigOptionInts(remap_ints_by_filament(m_config.nozzle_temperature)));
|
this->placeholder_parser().set("nozzle_temperature", new ConfigOptionInts(remap_by_filament(m_config.nozzle_temperature)));
|
||||||
// first_layer_temperature is a legacy alias of nozzle_temperature_initial_layer
|
// first_layer_temperature is a legacy alias of nozzle_temperature_initial_layer
|
||||||
this->placeholder_parser().set("first_layer_temperature", new ConfigOptionInts(remap_ints_by_filament(m_config.nozzle_temperature_initial_layer)));
|
this->placeholder_parser().set("first_layer_temperature", new ConfigOptionInts(remap_by_filament(m_config.nozzle_temperature_initial_layer)));
|
||||||
|
this->placeholder_parser().set("pressure_advance", new ConfigOptionFloats(remap_by_filament(m_config.pressure_advance)));
|
||||||
|
this->placeholder_parser().set("enable_pressure_advance", new ConfigOptionBools(remap_by_filament(m_config.enable_pressure_advance)));
|
||||||
|
|
||||||
// --- printer_options_with_variant_1: in m_config these are already merged as filament-indexed ---
|
// --- printer_options_with_variant_1: in m_config these are already merged as filament-indexed ---
|
||||||
this->placeholder_parser().set("retraction_distances_when_cut", new ConfigOptionFloats(remap_floats_by_filament(m_config.retraction_distances_when_cut)));
|
this->placeholder_parser().set("retraction_distances_when_cut", new ConfigOptionFloats(remap_by_filament(m_config.retraction_distances_when_cut)));
|
||||||
// hotend_cooling_rate / hotend_heating_rate: gcode uses [filament_map[x]-1] (extruder_id), no remap needed
|
// hotend_cooling_rate / hotend_heating_rate: gcode uses [filament_map[x]-1] (extruder_id), no remap needed
|
||||||
|
|
||||||
// --- filament_map: per-layer dynamic, sync from m_config to placeholder_parser ---
|
// --- filament_map: per-layer dynamic, sync from m_config to placeholder_parser ---
|
||||||
@@ -9458,10 +9437,10 @@ void GCode::update_placeholder_parser_with_variant_params()
|
|||||||
{
|
{
|
||||||
// Fast purge mode uses filament_flush_temp_fast; Default is inert.
|
// Fast purge mode uses filament_flush_temp_fast; Default is inert.
|
||||||
bool use_fast_flush = m_config.prime_volume_mode == PrimeVolumeMode::pvmFast;
|
bool use_fast_flush = m_config.prime_volume_mode == PrimeVolumeMode::pvmFast;
|
||||||
auto flush_v_speed = remap_floats_by_filament(m_config.filament_flush_volumetric_speed);
|
auto flush_v_speed = remap_by_filament(m_config.filament_flush_volumetric_speed);
|
||||||
auto filament_max_v = remap_floats_by_filament(m_config.filament_max_volumetric_speed);
|
auto filament_max_v = remap_by_filament(m_config.filament_max_volumetric_speed);
|
||||||
auto flush_temps = remap_ints_by_filament(use_fast_flush ? m_config.filament_flush_temp_fast
|
auto flush_temps = remap_by_filament(use_fast_flush ? m_config.filament_flush_temp_fast
|
||||||
: m_config.filament_flush_temp);
|
: m_config.filament_flush_temp);
|
||||||
for (size_t i = 0; i < num_filaments; ++i) {
|
for (size_t i = 0; i < num_filaments; ++i) {
|
||||||
if (flush_v_speed[i] == 0)
|
if (flush_v_speed[i] == 0)
|
||||||
flush_v_speed[i] = filament_max_v[i];
|
flush_v_speed[i] = filament_max_v[i];
|
||||||
@@ -9513,12 +9492,7 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
|
|||||||
gcode += this->placeholder_parser_process("filament_start_gcode", filament_start_gcode, new_filament_id, &config);
|
gcode += this->placeholder_parser_process("filament_start_gcode", filament_start_gcode, new_filament_id, &config);
|
||||||
check_add_eol(gcode);
|
check_add_eol(gcode);
|
||||||
}
|
}
|
||||||
if (m_config.enable_pressure_advance.get_at(new_filament_id)) {
|
gcode += set_filament_pressure_advance(new_filament_id);
|
||||||
gcode += m_writer.set_pressure_advance(m_config.pressure_advance.get_at(new_filament_id));
|
|
||||||
// Orca: Adaptive PA
|
|
||||||
// Reset Adaptive PA processor last PA value
|
|
||||||
m_pa_processor->resetPreviousPA(m_config.pressure_advance.get_at(new_filament_id));
|
|
||||||
}
|
|
||||||
|
|
||||||
gcode += m_writer.toolchange(new_filament_id, new_extruder_id);
|
gcode += m_writer.toolchange(new_filament_id, new_extruder_id);
|
||||||
if (Extruder *fil = m_writer.filament())
|
if (Extruder *fil = m_writer.filament())
|
||||||
@@ -9909,18 +9883,24 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
|
|||||||
if (m_ooze_prevention.enable && !defer_temp_wait)
|
if (m_ooze_prevention.enable && !defer_temp_wait)
|
||||||
gcode += m_ooze_prevention.post_toolchange(*this);
|
gcode += m_ooze_prevention.post_toolchange(*this);
|
||||||
|
|
||||||
if (m_config.enable_pressure_advance.get_at(new_filament_id)) {
|
gcode += set_filament_pressure_advance(new_filament_id);
|
||||||
gcode += m_writer.set_pressure_advance(m_config.pressure_advance.get_at(new_filament_id));
|
|
||||||
// Orca: Adaptive PA
|
|
||||||
// Reset Adaptive PA processor last PA value
|
|
||||||
m_pa_processor->resetPreviousPA(m_config.pressure_advance.get_at(new_filament_id));
|
|
||||||
}
|
|
||||||
//Orca: tool changer or IDEX's firmware may change Z position, so we set it to unknown/undefined
|
//Orca: tool changer or IDEX's firmware may change Z position, so we set it to unknown/undefined
|
||||||
m_last_pos_defined = false;
|
m_last_pos_defined = false;
|
||||||
|
|
||||||
return gcode;
|
return gcode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string GCode::set_filament_pressure_advance(unsigned int filament_id)
|
||||||
|
{
|
||||||
|
const size_t fi = get_filament_config_index(filament_id);
|
||||||
|
if (!m_config.enable_pressure_advance.get_at(fi))
|
||||||
|
return {};
|
||||||
|
// Orca: Adaptive PA
|
||||||
|
// Reset Adaptive PA processor last PA value
|
||||||
|
m_pa_processor->resetPreviousPA(m_config.pressure_advance.get_at(fi));
|
||||||
|
return m_writer.set_pressure_advance(m_config.pressure_advance.get_at(fi));
|
||||||
|
}
|
||||||
|
|
||||||
inline std::string polygon_to_string(const Polygon &polygon, Print *print, bool is_print_space = false) {
|
inline std::string polygon_to_string(const Polygon &polygon, Print *print, bool is_print_space = false) {
|
||||||
std::ostringstream gcode;
|
std::ostringstream gcode;
|
||||||
gcode << "[";
|
gcode << "[";
|
||||||
|
|||||||
@@ -281,6 +281,8 @@ public:
|
|||||||
// extra_retract forwards a PETG pre-extrusion over-extrusion; default 0 -> identical to the plain deretract.
|
// extra_retract forwards a PETG pre-extrusion over-extrusion; default 0 -> identical to the plain deretract.
|
||||||
std::string unretract(float extra_retract = 0.f) { return m_writer.unlift() + m_writer.unretract(extra_retract); }
|
std::string unretract(float extra_retract = 0.f) { return m_writer.unlift() + m_writer.unretract(extra_retract); }
|
||||||
std::string set_extruder(unsigned int extruder_id, double print_z, bool by_object=false, int toolchange_temp_override = -1, bool defer_temp_wait = false);
|
std::string set_extruder(unsigned int extruder_id, double print_z, bool by_object=false, int toolchange_temp_override = -1, bool defer_temp_wait = false);
|
||||||
|
// Sets the pressure advance of the filament's extruder variant, if enabled for it.
|
||||||
|
std::string set_filament_pressure_advance(unsigned int filament_id);
|
||||||
bool is_BBL_Printer();
|
bool is_BBL_Printer();
|
||||||
WipeTowerType wipe_tower_type();
|
WipeTowerType wipe_tower_type();
|
||||||
|
|
||||||
|
|||||||
@@ -21,38 +21,30 @@ namespace Slic3r {
|
|||||||
*
|
*
|
||||||
* @param gcodegen A reference to the GCode object that generates the G-code.
|
* @param gcodegen A reference to the GCode object that generates the G-code.
|
||||||
*/
|
*/
|
||||||
AdaptivePAProcessor::AdaptivePAProcessor(GCode &gcodegen, const std::vector<unsigned int> &tools_used)
|
AdaptivePAProcessor::AdaptivePAProcessor(GCode &gcodegen)
|
||||||
: m_gcodegen(gcodegen),
|
: m_gcodegen(gcodegen),
|
||||||
m_config(gcodegen.config()),
|
m_config(gcodegen.config()),
|
||||||
m_last_predicted_pa(0.0),
|
m_last_predicted_pa(0.0),
|
||||||
m_max_next_feedrate(0.0),
|
m_max_next_feedrate(0.0),
|
||||||
m_next_feedrate(0.0),
|
m_next_feedrate(0.0),
|
||||||
m_current_feedrate(0.0),
|
m_current_feedrate(0.0),
|
||||||
m_last_extruder_id(-1),
|
m_last_config_index(-1),
|
||||||
m_pa_change_pattern(R"(; PA_CHANGE:T(\d+) MM3MM:([0-9]*\.[0-9]+) ACCEL:(\d+) BR:(\d+) RC:(\d+) OV:(\d+))"),
|
m_pa_change_pattern(R"(; PA_CHANGE:T(\d+) MM3MM:([0-9]*\.[0-9]+) ACCEL:(\d+) BR:(\d+) RC:(\d+) OV:(\d+))"),
|
||||||
m_g1_f_pattern(R"(G1 F([0-9]+))")
|
m_g1_f_pattern(R"(G1 F([0-9]+))")
|
||||||
{
|
{
|
||||||
// Constructor body can be used for further initialization if necessary
|
|
||||||
for (unsigned int tool : tools_used) {
|
|
||||||
// Only enable model for the tool if both PA and adaptive PA options are enabled
|
|
||||||
if(m_config.adaptive_pressure_advance.get_at(tool) && m_config.enable_pressure_advance.get_at(tool)){
|
|
||||||
auto interpolator = std::make_unique<AdaptivePAInterpolator>();
|
|
||||||
// Get calibration values from extruder
|
|
||||||
std::string pa_calibration_values = m_config.adaptive_pressure_advance_model.get_at(tool);
|
|
||||||
// Setup the model and store it in the tool-interpolation model map
|
|
||||||
interpolator->parseAndSetData(pa_calibration_values);
|
|
||||||
m_AdaptivePAInterpolators[tool] = std::move(interpolator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method to get the interpolator for a specific tool ID
|
// Method to get the interpolator for a specific filament config index.
|
||||||
AdaptivePAInterpolator* AdaptivePAProcessor::getInterpolator(unsigned int tool_id) {
|
// The model is built the first time an index is requested, as the indices in use depend on
|
||||||
auto it = m_AdaptivePAInterpolators.find(tool_id);
|
// the extruder variant each filament prints with.
|
||||||
if (it != m_AdaptivePAInterpolators.end()) {
|
AdaptivePAInterpolator* AdaptivePAProcessor::getInterpolator(unsigned int config_index) {
|
||||||
return it->second.get();
|
auto [it, inserted] = m_AdaptivePAInterpolators.try_emplace(config_index);
|
||||||
|
// Only enable model for the index if both PA and adaptive PA options are enabled
|
||||||
|
if (inserted && m_config.adaptive_pressure_advance.get_at(config_index) && m_config.enable_pressure_advance.get_at(config_index)) {
|
||||||
|
it->second = std::make_unique<AdaptivePAInterpolator>();
|
||||||
|
it->second->parseAndSetData(m_config.adaptive_pressure_advance_model.get_at(config_index));
|
||||||
}
|
}
|
||||||
return nullptr; // Handle the case where the tool_id was not found
|
return it->second.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -108,16 +100,17 @@ std::string AdaptivePAProcessor::process_layer(std::string &&gcode) {
|
|||||||
// the PA for that material is set. As no tag below will be found for this extruder, the original PA is retained.
|
// the PA for that material is set. As no tag below will be found for this extruder, the original PA is retained.
|
||||||
if (line.find("; PA_CHANGE") == 0) { // prune lines quickly before running regex check as regex is more expensive to run
|
if (line.find("; PA_CHANGE") == 0) { // prune lines quickly before running regex check as regex is more expensive to run
|
||||||
if (std::regex_search(line, m_match, m_pa_change_pattern)) {
|
if (std::regex_search(line, m_match, m_pa_change_pattern)) {
|
||||||
int extruder_id = std::stoi(m_match[1].str());
|
// The tag carries the filament config index, which selects the PA settings of the filament's extruder variant
|
||||||
|
int config_index = std::stoi(m_match[1].str());
|
||||||
mm3mm_value = std::stod(m_match[2].str());
|
mm3mm_value = std::stod(m_match[2].str());
|
||||||
accel_value = std::stod(m_match[3].str());
|
accel_value = std::stod(m_match[3].str());
|
||||||
int isBridge = std::stoi(m_match[4].str());
|
int isBridge = std::stoi(m_match[4].str());
|
||||||
int roleChange = std::stoi(m_match[5].str());
|
int roleChange = std::stoi(m_match[5].str());
|
||||||
int isOverhang = std::stoi(m_match[6].str());
|
int isOverhang = std::stoi(m_match[6].str());
|
||||||
|
|
||||||
// Check if the extruder ID has changed
|
// Check if the filament config index has changed
|
||||||
bool extruder_changed = (extruder_id != m_last_extruder_id);
|
bool config_index_changed = (config_index != m_last_config_index);
|
||||||
m_last_extruder_id = extruder_id;
|
m_last_config_index = config_index;
|
||||||
|
|
||||||
// Save the PA_CHANGE line to output later after finding feedrate
|
// Save the PA_CHANGE line to output later after finding feedrate
|
||||||
pa_change_line = line;
|
pa_change_line = line;
|
||||||
@@ -212,23 +205,23 @@ std::string AdaptivePAProcessor::process_layer(std::string &&gcode) {
|
|||||||
|
|
||||||
// Calculate the predicted PA using the upcomming feature maximum feedrate
|
// Calculate the predicted PA using the upcomming feature maximum feedrate
|
||||||
// Get the interpolator for the active tool
|
// Get the interpolator for the active tool
|
||||||
AdaptivePAInterpolator* interpolator = getInterpolator(m_last_extruder_id);
|
AdaptivePAInterpolator* interpolator = getInterpolator(m_last_config_index);
|
||||||
|
|
||||||
double predicted_pa = 0;
|
double predicted_pa = 0;
|
||||||
double adaptive_PA_speed = 0;
|
double adaptive_PA_speed = 0;
|
||||||
|
|
||||||
if(!interpolator){ // Tool not found in the interpolator map
|
if(!interpolator){ // Tool not found in the interpolator map
|
||||||
// Tool not found in the PA interpolator to tool map
|
// Tool not found in the PA interpolator to tool map
|
||||||
predicted_pa = m_config.enable_pressure_advance.get_at(m_last_extruder_id) ? m_config.pressure_advance.get_at(m_last_extruder_id) : 0;
|
predicted_pa = m_config.enable_pressure_advance.get_at(m_last_config_index) ? m_config.pressure_advance.get_at(m_last_config_index) : 0;
|
||||||
if(m_config.gcode_comments) output << "; APA: Tool doesnt have APA enabled\n";
|
if(m_config.gcode_comments) output << "; APA: Tool doesnt have APA enabled\n";
|
||||||
} else if (!interpolator->isInitialised() || (!m_config.adaptive_pressure_advance.get_at(m_last_extruder_id)) )
|
} else if (!interpolator->isInitialised() || (!m_config.adaptive_pressure_advance.get_at(m_last_config_index)) )
|
||||||
// Check if the model is not initialised by the constructor for the active extruder
|
// Check if the model is not initialised by the constructor for the active extruder
|
||||||
// Also check that adaptive PA is enabled for that extruder. This should not be needed
|
// Also check that adaptive PA is enabled for that extruder. This should not be needed
|
||||||
// as the PA change flag should not be set upstream (in the GCode.cpp file) if adaptive PA is disabled
|
// as the PA change flag should not be set upstream (in the GCode.cpp file) if adaptive PA is disabled
|
||||||
// however check for robustness sake.
|
// however check for robustness sake.
|
||||||
{
|
{
|
||||||
// Model failed or adaptive pressure advance not enabled - use default value from m_config
|
// Model failed or adaptive pressure advance not enabled - use default value from m_config
|
||||||
predicted_pa = m_config.enable_pressure_advance.get_at(m_last_extruder_id) ? m_config.pressure_advance.get_at(m_last_extruder_id) : 0;
|
predicted_pa = m_config.enable_pressure_advance.get_at(m_last_config_index) ? m_config.pressure_advance.get_at(m_last_config_index) : 0;
|
||||||
if(m_config.gcode_comments) output << "; APA: Interpolator setup failed, using default pressure advance\n";
|
if(m_config.gcode_comments) output << "; APA: Interpolator setup failed, using default pressure advance\n";
|
||||||
} else { // Model setup succeeded
|
} else { // Model setup succeeded
|
||||||
// Proceed to identify the print speed to use to calculate the adaptive PA value
|
// Proceed to identify the print speed to use to calculate the adaptive PA value
|
||||||
@@ -249,18 +242,18 @@ std::string AdaptivePAProcessor::process_layer(std::string &&gcode) {
|
|||||||
predicted_pa = (*interpolator)(mm3mm_value * adaptive_PA_speed, accel_value);
|
predicted_pa = (*interpolator)(mm3mm_value * adaptive_PA_speed, accel_value);
|
||||||
|
|
||||||
// This is a bridge, use the dedicated PA setting.
|
// This is a bridge, use the dedicated PA setting.
|
||||||
if(isBridge && m_config.adaptive_pressure_advance_bridges.get_at(m_last_extruder_id) > EPSILON)
|
if(isBridge && m_config.adaptive_pressure_advance_bridges.get_at(m_last_config_index) > EPSILON)
|
||||||
predicted_pa = m_config.adaptive_pressure_advance_bridges.get_at(m_last_extruder_id);
|
predicted_pa = m_config.adaptive_pressure_advance_bridges.get_at(m_last_config_index);
|
||||||
|
|
||||||
if (predicted_pa < 0) { // If extrapolation fails, fall back to the default PA for the extruder.
|
if (predicted_pa < 0) { // If extrapolation fails, fall back to the default PA for the extruder.
|
||||||
predicted_pa = m_config.enable_pressure_advance.get_at(m_last_extruder_id) ? m_config.pressure_advance.get_at(m_last_extruder_id) : 0;
|
predicted_pa = m_config.enable_pressure_advance.get_at(m_last_config_index) ? m_config.pressure_advance.get_at(m_last_config_index) : 0;
|
||||||
if(m_config.gcode_comments) output << "; APA: Interpolation failed, using fallback pressure advance value\n";
|
if(m_config.gcode_comments) output << "; APA: Interpolation failed, using fallback pressure advance value\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(m_config.gcode_comments) {
|
if(m_config.gcode_comments) {
|
||||||
// Output debug GCode comments
|
// Output debug GCode comments
|
||||||
output << pa_change_line << '\n'; // Output PA change command tag
|
output << pa_change_line << '\n'; // Output PA change command tag
|
||||||
if(isBridge && m_config.adaptive_pressure_advance_bridges.get_at(m_last_extruder_id) > EPSILON)
|
if(isBridge && m_config.adaptive_pressure_advance_bridges.get_at(m_last_config_index) > EPSILON)
|
||||||
output << "; APA Model Override (bridge)\n";
|
output << "; APA Model Override (bridge)\n";
|
||||||
output << "; APA Current Speed: " << std::to_string(m_current_feedrate) << "\n";
|
output << "; APA Current Speed: " << std::to_string(m_current_feedrate) << "\n";
|
||||||
output << "; APA Next Speed: " << std::to_string(m_next_feedrate) << "\n";
|
output << "; APA Next Speed: " << std::to_string(m_next_feedrate) << "\n";
|
||||||
@@ -269,7 +262,7 @@ std::string AdaptivePAProcessor::process_layer(std::string &&gcode) {
|
|||||||
output << "; APA Flow rate: " << std::to_string(mm3mm_value * m_max_next_feedrate) << "\n";
|
output << "; APA Flow rate: " << std::to_string(mm3mm_value * m_max_next_feedrate) << "\n";
|
||||||
output << "; APA Prev PA: " << std::to_string(m_last_predicted_pa) << " New PA: " << std::to_string(predicted_pa) << "\n";
|
output << "; APA Prev PA: " << std::to_string(m_last_predicted_pa) << " New PA: " << std::to_string(predicted_pa) << "\n";
|
||||||
}
|
}
|
||||||
if (extruder_changed || std::fabs(predicted_pa - m_last_predicted_pa) > EPSILON) {
|
if (config_index_changed || std::fabs(predicted_pa - m_last_predicted_pa) > EPSILON) {
|
||||||
output << m_gcodegen.writer().set_pressure_advance(predicted_pa); // Use m_writer to set pressure advance
|
output << m_gcodegen.writer().set_pressure_advance(predicted_pa); // Use m_writer to set pressure advance
|
||||||
m_last_predicted_pa = predicted_pa; // Update the last predicted PA value
|
m_last_predicted_pa = predicted_pa; // Update the last predicted PA value
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
#include <regex>
|
#include <regex>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <vector>
|
|
||||||
#include "AdaptivePAInterpolator.hpp"
|
#include "AdaptivePAInterpolator.hpp"
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
@@ -33,7 +32,7 @@ public:
|
|||||||
*
|
*
|
||||||
* @param gcodegen A reference to the GCode object that generates the G-code.
|
* @param gcodegen A reference to the GCode object that generates the G-code.
|
||||||
*/
|
*/
|
||||||
AdaptivePAProcessor(GCode &gcodegen, const std::vector<unsigned int> &tools_used);
|
AdaptivePAProcessor(GCode &gcodegen);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Processes a layer of G-code and applies adaptive pressure advance.
|
* @brief Processes a layer of G-code and applies adaptive pressure advance.
|
||||||
@@ -70,28 +69,25 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
GCode &m_gcodegen; ///< Reference to the GCode object.
|
GCode &m_gcodegen; ///< Reference to the GCode object.
|
||||||
std::unordered_map<unsigned int, std::unique_ptr<AdaptivePAInterpolator>> m_AdaptivePAInterpolators; ///< Map between Interpolator objects and tool ID's
|
std::unordered_map<unsigned int, std::unique_ptr<AdaptivePAInterpolator>> m_AdaptivePAInterpolators; ///< Map between Interpolator objects and filament config indices (null when adaptive PA is off)
|
||||||
const PrintConfig &m_config; ///< Reference to the print configuration.
|
const PrintConfig &m_config; ///< Reference to the print configuration.
|
||||||
double m_last_predicted_pa; ///< Last predicted pressure advance value.
|
double m_last_predicted_pa; ///< Last predicted pressure advance value.
|
||||||
double m_max_next_feedrate; ///< Maximum feed rate (speed) for the upcomming island. If no speed is found, the previous island speed is used.
|
double m_max_next_feedrate; ///< Maximum feed rate (speed) for the upcomming island. If no speed is found, the previous island speed is used.
|
||||||
double m_next_feedrate; ///< First feed rate (speed) for the upcomming island.
|
double m_next_feedrate; ///< First feed rate (speed) for the upcomming island.
|
||||||
double m_current_feedrate; ///< Current, latest feedrate.
|
double m_current_feedrate; ///< Current, latest feedrate.
|
||||||
int m_last_extruder_id; ///< Last used extruder ID.
|
int m_last_config_index; ///< Filament config index of the last PA_CHANGE tag.
|
||||||
|
|
||||||
std::regex m_pa_change_pattern; ///< Regular expression to detect PA_CHANGE pattern.
|
std::regex m_pa_change_pattern; ///< Regular expression to detect PA_CHANGE pattern.
|
||||||
std::regex m_g1_f_pattern; ///< Regular expression to detect G1 F pattern.
|
std::regex m_g1_f_pattern; ///< Regular expression to detect G1 F pattern.
|
||||||
std::smatch m_match; ///< Match results for regular expressions.
|
std::smatch m_match; ///< Match results for regular expressions.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the PA interpolator attached to the specified tool ID.
|
* @brief Get the PA interpolator attached to the specified filament config index.
|
||||||
*
|
*
|
||||||
* This method manually sets the adaptive PA internally held value.
|
* @param config_index The filament config index (one per filament and extruder variant) for which the PA interpolation model is to be returned.
|
||||||
* Call this when changing tools or in any other case where the internally assumed last PA value may be incorrect
|
* @return The Adaptive PA Interpolator object corresponding to that index, or nullptr when adaptive PA is off for it.
|
||||||
*
|
|
||||||
* @param An integer with the tool ID for which the PA interpolation model is to be returned.
|
|
||||||
* @return The Adaptive PA Interpolator object corresponding to that tool.
|
|
||||||
*/
|
*/
|
||||||
AdaptivePAInterpolator* getInterpolator(unsigned int tool_id);
|
AdaptivePAInterpolator* getInterpolator(unsigned int config_index);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|||||||
+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)
|
if (m_default_region_config.precise_outer_wall && m_default_region_config.wall_sequence != WallSequence::InnerOuter)
|
||||||
warn(L("The precise wall option will be ignored for outer-inner or inner-outer-inner wall sequences."), "precise_outer_wall");
|
warn(L("The precise wall option will be ignored for outer-inner or inner-outer-inner wall sequences."), "precise_outer_wall");
|
||||||
|
|
||||||
// check adaptive pressure advance model
|
// check adaptive pressure advance model of every extruder variant column of the used filaments
|
||||||
for (unsigned int extruder_id : extruders) {
|
const std::vector<int> &self_index = m_config.filament_self_index.values;
|
||||||
if (m_config.adaptive_pressure_advance.get_at(extruder_id) &&
|
const size_t pa_columns = std::max(m_config.adaptive_pressure_advance_model.size(), size_t(extruders.back()) + 1);
|
||||||
m_config.enable_pressure_advance.get_at(extruder_id)) {
|
for (size_t column = 0; column < pa_columns; ++column) {
|
||||||
|
// filament_self_index maps a column to its filament once the filament arrays hold one column per variant
|
||||||
|
const unsigned int filament_id = self_index.size() == pa_columns ? self_index[column] - 1 : column;
|
||||||
|
if (!std::binary_search(extruders.begin(), extruders.end(), filament_id))
|
||||||
|
continue;
|
||||||
|
if (m_config.adaptive_pressure_advance.get_at(column) &&
|
||||||
|
m_config.enable_pressure_advance.get_at(column)) {
|
||||||
|
|
||||||
const std::string pa_model = m_config.adaptive_pressure_advance_model.get_at(extruder_id);
|
const std::string pa_model = m_config.adaptive_pressure_advance_model.get_at(column);
|
||||||
if (!pa_model.empty()) {
|
if (!pa_model.empty()) {
|
||||||
std::string validation_error = AdaptivePAProcessor::validate_adaptive_pa_model(pa_model);
|
std::string validation_error = AdaptivePAProcessor::validate_adaptive_pa_model(pa_model);
|
||||||
if (!validation_error.empty()) {
|
if (!validation_error.empty()) {
|
||||||
|
|||||||
@@ -9403,6 +9403,10 @@ void PrintConfigDef::handle_legacy_composite(DynamicPrintConfig &config)
|
|||||||
}
|
}
|
||||||
config.set_key_value("wiping_volumes_use_custom_matrix", new ConfigOptionBool(custom));
|
config.set_key_value("wiping_volumes_use_custom_matrix", new ConfigOptionBool(custom));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Orca: a config saved before a key joined filament_options_with_variant stores it once per filament
|
||||||
|
// rather than once per filament variant, and one exported by an older CLI may store a single value.
|
||||||
|
normalize_filament_values_to_variants(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
const PrintConfigDef print_config_def;
|
const PrintConfigDef print_config_def;
|
||||||
@@ -9505,6 +9509,13 @@ std::set<std::string> filament_options_with_variant = {
|
|||||||
"filament_ironing_spacing",
|
"filament_ironing_spacing",
|
||||||
"filament_ironing_inset",
|
"filament_ironing_inset",
|
||||||
"filament_ironing_speed",
|
"filament_ironing_speed",
|
||||||
|
// Orca: pressure advance
|
||||||
|
"enable_pressure_advance",
|
||||||
|
"pressure_advance",
|
||||||
|
"adaptive_pressure_advance",
|
||||||
|
"adaptive_pressure_advance_model",
|
||||||
|
"adaptive_pressure_advance_overhangs",
|
||||||
|
"adaptive_pressure_advance_bridges",
|
||||||
"activate_air_filtration",
|
"activate_air_filtration",
|
||||||
"activate_air_filtration_during_print",
|
"activate_air_filtration_during_print",
|
||||||
"activate_air_filtration_on_completion",
|
"activate_air_filtration_on_completion",
|
||||||
@@ -10712,6 +10723,25 @@ void set_variant_override(ConfigOptionVectorBase &target, const ConfigOptionVect
|
|||||||
target.set_to_index(&source, indices, stride);
|
target.set_to_index(&source, indices, stride);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void normalize_filament_values_to_variants(DynamicPrintConfig &config)
|
||||||
|
{
|
||||||
|
const auto *self_index = config.option<ConfigOptionInts>("filament_self_index");
|
||||||
|
if (self_index == nullptr || self_index->empty())
|
||||||
|
return;
|
||||||
|
const int filament_count = *std::max_element(self_index->values.begin(), self_index->values.end());
|
||||||
|
if (filament_count <= 0 || size_t(filament_count) >= self_index->size())
|
||||||
|
return;
|
||||||
|
for (const std::string &key : filament_options_with_variant) {
|
||||||
|
auto *opt = dynamic_cast<ConfigOptionVectorBase *>(config.option(key));
|
||||||
|
if (opt == nullptr || (opt->size() != size_t(filament_count) && opt->size() != 1))
|
||||||
|
continue;
|
||||||
|
std::unique_ptr<ConfigOption> per_filament(opt->clone());
|
||||||
|
// set_at() takes the first value for a filament past the end of a single-value vector
|
||||||
|
for (size_t variant = 0; variant < self_index->size(); ++variant)
|
||||||
|
opt->set_at(per_filament.get(), variant, self_index->values[variant] - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//used for object/region config
|
//used for object/region config
|
||||||
//use the smallest of multiple to single
|
//use the smallest of multiple to single
|
||||||
|
|||||||
@@ -884,6 +884,11 @@ extern std::set<std::string> empty_options;
|
|||||||
void set_variant_override(ConfigOptionVectorBase &target, const ConfigOptionVectorBase &source,
|
void set_variant_override(ConfigOptionVectorBase &target, const ConfigOptionVectorBase &source,
|
||||||
const std::vector<int> &variant_index, int stride = 1);
|
const std::vector<int> &variant_index, int stride = 1);
|
||||||
|
|
||||||
|
// Orca: lays every filament_options_with_variant option out one value per filament variant, as
|
||||||
|
// filament_self_index maps the variants to filaments. An option holding one value per filament, or a
|
||||||
|
// single value, gives every variant of a filament that filament's value; other lengths are left alone.
|
||||||
|
void normalize_filament_values_to_variants(DynamicPrintConfig &config);
|
||||||
|
|
||||||
extern std::set<std::string> filament_dev_options;
|
extern std::set<std::string> filament_dev_options;
|
||||||
|
|
||||||
extern void update_static_print_config_from_dynamic(ConfigBase& config, const DynamicPrintConfig& dest_config, std::vector<int> variant_index, std::set<std::string>& key_set1, int stride = 1);
|
extern void update_static_print_config_from_dynamic(ConfigBase& config, const DynamicPrintConfig& dest_config, std::vector<int> variant_index, std::set<std::string>& key_set1, int stride = 1);
|
||||||
|
|||||||
+10
-1
@@ -256,10 +256,19 @@ extern bool is_gallery_file(const std::string& path, char const* type);
|
|||||||
extern bool is_shapes_dir(const std::string& dir);
|
extern bool is_shapes_dir(const std::string& dir);
|
||||||
//BBS: add json support
|
//BBS: add json support
|
||||||
extern bool is_json_file(const std::string& path);
|
extern bool is_json_file(const std::string& path);
|
||||||
// True if rel_path is relative, has no ".." component and, joined to root, still resolves inside it.
|
// True if rel_path is relative, has no ".." component or embedded NUL and, joined to root, still resolves inside it.
|
||||||
// Both '/' and '\\' are treated as separators on every platform, so an archive rejected on one OS
|
// Both '/' and '\\' are treated as separators on every platform, so an archive rejected on one OS
|
||||||
// is rejected on all of them.
|
// is rejected on all of them.
|
||||||
extern bool is_path_within_root(const std::string &rel_path, const boost::filesystem::path &root);
|
extern bool is_path_within_root(const std::string &rel_path, const boost::filesystem::path &root);
|
||||||
|
// True if a symlink stored at link_rel_path (relative to root) with this target stays inside root: the target
|
||||||
|
// must be relative, and joined to the link's directory it must pass is_path_within_root.
|
||||||
|
extern bool is_symlink_target_within_root(const std::string &link_rel_path, const std::string &target, const boost::filesystem::path &root);
|
||||||
|
// True if path names an entry strictly inside root: it must be spelled with root as its prefix,
|
||||||
|
// and must still resolve inside root once symlinks are followed.
|
||||||
|
extern bool is_absolute_path_within_root(const boost::filesystem::path &path, const boost::filesystem::path &root);
|
||||||
|
// True if a file with this name is of a type that the desktop opens as plain content, so it cannot run code.
|
||||||
|
// Anything unknown is not safe.
|
||||||
|
extern bool is_safe_to_open_file_name(const std::string &file_name);
|
||||||
|
|
||||||
// Orca: custom protocal support utils
|
// Orca: custom protocal support utils
|
||||||
inline bool is_orca_open(const std::string& url) { return boost::starts_with(url, "orcaslicer://open"); }
|
inline bool is_orca_open(const std::string& url) { return boost::starts_with(url, "orcaslicer://open"); }
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
#include "miniz_extension.hpp"
|
#include "miniz_extension.hpp"
|
||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
|
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
|
#include <boost/log/trivial.hpp>
|
||||||
|
|
||||||
#if defined(_MSC_VER) || defined(__MINGW64__)
|
#if defined(_MSC_VER) || defined(__MINGW64__)
|
||||||
#include "boost/nowide/cstdio.hpp"
|
#include "boost/nowide/cstdio.hpp"
|
||||||
#endif
|
#endif
|
||||||
@@ -115,6 +118,66 @@ std::string decode_archive_entry_path(mz_zip_archive *zip, const mz_zip_archive_
|
|||||||
return decode_zip_unicode_path_extra_field(extra.substr(0, extra_size > 0 ? extra_size - 1 : 0), stat.m_filename);
|
return decode_zip_unicode_path_extra_field(extra.substr(0, extra_size > 0 ? extra_size - 1 : 0), stat.m_filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool extract_archive_confined(const std::string &zip_path_utf8, const std::string &dest_dir)
|
||||||
|
{
|
||||||
|
mz_zip_archive archive;
|
||||||
|
mz_zip_zero_struct(&archive);
|
||||||
|
|
||||||
|
if (!open_zip_reader(&archive, zip_path_utf8)) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "Unable to open zip reader for " << zip_path_utf8;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mz_uint num_entries = mz_zip_reader_get_num_files(&archive);
|
||||||
|
mz_zip_archive_file_stat stat;
|
||||||
|
|
||||||
|
// Validate every entry first so an archive with a single escaping entry leaves no partial output behind.
|
||||||
|
const boost::filesystem::path root(dest_dir);
|
||||||
|
for (mz_uint i = 0; i < num_entries; ++i) {
|
||||||
|
if (mz_zip_reader_file_stat(&archive, i, &stat) && !is_path_within_root(stat.m_filename, root)) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "Unzip: rejecting " << zip_path_utf8 << ", entry " << stat.m_filename << " resolves outside " << dest_dir;
|
||||||
|
close_zip_reader(&archive);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (mz_uint i = 0; i < num_entries; ++i) {
|
||||||
|
if (!mz_zip_reader_file_stat(&archive, i, &stat)) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "Unzip: read file stat failed";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const std::string dest_file = dest_dir + "/" + stat.m_filename;
|
||||||
|
try {
|
||||||
|
if (stat.m_is_directory) {
|
||||||
|
const boost::filesystem::path dest_path(dest_file);
|
||||||
|
if (!boost::filesystem::exists(dest_path))
|
||||||
|
boost::filesystem::create_directories(dest_path);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (stat.m_uncomp_size == 0) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "Unzip: invalid size for file " << stat.m_filename;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Replace a symlink at the destination rather than writing through it.
|
||||||
|
const boost::filesystem::path dest_path(dest_file);
|
||||||
|
if (boost::filesystem::is_symlink(boost::filesystem::symlink_status(dest_path)))
|
||||||
|
boost::filesystem::remove(dest_path);
|
||||||
|
if (!mz_zip_reader_extract_to_file(&archive, stat.m_file_index, dest_file.c_str(), 0)) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "Unzip: extract file " << stat.m_filename << " to dest " << dest_file << " failed";
|
||||||
|
close_zip_reader(&archive);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "Unzip: successfully extract file " << stat.m_file_index << " to " << dest_file;
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
close_zip_reader(&archive);
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "Unzip: archive read exception: " << e.what();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close_zip_reader(&archive);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
MZ_Archive::MZ_Archive()
|
MZ_Archive::MZ_Archive()
|
||||||
{
|
{
|
||||||
mz_zip_zero_struct(&arch);
|
mz_zip_zero_struct(&arch);
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ bool open_zip_writer(mz_zip_archive *zip, const std::string &fname_utf8);
|
|||||||
bool close_zip_reader(mz_zip_archive *zip);
|
bool close_zip_reader(mz_zip_archive *zip);
|
||||||
bool close_zip_writer(mz_zip_archive *zip);
|
bool close_zip_writer(mz_zip_archive *zip);
|
||||||
std::string decode_archive_entry_path(mz_zip_archive *zip, const mz_zip_archive_file_stat &stat);
|
std::string decode_archive_entry_path(mz_zip_archive *zip, const mz_zip_archive_file_stat &stat);
|
||||||
|
// Extracts every entry of the archive under dest_dir. Nothing is written if any entry would resolve outside dest_dir.
|
||||||
|
bool extract_archive_confined(const std::string &zip_path_utf8, const std::string &dest_dir);
|
||||||
|
|
||||||
class MZ_Archive {
|
class MZ_Archive {
|
||||||
public:
|
public:
|
||||||
|
|||||||
+39
-1
@@ -70,6 +70,7 @@
|
|||||||
#include <boost/shared_ptr.hpp>
|
#include <boost/shared_ptr.hpp>
|
||||||
|
|
||||||
#include <boost/algorithm/string/predicate.hpp>
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
|
#include <boost/algorithm/string/case_conv.hpp>
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
#include <boost/filesystem/path.hpp>
|
#include <boost/filesystem/path.hpp>
|
||||||
#include <boost/nowide/fstream.hpp>
|
#include <boost/nowide/fstream.hpp>
|
||||||
@@ -1093,6 +1094,9 @@ bool is_path_within_root(const std::string &rel_path, const boost::filesystem::p
|
|||||||
auto is_separator = [](char c) { return c == '/' || c == '\\'; };
|
auto is_separator = [](char c) { return c == '/' || c == '\\'; };
|
||||||
if (rel_path.empty() || is_separator(rel_path.front()) || (rel_path.size() > 1 && rel_path[1] == ':'))
|
if (rel_path.empty() || is_separator(rel_path.front()) || (rel_path.size() > 1 && rel_path[1] == ':'))
|
||||||
return false;
|
return false;
|
||||||
|
// The filesystem calls stop at a NUL, so they would act on a shorter path than the one checked here.
|
||||||
|
if (rel_path.find('\0') != std::string::npos)
|
||||||
|
return false;
|
||||||
for (size_t start = 0; start <= rel_path.size();) {
|
for (size_t start = 0; start <= rel_path.size();) {
|
||||||
size_t end = start;
|
size_t end = start;
|
||||||
while (end < rel_path.size() && !is_separator(rel_path[end]))
|
while (end < rel_path.size() && !is_separator(rel_path[end]))
|
||||||
@@ -1103,7 +1107,10 @@ bool is_path_within_root(const std::string &rel_path, const boost::filesystem::p
|
|||||||
}
|
}
|
||||||
// Resolve against the canonical root so a symlink inside it cannot lead back out.
|
// Resolve against the canonical root so a symlink inside it cannot lead back out.
|
||||||
try {
|
try {
|
||||||
const std::string root_str = boost::filesystem::weakly_canonical(root).string();
|
std::string root_str = boost::filesystem::weakly_canonical(root).string();
|
||||||
|
// A trailing separator on root would otherwise fail the prefix match below for every path.
|
||||||
|
while (!root_str.empty() && (root_str.back() == '/' || root_str.back() == boost::filesystem::path::preferred_separator))
|
||||||
|
root_str.pop_back();
|
||||||
const std::string full_str = boost::filesystem::weakly_canonical(root / rel_path).string();
|
const std::string full_str = boost::filesystem::weakly_canonical(root / rel_path).string();
|
||||||
return full_str.compare(0, root_str.size(), root_str) == 0 &&
|
return full_str.compare(0, root_str.size(), root_str) == 0 &&
|
||||||
(full_str.size() == root_str.size() || full_str[root_str.size()] == boost::filesystem::path::preferred_separator);
|
(full_str.size() == root_str.size() || full_str[root_str.size()] == boost::filesystem::path::preferred_separator);
|
||||||
@@ -1112,6 +1119,37 @@ bool is_path_within_root(const std::string &rel_path, const boost::filesystem::p
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool is_symlink_target_within_root(const std::string &link_rel_path, const std::string &target, const boost::filesystem::path &root)
|
||||||
|
{
|
||||||
|
if (target.empty() || target.front() == '/' || target.front() == '\\' || (target.size() > 1 && target[1] == ':'))
|
||||||
|
return false;
|
||||||
|
// A relative target without ".." only descends from the link's directory, so no chain of such links can leave root.
|
||||||
|
const size_t sep = link_rel_path.find_last_of("/\\");
|
||||||
|
return is_path_within_root((sep == std::string::npos ? std::string() : link_rel_path.substr(0, sep + 1)) + target, root);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_absolute_path_within_root(const boost::filesystem::path &path, const boost::filesystem::path &root)
|
||||||
|
{
|
||||||
|
const boost::filesystem::path rel = path.lexically_relative(root);
|
||||||
|
return !rel.empty() && rel != "." && is_path_within_root(rel.string(), root);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_safe_to_open_file_name(const std::string &file_name)
|
||||||
|
{
|
||||||
|
// Formats that cannot carry macros or scripts. Legacy and OpenDocument office files, HTML and SVG are left out on purpose.
|
||||||
|
static const std::vector<std::string> safe_extensions = {
|
||||||
|
"jpg", "jpeg", "jfif", "pjpeg", "pjp", "png", "gif", "bmp", "webp", "tif", "tiff",
|
||||||
|
"pdf", "txt", "md", "csv", "docx", "xlsx", "pptx",
|
||||||
|
"stl", "obj", "3mf", "amf", "ply", "step", "stp", "iges", "igs", "dxf",
|
||||||
|
"mp4", "mov", "webm"};
|
||||||
|
// The name must end in the extension itself: Windows drops trailing dots and spaces and reads ':' as a stream separator.
|
||||||
|
const size_t dot = file_name.find_last_of('.');
|
||||||
|
if (dot == std::string::npos || file_name.find_first_of("/\\:") != std::string::npos)
|
||||||
|
return false;
|
||||||
|
const std::string extension = boost::algorithm::to_lower_copy(file_name.substr(dot + 1));
|
||||||
|
return std::find(safe_extensions.begin(), safe_extensions.end(), extension) != safe_extensions.end();
|
||||||
|
}
|
||||||
|
|
||||||
bool is_img_file(const std::string &path)
|
bool is_img_file(const std::string &path)
|
||||||
{
|
{
|
||||||
return boost::iends_with(path, ".png") || boost::iends_with(path, ".svg");
|
return boost::iends_with(path, ".png") || boost::iends_with(path, ".svg");
|
||||||
|
|||||||
@@ -1019,14 +1019,9 @@ if (UNIX AND NOT APPLE)
|
|||||||
find_package(GTK${SLIC3R_GTK} REQUIRED)
|
find_package(GTK${SLIC3R_GTK} REQUIRED)
|
||||||
pkg_check_modules(LIBSECRET REQUIRED libsecret-1)
|
pkg_check_modules(LIBSECRET REQUIRED libsecret-1)
|
||||||
pkg_check_modules(webkit2gtk REQUIRED webkit2gtk-4.1)
|
pkg_check_modules(webkit2gtk REQUIRED webkit2gtk-4.1)
|
||||||
if (FLATPAK)
|
find_package(X11 REQUIRED)
|
||||||
# I don't know why this is needed, but for whatever reason slic3r isn't
|
|
||||||
# linking to X11 and webkit2gtk. force it.
|
|
||||||
find_package(X11 REQUIRED)
|
|
||||||
target_link_libraries(libslic3r_gui ${X11_LIBRARIES} ${webkit2gtk_LIBRARIES})
|
|
||||||
endif()
|
|
||||||
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${GTK${SLIC3R_GTK}_INCLUDE_DIRS} ${LIBSECRET_INCLUDE_DIRS} ${webkit2gtk_INCLUDE_DIRS})
|
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${GTK${SLIC3R_GTK}_INCLUDE_DIRS} ${LIBSECRET_INCLUDE_DIRS} ${webkit2gtk_INCLUDE_DIRS})
|
||||||
target_link_libraries(libslic3r_gui ${GTK${SLIC3R_GTK}_LIBRARIES} fontconfig ${LIBSECRET_LIBRARIES})
|
target_link_libraries(libslic3r_gui ${GTK${SLIC3R_GTK}_LIBRARIES} fontconfig ${LIBSECRET_LIBRARIES} ${webkit2gtk_LIBRARIES} ${X11_LIBRARIES})
|
||||||
|
|
||||||
# Propagate GDK backend detection results as compile definitions so that
|
# Propagate GDK backend detection results as compile definitions so that
|
||||||
# LinuxDisplayBackend.cpp can include the right GDK headers.
|
# LinuxDisplayBackend.cpp can include the right GDK headers.
|
||||||
|
|||||||
@@ -428,7 +428,7 @@ void AuFile::on_dclick(wxMouseEvent &evt)
|
|||||||
if (m_type == AddFileButton)
|
if (m_type == AddFileButton)
|
||||||
return;
|
return;
|
||||||
else
|
else
|
||||||
wxLaunchDefaultApplication(m_file_path.wstring(), 0);
|
desktop_open_project_attachment(this, m_file_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AuFile::on_mouse_left_up(wxMouseEvent &evt)
|
void AuFile::on_mouse_left_up(wxMouseEvent &evt)
|
||||||
|
|||||||
@@ -171,14 +171,16 @@ void ConfigManipulation::check_adaptive_pressure_advance_model(DynamicPrintConfi
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
const auto* model = config->option<ConfigOptionStrings>("adaptive_pressure_advance_model");
|
const auto* model = config->option<ConfigOptionStrings>("adaptive_pressure_advance_model");
|
||||||
if (model == nullptr || model->values.empty())
|
if (model == nullptr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::string raw_model;
|
// Each extruder variant holds its own model.
|
||||||
for (const std::string& chunk : model->values)
|
std::string error;
|
||||||
raw_model += chunk;
|
for (const std::string& variant_model : model->values) {
|
||||||
|
error = AdaptivePAProcessor::validate_adaptive_pa_model(variant_model);
|
||||||
std::string error = AdaptivePAProcessor::validate_adaptive_pa_model(raw_model);
|
if (!error.empty())
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (!error.empty()) {
|
if (!error.empty()) {
|
||||||
wxString msg_text = _L("Adaptive Pressure Advance model validation failed:\n");
|
wxString msg_text = _L("Adaptive Pressure Advance model validation failed:\n");
|
||||||
msg_text += from_u8(error);
|
msg_text += from_u8(error);
|
||||||
|
|||||||
@@ -14,6 +14,9 @@
|
|||||||
#include <boost/algorithm/string/predicate.hpp>
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
#include <boost/any.hpp>
|
#include <boost/any.hpp>
|
||||||
|
|
||||||
|
#include <wx/filename.h>
|
||||||
|
#include <wx/filesys.h>
|
||||||
|
|
||||||
#if __APPLE__
|
#if __APPLE__
|
||||||
#import <IOKit/pwr_mgt/IOPMLib.h>
|
#import <IOKit/pwr_mgt/IOPMLib.h>
|
||||||
#elif _WIN32
|
#elif _WIN32
|
||||||
@@ -27,11 +30,13 @@
|
|||||||
|
|
||||||
#include "AboutDialog.hpp"
|
#include "AboutDialog.hpp"
|
||||||
#include "MsgDialog.hpp"
|
#include "MsgDialog.hpp"
|
||||||
|
#include "Plater.hpp"
|
||||||
#include "format.hpp"
|
#include "format.hpp"
|
||||||
|
|
||||||
#include "WebUserLoginDialog.hpp"
|
#include "WebUserLoginDialog.hpp"
|
||||||
|
|
||||||
#include "libslic3r/Print.hpp"
|
#include "libslic3r/Print.hpp"
|
||||||
|
#include "libslic3r/Utils.hpp"
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
@@ -531,6 +536,11 @@ boost::filesystem::path into_path(const wxString &str)
|
|||||||
return boost::filesystem::path(str.wx_str());
|
return boost::filesystem::path(str.wx_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wxString file_url_from_path(const boost::filesystem::path &path)
|
||||||
|
{
|
||||||
|
return wxFileSystem::FileNameToURL(wxFileName(from_path(path)));
|
||||||
|
}
|
||||||
|
|
||||||
void about()
|
void about()
|
||||||
{
|
{
|
||||||
AboutDialog dlg;
|
AboutDialog dlg;
|
||||||
@@ -635,4 +645,26 @@ void desktop_open_any_folder( const std::string& path )
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool desktop_open_project_attachment(wxWindow *parent, const boost::filesystem::path &path)
|
||||||
|
{
|
||||||
|
// The auxiliary path is UTF-8, which is what boost::filesystem reads a narrow string as.
|
||||||
|
const boost::filesystem::path aux_root(wxGetApp().plater()->model().get_auxiliary_file_temp_path());
|
||||||
|
boost::system::error_code ec;
|
||||||
|
if (!is_absolute_path_within_root(path, aux_root) || !boost::filesystem::is_regular_file(path, ec))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Attachments come with the project and carry no download mark, so the desktop would open them without a warning.
|
||||||
|
if (!is_safe_to_open_file_name(path.filename().string())) {
|
||||||
|
MessageDialog dlg(parent,
|
||||||
|
wxString::Format(_L("\"%s\" is not a plain document, image or model file. Opening it may run it as a "
|
||||||
|
"program or script on this computer.\n\n"
|
||||||
|
"Only open attachments from projects you trust. Open it anyway?"),
|
||||||
|
from_path(path.filename())),
|
||||||
|
_L("Open attachment"), wxICON_WARNING | wxYES_NO);
|
||||||
|
if (dlg.ShowModal() != wxID_YES)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return wxLaunchDefaultApplication(from_path(path), 0);
|
||||||
|
}
|
||||||
|
|
||||||
} }
|
} }
|
||||||
|
|||||||
@@ -76,6 +76,9 @@ std::string into_u8(const wxString &str);
|
|||||||
wxString from_path(const boost::filesystem::path &path);
|
wxString from_path(const boost::filesystem::path &path);
|
||||||
// boost path from wxString
|
// boost path from wxString
|
||||||
boost::filesystem::path into_path(const wxString &str);
|
boost::filesystem::path into_path(const wxString &str);
|
||||||
|
// file:// URL of a local path, percent-encoded so characters such as '#', '%' and '?' stay part of the path.
|
||||||
|
// Append any query or fragment to the result.
|
||||||
|
wxString file_url_from_path(const boost::filesystem::path &path);
|
||||||
|
|
||||||
// Display an About dialog
|
// Display an About dialog
|
||||||
extern void about();
|
extern void about();
|
||||||
@@ -83,6 +86,9 @@ extern void about();
|
|||||||
extern void desktop_open_datadir_folder();
|
extern void desktop_open_datadir_folder();
|
||||||
// Ask the destop to open one folder
|
// Ask the destop to open one folder
|
||||||
extern void desktop_open_any_folder(const std::string& path);
|
extern void desktop_open_any_folder(const std::string& path);
|
||||||
|
// Ask the desktop to open a file from the project's auxiliary directory, after a confirmation
|
||||||
|
// unless its type is known to be plain content. Returns false if the file was not opened.
|
||||||
|
extern bool desktop_open_project_attachment(wxWindow *parent, const boost::filesystem::path &path);
|
||||||
} // namespace GUI
|
} // namespace GUI
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
|||||||
@@ -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());
|
size_t n = mz_zip_reader_get_extra(&archive, stat.m_file_index, extra.data(), extra.size());
|
||||||
dest_file = decode(extra.substr(0, n), stat.m_filename);
|
dest_file = decode(extra.substr(0, n), stat.m_filename);
|
||||||
}
|
}
|
||||||
|
if (!is_path_within_root(dest_file, plugin_folder)) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "[install_plugin] entry " << dest_file << " resolves outside " << plugin_folder.string();
|
||||||
|
close_zip_reader(&archive);
|
||||||
|
if (pro_fn) { pro_fn(InstallStatusUnzipFailed, 0, cancel); }
|
||||||
|
return InstallStatusUnzipFailed;
|
||||||
|
}
|
||||||
auto dest_path = plugin_folder / dest_file;
|
auto dest_path = plugin_folder / dest_file;
|
||||||
boost::filesystem::create_directories(dest_path.parent_path());
|
|
||||||
std::string dest_zip_file = encode_path(dest_path.string().c_str());
|
std::string dest_zip_file = encode_path(dest_path.string().c_str());
|
||||||
|
#ifndef WIN32
|
||||||
|
// Validate a symlink's target before anything at the destination is replaced.
|
||||||
|
const bool is_link = S_ISLNK(stat.m_external_attr >> 16);
|
||||||
|
std::string link;
|
||||||
|
if (is_link) {
|
||||||
|
link.assign(stat.m_uncomp_size, 0);
|
||||||
|
if (!mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, link.data(), stat.m_uncomp_size, 0) ||
|
||||||
|
!is_symlink_target_within_root(dest_file, link, plugin_folder)) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "[install_plugin] link " << dest_file << " -> " << link << " is unreadable or resolves outside " << plugin_folder.string();
|
||||||
|
close_zip_reader(&archive);
|
||||||
|
if (pro_fn) { pro_fn(InstallStatusUnzipFailed, 0, cancel); }
|
||||||
|
return InstallStatusUnzipFailed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
try {
|
try {
|
||||||
if (fs::exists(dest_path)) {
|
boost::filesystem::create_directories(dest_path.parent_path());
|
||||||
|
// symlink_status so that an existing symlink, dangling or not, is replaced rather than written through.
|
||||||
|
if (fs::exists(fs::symlink_status(dest_path))) {
|
||||||
boost::system::error_code ec;
|
boost::system::error_code ec;
|
||||||
fs::remove(dest_path, ec);
|
fs::remove(dest_path, ec);
|
||||||
if (ec) {
|
if (ec) {
|
||||||
@@ -1544,9 +1566,8 @@ int GUI_App::install_plugin(std::string name, std::string package_name, InstallP
|
|||||||
}
|
}
|
||||||
mz_bool res = 0;
|
mz_bool res = 0;
|
||||||
#ifndef WIN32
|
#ifndef WIN32
|
||||||
if (S_ISLNK(stat.m_external_attr >> 16)) {
|
if (is_link) {
|
||||||
std::string link(stat.m_uncomp_size + 1, 0);
|
res = 1;
|
||||||
res = mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, link.data(), stat.m_uncomp_size, 0);
|
|
||||||
try {
|
try {
|
||||||
boost::filesystem::create_symlink(link, dest_path);
|
boost::filesystem::create_symlink(link, dest_path);
|
||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
|
|||||||
@@ -98,10 +98,7 @@ void MarkdownTip::LoadStyle()
|
|||||||
ph /= "tooltip/styled.html";
|
ph /= "tooltip/styled.html";
|
||||||
_data_dir = false;
|
_data_dir = false;
|
||||||
}
|
}
|
||||||
auto url = ph.string();
|
_tipView->LoadURL(file_url_from_path(ph));
|
||||||
std::replace(url.begin(), url.end(), '\\', '/');
|
|
||||||
url = "file:///" + url;
|
|
||||||
_tipView->LoadURL(from_u8(url));
|
|
||||||
_lastTip.clear();
|
_lastTip.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+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)
|
bool Plater::has_illegal_filename_characters(const wxString& wxs_name)
|
||||||
{
|
{
|
||||||
std::string name = into_u8(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 url = from_u8(PrintHost::get_print_host_webui(&cfg));
|
||||||
wxString apikey;
|
wxString apikey;
|
||||||
if(url.empty())
|
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 {
|
else {
|
||||||
const auto host_type = cfg.option<ConfigOptionEnum<PrintHostType>>("host_type")->value;
|
const auto host_type = cfg.option<ConfigOptionEnum<PrintHostType>>("host_type")->value;
|
||||||
if (cfg.has("printhost_apikey") && (host_type != htSimplyPrint))
|
if (cfg.has("printhost_apikey") && (host_type != htSimplyPrint))
|
||||||
@@ -17064,11 +17088,7 @@ void Plater::calib_input_shaping_freq(const Calib_Params& params)
|
|||||||
set_config_values<double, ConfigOptionFloatsNullable>(print_config, "default_jerk", 0);
|
set_config_values<double, ConfigOptionFloatsNullable>(print_config, "default_jerk", 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!filament_config->option<ConfigOptionBools>("enable_pressure_advance")->get_at(0)) {
|
zero_pressure_advance_where_disabled(filament_config);
|
||||||
set_config_values<bool, ConfigOptionBools>(filament_config, "enable_pressure_advance", true);
|
|
||||||
set_config_values<double, ConfigOptionFloatsNullable>(filament_config, "pressure_advance", 0.0);
|
|
||||||
set_config_values<bool, ConfigOptionBools>(filament_config, "adaptive_pressure_advance", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false});
|
printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false});
|
||||||
printer_config->set_key_value("input_shaping_emit", new ConfigOptionBool{false});
|
printer_config->set_key_value("input_shaping_emit", new ConfigOptionBool{false});
|
||||||
@@ -17130,11 +17150,7 @@ void Plater::calib_input_shaping_damp(const Calib_Params& params)
|
|||||||
set_config_values<double, ConfigOptionFloatsNullable>(print_config, "default_jerk", 0);
|
set_config_values<double, ConfigOptionFloatsNullable>(print_config, "default_jerk", 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!filament_config->option<ConfigOptionBools>("enable_pressure_advance")->get_at(0)) {
|
zero_pressure_advance_where_disabled(filament_config);
|
||||||
set_config_values<bool, ConfigOptionBools>(filament_config, "enable_pressure_advance", true);
|
|
||||||
set_config_values<double, ConfigOptionFloatsNullable>(filament_config, "pressure_advance", 0.0);
|
|
||||||
set_config_values<bool, ConfigOptionBools>(filament_config, "adaptive_pressure_advance", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false});
|
printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false});
|
||||||
printer_config->set_key_value("input_shaping_emit", new ConfigOptionBool{false});
|
printer_config->set_key_value("input_shaping_emit", new ConfigOptionBool{false});
|
||||||
@@ -17196,11 +17212,7 @@ void Plater::Calib_Cornering(const Calib_Params& params)
|
|||||||
set_config_values<double, ConfigOptionFloatsNullable>(print_config, "default_jerk", 0);
|
set_config_values<double, ConfigOptionFloatsNullable>(print_config, "default_jerk", 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!filament_config->option<ConfigOptionBools>("enable_pressure_advance")->get_at(0)) {
|
zero_pressure_advance_where_disabled(filament_config);
|
||||||
set_config_values<bool, ConfigOptionBools>(filament_config, "enable_pressure_advance", true);
|
|
||||||
set_config_values<double, ConfigOptionFloatsNullable>(filament_config, "pressure_advance", 0.0);
|
|
||||||
set_config_values<bool, ConfigOptionBools>(filament_config, "adaptive_pressure_advance", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false});
|
printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false});
|
||||||
printer_config->set_key_value("input_shaping_emit", new ConfigOptionBool{true});
|
printer_config->set_key_value("input_shaping_emit", new ConfigOptionBool{true});
|
||||||
|
|||||||
@@ -57,9 +57,7 @@ PrivacyUpdateDialog::PrivacyUpdateDialog(wxWindow* parent, wxWindowID id, const
|
|||||||
|
|
||||||
fs::path ph(resources_dir());
|
fs::path ph(resources_dir());
|
||||||
ph /= "tooltip/privacyupdate.html";
|
ph /= "tooltip/privacyupdate.html";
|
||||||
m_host_url = ph.string();
|
m_host_url = into_u8(file_url_from_path(ph));
|
||||||
std::replace(m_host_url.begin(), m_host_url.end(), '\\', '/');
|
|
||||||
m_host_url = "file:///" + m_host_url;
|
|
||||||
m_vebview_release_note->LoadURL(from_u8(m_host_url));
|
m_vebview_release_note->LoadURL(from_u8(m_host_url));
|
||||||
m_sizer_right->Add(m_vebview_release_note, 0, wxEXPAND | wxRIGHT | wxLEFT, FromDIP(15));
|
m_sizer_right->Add(m_vebview_release_note, 0, wxEXPAND | wxRIGHT | wxLEFT, FromDIP(15));
|
||||||
|
|
||||||
|
|||||||
@@ -45,10 +45,10 @@ const std::vector<std::string> license_list = {
|
|||||||
ProjectPanel::ProjectPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style) : wxPanel(parent, id, pos, size, style)
|
ProjectPanel::ProjectPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style) : wxPanel(parent, id, pos, size, style)
|
||||||
{
|
{
|
||||||
SetBackgroundColour(*wxWHITE);
|
SetBackgroundColour(*wxWHITE);
|
||||||
m_project_home_url = 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();
|
wxString strlang = wxGetApp().current_language_code_safe();
|
||||||
if (strlang != "")
|
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);
|
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||||
|
|
||||||
@@ -293,10 +293,8 @@ void ProjectPanel::OnScriptMessage(wxWebViewEvent& evt)
|
|||||||
if (!accessory_path.empty()) {
|
if (!accessory_path.empty()) {
|
||||||
std::string decode_path = wxGetApp().url_decode(accessory_path.ToStdString());
|
std::string decode_path = wxGetApp().url_decode(accessory_path.ToStdString());
|
||||||
fs::path path(decode_path);
|
fs::path path(decode_path);
|
||||||
|
if (!desktop_open_project_attachment(this, path))
|
||||||
if (fs::exists(path)) {
|
BOOST_LOG_TRIVIAL(warning) << "open_3mf_accessory: not opening " << decode_path;
|
||||||
wxLaunchDefaultApplication(path.wstring(), 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (strCmd == "request_3mf_info") {
|
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);
|
out_buf->append(text, size);
|
||||||
}, (void*) &html_source, MD_DIALECT_GITHUB | MD_FLAG_STRIKETHROUGH | MD_FLAG_WIKILINKS, 0);
|
}, (void*) &html_source, MD_DIALECT_GITHUB | MD_FLAG_STRIKETHROUGH | MD_FLAG_WIKILINKS, 0);
|
||||||
html_source.append("</body></html>");
|
html_source.append("</body></html>");
|
||||||
m_vebview_release_note->LoadURL("file://" + (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());
|
SetMinSize(GetSize());
|
||||||
SetMaxSize(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("pellet_flow_coefficient", "printer_basic_information_advanced#pellet-modded-printer");
|
||||||
optgroup->append_single_option_line("filament_flow_ratio", "material_flow_ratio_and_pressure_advance#flow-ratio", 0);
|
optgroup->append_single_option_line("filament_flow_ratio", "material_flow_ratio_and_pressure_advance#flow-ratio", 0);
|
||||||
|
|
||||||
optgroup->append_single_option_line("enable_pressure_advance", "material_flow_ratio_and_pressure_advance#pressure-advance");
|
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");
|
optgroup->append_single_option_line("pressure_advance", "material_flow_ratio_and_pressure_advance#pressure-advance", 0);
|
||||||
|
|
||||||
// Orca: adaptive pressure advance and calibration model
|
// Orca: adaptive pressure advance and calibration model
|
||||||
optgroup->append_single_option_line("adaptive_pressure_advance", "material_flow_ratio_and_pressure_advance#enable-adaptive-pressure-advance-beta");
|
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");
|
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");
|
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.full_width = true;
|
||||||
option.opt.is_code = true;
|
option.opt.is_code = true;
|
||||||
option.opt.height = 15;
|
option.opt.height = 15;
|
||||||
@@ -4432,7 +4432,7 @@ void TabFilament::build()
|
|||||||
DynamicPrintConfig& filament_config = m_preset_bundle->filaments.get_edited_preset().config;
|
DynamicPrintConfig& filament_config = m_preset_bundle->filaments.get_edited_preset().config;
|
||||||
|
|
||||||
update_dirty();
|
update_dirty();
|
||||||
if (opt_key == "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);
|
m_config_manipulation.check_adaptive_pressure_advance_model(&filament_config);
|
||||||
|
|
||||||
on_value_change(opt_key, value);
|
on_value_change(opt_key, value);
|
||||||
@@ -4829,8 +4829,11 @@ void TabFilament::toggle_options()
|
|||||||
}
|
}
|
||||||
if (m_active_page->title() == L("Filament"))
|
if (m_active_page->title() == L("Filament"))
|
||||||
{
|
{
|
||||||
bool pa = m_config->opt_bool("enable_pressure_advance", 0);
|
const int selection = m_variant_combo ? m_variant_combo->GetSelection() : 0;
|
||||||
toggle_option("pressure_advance", pa);
|
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.
|
//Orca: Enable the plates that should be visible when multi bed support is enabled or a BBL printer is selected; otherwise, enable only the plate visible for the selected bed type.
|
||||||
DynamicConfig& proj_cfg = m_preset_bundle->project_config;
|
DynamicConfig& proj_cfg = m_preset_bundle->project_config;
|
||||||
@@ -4858,12 +4861,12 @@ void TabFilament::toggle_options()
|
|||||||
// Orca: adaptive pressure advance and calibration model
|
// Orca: adaptive pressure advance and calibration model
|
||||||
// If PA is not enabled, disable adaptive pressure advance and hide the model section
|
// If PA is not enabled, disable adaptive pressure advance and hide the model section
|
||||||
// If adaptive PA is not enabled, hide the adaptive PA model section
|
// If adaptive PA is not enabled, hide the adaptive PA model section
|
||||||
toggle_option("adaptive_pressure_advance", pa);
|
toggle_option("adaptive_pressure_advance", pa, 0);
|
||||||
toggle_option("adaptive_pressure_advance_overhangs", pa);
|
toggle_option("adaptive_pressure_advance_overhangs", pa, 0);
|
||||||
bool has_adaptive_pa = m_config->opt_bool("adaptive_pressure_advance", 0);
|
bool has_adaptive_pa = m_config->opt_bool("adaptive_pressure_advance", variant_idx);
|
||||||
toggle_line("adaptive_pressure_advance_overhangs", has_adaptive_pa && pa);
|
toggle_line("adaptive_pressure_advance_overhangs", has_adaptive_pa && pa, 0);
|
||||||
toggle_line("adaptive_pressure_advance_model", has_adaptive_pa && pa);
|
toggle_line("adaptive_pressure_advance_model", has_adaptive_pa && pa, 0);
|
||||||
toggle_line("adaptive_pressure_advance_bridges", has_adaptive_pa && pa);
|
toggle_line("adaptive_pressure_advance_bridges", has_adaptive_pa && pa, 0);
|
||||||
|
|
||||||
bool is_pellet_printer = printer_cfg.opt_bool("pellet_modded_printer");
|
bool is_pellet_printer = printer_cfg.opt_bool("pellet_modded_printer");
|
||||||
toggle_line("pellet_flow_coefficient", is_pellet_printer);
|
toggle_line("pellet_flow_coefficient", is_pellet_printer);
|
||||||
@@ -4871,8 +4874,6 @@ void TabFilament::toggle_options()
|
|||||||
|
|
||||||
toggle_line("activate_chamber_temp_control", printer_cfg.opt_bool("support_chamber_temp_control"));
|
toggle_line("activate_chamber_temp_control", printer_cfg.opt_bool("support_chamber_temp_control"));
|
||||||
|
|
||||||
const int selection = m_variant_combo ? m_variant_combo->GetSelection() : 0;
|
|
||||||
const unsigned int variant_idx = (unsigned int) std::max(selection, 0);
|
|
||||||
std::string volumetric_speed_cos = m_config->opt_string("volumetric_speed_coefficients", variant_idx);
|
std::string volumetric_speed_cos = m_config->opt_string("volumetric_speed_coefficients", variant_idx);
|
||||||
bool enable_fit = volumetric_speed_cos != "0 0 0 0 0 0";
|
bool enable_fit = volumetric_speed_cos != "0 0 0 0 0 0";
|
||||||
toggle_option("filament_adaptive_volumetric_speed", enable_fit, 256 + variant_idx);
|
toggle_option("filament_adaptive_volumetric_speed", enable_fit, 256 + variant_idx);
|
||||||
@@ -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->append_single_option_line("adaptive_bed_mesh_margin", "printer_basic_information_adaptive_bed_mesh#mesh-margin");
|
||||||
|
|
||||||
optgroup = page->new_optgroup(L("Accessory"), "param_accessory");
|
optgroup = page->new_optgroup(L("Accessory"), "param_accessory");
|
||||||
optgroup->append_single_option_line("nozzle_type", "printer_basic_information_accessory#nozzle-type", 0);
|
|
||||||
optgroup->append_single_option_line("nozzle_hrc", "printer_basic_information_accessory#nozzle-hrc");
|
optgroup->append_single_option_line("nozzle_hrc", "printer_basic_information_accessory#nozzle-hrc");
|
||||||
optgroup->append_single_option_line("auxiliary_fan", "printer_basic_information_accessory#auxiliary-part-cooling-fan");
|
optgroup->append_single_option_line("auxiliary_fan", "printer_basic_information_accessory#auxiliary-part-cooling-fan");
|
||||||
optgroup->append_single_option_line("fan_direction");
|
optgroup->append_single_option_line("fan_direction");
|
||||||
@@ -5720,6 +5720,7 @@ if (is_marlin_flavor)
|
|||||||
|
|
||||||
auto optgroup = page->new_optgroup(L("Basic information"), L"param_information", -1, true);
|
auto optgroup = page->new_optgroup(L("Basic information"), L"param_information", -1, true);
|
||||||
optgroup->append_single_option_line("nozzle_diameter", "printer_extruder_basic_information#nozzle-diameter", extruder_idx);
|
optgroup->append_single_option_line("nozzle_diameter", "printer_extruder_basic_information#nozzle-diameter", extruder_idx);
|
||||||
|
optgroup->append_single_option_line("nozzle_type", "printer_basic_information_accessory#nozzle-type", extruder_idx);
|
||||||
//optgroup->append_single_option_line("nozzle_volume_type", "", extruder_idx);
|
//optgroup->append_single_option_line("nozzle_volume_type", "", extruder_idx);
|
||||||
|
|
||||||
optgroup->append_single_option_line("nozzle_volume", "printer_extruder_basic_information#nozzle-volume", extruder_idx);
|
optgroup->append_single_option_line("nozzle_volume", "printer_extruder_basic_information#nozzle-volume", extruder_idx);
|
||||||
@@ -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)
|
optgroup->m_on_change = [this, extruder_idx](const t_config_option_key& opt_key, boost::any value)
|
||||||
{
|
{
|
||||||
bool is_SEMM = m_config->opt_bool("single_extruder_multi_material");
|
bool is_SEMM = m_config->opt_bool("single_extruder_multi_material");
|
||||||
if (is_SEMM && m_extruders_count > 1 && 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;
|
SuppressBackgroundProcessingUpdate sbpu;
|
||||||
const double new_nd = boost::any_cast<double>(value);
|
const double new_nd = boost::any_cast<double>(value);
|
||||||
|
|||||||
@@ -38,9 +38,7 @@ DownPluginFrame::DownPluginFrame(GUI_App *pGUI) : wxDialog((wxWindow *) (pGUI->m
|
|||||||
|
|
||||||
// set the frame icon
|
// set the frame icon
|
||||||
wxBoxSizer *topsizer = new wxBoxSizer(wxVERTICAL);
|
wxBoxSizer *topsizer = new wxBoxSizer(wxVERTICAL);
|
||||||
wxString TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/6/index.html").make_preferred().string());
|
wxString TargetUrl = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/guide/6/index.html");
|
||||||
|
|
||||||
TargetUrl = "file://" + TargetUrl;
|
|
||||||
|
|
||||||
// Create the webview
|
// Create the webview
|
||||||
m_browser = WebView::CreateWebView(this, TargetUrl);
|
m_browser = WebView::CreateWebView(this, TargetUrl);
|
||||||
|
|||||||
@@ -218,37 +218,38 @@ wxString GuideFrame::SetStartPage(GuidePage startpage, bool load)
|
|||||||
m_page = startpage;
|
m_page = startpage;
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(" enter, load=%1%, start_page=%2%")%load%int(startpage);
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(" enter, load=%1%, start_page=%2%")%load%int(startpage);
|
||||||
//wxLogMessage("GUIDE: webpage_1 %s", (boost::filesystem::path(resources_dir()) / "web\\guide\\1\\index.html").make_preferred().string().c_str() );
|
//wxLogMessage("GUIDE: webpage_1 %s", (boost::filesystem::path(resources_dir()) / "web\\guide\\1\\index.html").make_preferred().string().c_str() );
|
||||||
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());
|
//wxLogMessage("GUIDE: webpage_2 %s", TargetUrl.mb_str());
|
||||||
|
|
||||||
if (startpage == BBL_WELCOME){
|
if (startpage == BBL_WELCOME){
|
||||||
SetTitle(_L("Setup Wizard"));
|
SetTitle(_L("Setup Wizard"));
|
||||||
TargetUrl = 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) {
|
} else if (startpage == BBL_REGION) {
|
||||||
SetTitle(_L("Setup Wizard"));
|
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) {
|
} else if (startpage == BBL_MODELS) {
|
||||||
SetTitle(_L("Setup Wizard"));
|
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) {
|
} else if (startpage == BBL_FILAMENTS) {
|
||||||
SetTitle(_L("Setup Wizard"));
|
SetTitle(_L("Setup Wizard"));
|
||||||
|
|
||||||
int nSize = m_ProfileJson["model"].size();
|
int nSize = m_ProfileJson["model"].size();
|
||||||
|
|
||||||
if (nSize>0)
|
if (nSize>0)
|
||||||
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=22").make_preferred().string());
|
TargetUrl = guide_url + "?target=22";
|
||||||
else
|
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) {
|
} else if (startpage == BBL_FILAMENT_ONLY) {
|
||||||
SetTitle("");
|
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) {
|
} else if (startpage == BBL_MODELS_ONLY) {
|
||||||
SetTitle("");
|
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 {
|
else {
|
||||||
SetTitle(_L("Setup Wizard"));
|
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();
|
wxString strlang = wxGetApp().current_language_code_safe();
|
||||||
@@ -256,7 +257,6 @@ wxString GuideFrame::SetStartPage(GuidePage startpage, bool load)
|
|||||||
if (strlang != "")
|
if (strlang != "")
|
||||||
TargetUrl = wxString::Format("%s&lang=%s", w2s(TargetUrl), strlang);
|
TargetUrl = wxString::Format("%s&lang=%s", w2s(TargetUrl), strlang);
|
||||||
|
|
||||||
TargetUrl = "file://" + TargetUrl;
|
|
||||||
if (load)
|
if (load)
|
||||||
load_url(TargetUrl);
|
load_url(TargetUrl);
|
||||||
|
|
||||||
@@ -1284,7 +1284,7 @@ bool GuideFrame::BuildProfileJson(const PresetBundle& bundle, bool require_all_r
|
|||||||
entry["vendor"] = vp.id;
|
entry["vendor"] = vp.id;
|
||||||
entry["nozzle_diameter"] = nozzle_str;
|
entry["nozzle_diameter"] = nozzle_str;
|
||||||
entry["materials"] = materials_str;
|
entry["materials"] = materials_str;
|
||||||
entry["cover"] = cover_path.string();
|
entry["cover"] = into_u8(file_url_from_path(cover_path));
|
||||||
entry["nozzle_selected"] = "";
|
entry["nozzle_selected"] = "";
|
||||||
entry["sub_path"] = "";
|
entry["sub_path"] = "";
|
||||||
m_ProfileJson["model"].push_back(entry);
|
m_ProfileJson["model"].push_back(entry);
|
||||||
@@ -1732,7 +1732,7 @@ int GuideFrame::LoadProfileFamily(std::string strVendor, std::string strFilePath
|
|||||||
cover_path = (boost::filesystem::absolute(boost::filesystem::path(resources_dir()) / "/web/image/printer/") / cover_file)
|
cover_path = (boost::filesystem::absolute(boost::filesystem::path(resources_dir()) / "/web/image/printer/") / cover_file)
|
||||||
.make_preferred();
|
.make_preferred();
|
||||||
}
|
}
|
||||||
OneModel["cover"] = cover_path.string();
|
OneModel["cover"] = into_u8(file_url_from_path(cover_path));
|
||||||
|
|
||||||
OneModel["nozzle_selected"] = "";
|
OneModel["nozzle_selected"] = "";
|
||||||
|
|
||||||
|
|||||||
@@ -599,7 +599,7 @@ void ZUserLogin::OnScriptResponseMessage(wxCommandEvent &WXUNUSED(evt))
|
|||||||
|
|
||||||
bool ZUserLogin::ShowErrorPage()
|
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);
|
load_url(ErrortUrl);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -36,10 +36,10 @@ namespace GUI {
|
|||||||
WebViewPanel::WebViewPanel(wxWindow *parent)
|
WebViewPanel::WebViewPanel(wxWindow *parent)
|
||||||
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
|
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
|
||||||
{
|
{
|
||||||
wxString url = 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();
|
wxString strlang = wxGetApp().current_language_code_safe();
|
||||||
if (strlang != "")
|
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);
|
wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "MultiNozzleSync.hpp"
|
#include "MultiNozzleSync.hpp"
|
||||||
|
|
||||||
|
#include "../GUI.hpp"
|
||||||
#include "../GUI_App.hpp"
|
#include "../GUI_App.hpp"
|
||||||
#include "../I18N.hpp"
|
#include "../I18N.hpp"
|
||||||
#include "../Plater.hpp"
|
#include "../Plater.hpp"
|
||||||
@@ -21,8 +22,6 @@
|
|||||||
#include <set>
|
#include <set>
|
||||||
|
|
||||||
#include <wx/choice.h>
|
#include <wx/choice.h>
|
||||||
#include <wx/filename.h>
|
|
||||||
#include <wx/filesys.h>
|
|
||||||
#include <wx/sizer.h>
|
#include <wx/sizer.h>
|
||||||
#include <wx/stattext.h>
|
#include <wx/stattext.h>
|
||||||
|
|
||||||
@@ -641,8 +640,7 @@ NozzleListTable::NozzleListTable(wxWindow* parent) : wxPanel(parent,wxID_ANY,wxD
|
|||||||
m_web_view->AddScriptMessageHandler("nozzleListTable");
|
m_web_view->AddScriptMessageHandler("nozzleListTable");
|
||||||
m_web_view->EnableContextMenu(false);
|
m_web_view->EnableContextMenu(false);
|
||||||
fs::path filepath = fs::path(resources_dir()) / "web/flush/NozzleListTable.html";
|
fs::path filepath = fs::path(resources_dir()) / "web/flush/NozzleListTable.html";
|
||||||
wxFileName fn(wxString::FromUTF8(filepath.string()));
|
wxString url = file_url_from_path(filepath);
|
||||||
wxString url = wxFileSystem::FileNameToURL(fn);
|
|
||||||
m_web_view->LoadURL(url);
|
m_web_view->LoadURL(url);
|
||||||
|
|
||||||
auto sizer = new wxBoxSizer(wxVERTICAL);
|
auto sizer = new wxBoxSizer(wxVERTICAL);
|
||||||
|
|||||||
@@ -48,20 +48,19 @@ constexpr char ORCA_BRIDGE_JS[] = R"JS(
|
|||||||
|
|
||||||
wxString bootstrap_url()
|
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()
|
wxString content_base_url()
|
||||||
{
|
{
|
||||||
const std::string dir = (boost::filesystem::path(resources_dir()) / "web").make_preferred().string();
|
return file_url_from_path(boost::filesystem::path(resources_dir()) / "web") + "/";
|
||||||
return wxString("file://") + from_u8(dir) + "/";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_content_url(const wxString& url)
|
bool is_content_url(const wxString& url)
|
||||||
{
|
{
|
||||||
// The web view reports the URL it parsed, which escapes anything the resources path holds
|
// The web view reports the URL it parsed, which may escape the resources path differently
|
||||||
// (a space, a non-ASCII character), while content_base_url() is the raw path.
|
// from content_base_url().
|
||||||
return wxURI::Unescape(url.BeforeFirst('#')) == content_base_url();
|
return wxURI::Unescape(url.BeforeFirst('#')) == wxURI::Unescape(content_base_url());
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* orca_bridge_script() { return ORCA_BRIDGE_JS; }
|
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 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()) {
|
if (append_language_to_url()) {
|
||||||
const wxString lang = wxGetApp().current_language_code_safe();
|
const wxString lang = wxGetApp().current_language_code_safe();
|
||||||
@@ -202,7 +202,7 @@ wxString WebViewHostDialog::build_resource_url(const std::string& resource_path)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return wxString("file://") + target_url;
|
return target_url;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebViewHostDialog::load_url(const wxString& 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);
|
wxString filepath_str = from_path(filepath);
|
||||||
wxFileName fn(filepath_str);
|
wxFileName fn(filepath_str);
|
||||||
if(fn.FileExists()) {
|
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();
|
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__<< "File exists and load url " << url.ToStdString();
|
||||||
m_webview->LoadURL(url);
|
m_webview->LoadURL(url);
|
||||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__<< "Successfully loaded url: " << url.ToStdString();
|
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__<< "Successfully loaded url: " << url.ToStdString();
|
||||||
|
|||||||
@@ -340,9 +340,7 @@ namespace Slic3r {
|
|||||||
if (classify_printer_model(config->opt_string("printer_model")) != ElegooPrinterType::CC2)
|
if (classify_printer_model(config->opt_string("printer_model")) != ElegooPrinterType::CC2)
|
||||||
return fallback_webui;
|
return fallback_webui;
|
||||||
|
|
||||||
std::string web_path = resources_dir() + "/web/elegoolink/lan_service_web/index.html";
|
std::string web_path = GUI::into_u8(GUI::file_url_from_path(boost::filesystem::path(resources_dir()) / "web/elegoolink/lan_service_web/index.html"));
|
||||||
std::replace(web_path.begin(), web_path.end(), '\\', '/');
|
|
||||||
web_path = "file://" + web_path;
|
|
||||||
const std::string token = get_cc2_token(config->opt_string("printhost_apikey"));
|
const std::string token = get_cc2_token(config->opt_string("printhost_apikey"));
|
||||||
const std::string host_ip = Http::get_host_header_value(host);
|
const std::string host_ip = Http::get_host_header_value(host);
|
||||||
|
|
||||||
|
|||||||
@@ -339,62 +339,8 @@ bool PresetUpdater::priv::get_file(const std::string &url, const fs::path &targe
|
|||||||
//BBS: refine preset update logic
|
//BBS: refine preset update logic
|
||||||
bool PresetUpdater::priv::extract_file(const fs::path &source_path, const fs::path &dest_path)
|
bool PresetUpdater::priv::extract_file(const fs::path &source_path, const fs::path &dest_path)
|
||||||
{
|
{
|
||||||
bool res = true;
|
const std::string parent_path = (!dest_path.empty() ? dest_path : source_path.parent_path()).string();
|
||||||
std::string file_path = source_path.string();
|
return extract_archive_confined(source_path.string(), parent_path);
|
||||||
std::string parent_path = (!dest_path.empty() ? dest_path : source_path.parent_path()).string();
|
|
||||||
mz_zip_archive archive;
|
|
||||||
mz_zip_zero_struct(&archive);
|
|
||||||
|
|
||||||
if (!open_zip_reader(&archive, file_path))
|
|
||||||
{
|
|
||||||
BOOST_LOG_TRIVIAL(error) << "Unable to open zip reader for "<<file_path;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
mz_uint num_entries = mz_zip_reader_get_num_files(&archive);
|
|
||||||
|
|
||||||
mz_zip_archive_file_stat stat;
|
|
||||||
// we first loop the entries to read from the archive the .amf file only, in order to extract the version from it
|
|
||||||
for (mz_uint i = 0; i < num_entries; ++i)
|
|
||||||
{
|
|
||||||
if (mz_zip_reader_file_stat(&archive, i, &stat))
|
|
||||||
{
|
|
||||||
std::string dest_file = parent_path+"/"+stat.m_filename;
|
|
||||||
if (stat.m_is_directory) {
|
|
||||||
fs::path dest_path(dest_file);
|
|
||||||
if (!fs::exists(dest_path))
|
|
||||||
fs::create_directories(dest_path);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else if (stat.m_uncomp_size == 0) {
|
|
||||||
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater]Unzip: invalid size for file "<<stat.m_filename;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
res = mz_zip_reader_extract_to_file(&archive, stat.m_file_index, dest_file.c_str(), 0);
|
|
||||||
if (!res) {
|
|
||||||
BOOST_LOG_TRIVIAL(error) << "[Orca Updater]extract file "<<stat.m_filename<<" to dest "<<dest_file<<" failed";
|
|
||||||
close_zip_reader(&archive);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]successfully extract file " << stat.m_file_index << " to "<<dest_file;
|
|
||||||
}
|
|
||||||
catch (const std::exception& e)
|
|
||||||
{
|
|
||||||
// ensure the zip archive is closed and rethrow the exception
|
|
||||||
close_zip_reader(&archive);
|
|
||||||
BOOST_LOG_TRIVIAL(error) << "[Orca Updater]Archive read exception:"<<e.what();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater]Unzip: read file stat failed";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close_zip_reader(&archive);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove a leftover partial archive for the vendor about to be synchronized.
|
// Remove a leftover partial archive for the vendor about to be synchronized.
|
||||||
|
|||||||
@@ -768,3 +768,128 @@ TEST_CASE("Each filament prints with its variant of the extruder's variant strin
|
|||||||
CHECK(gcode.find("; nozzle_temperature = " + resolved + "\n") != std::string::npos);
|
CHECK(gcode.find("; nozzle_temperature = " + resolved + "\n") != std::string::npos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// An adaptive pressure advance model predicting the same pressure advance at every flow and acceleration.
|
||||||
|
static std::string constant_pressure_advance_model(const std::string &pa)
|
||||||
|
{
|
||||||
|
return pa + ",1,1000\n" + pa + ",500,1000\n" + pa + ",1,100000\n" + pa + ",500,100000";
|
||||||
|
}
|
||||||
|
|
||||||
|
// The pressure advance values a Klipper G-code sets.
|
||||||
|
static std::set<std::string> pressure_advance_values(const std::string &gcode)
|
||||||
|
{
|
||||||
|
const std::string token = "SET_PRESSURE_ADVANCE ADVANCE=";
|
||||||
|
std::set<std::string> values;
|
||||||
|
std::istringstream stream(gcode);
|
||||||
|
for (std::string line; std::getline(stream, line);)
|
||||||
|
if (line.rfind(token, 0) == 0)
|
||||||
|
values.insert(line.substr(token.size(), line.find(';') - token.size()));
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pressure advance, and the adaptive pressure advance model, are tuned per extruder variant like the
|
||||||
|
// other filament variant settings.
|
||||||
|
TEST_CASE("Each filament sets the pressure advance of its extruder variant", "[MultiFilament]")
|
||||||
|
{
|
||||||
|
auto [nozzle_volume_type, filament, pressure_advance, adaptive_pressure_advance] = GENERATE(table<NozzleVolumeType, int, std::string, std::string>({
|
||||||
|
{ nvtStandard, 1, "0.021", "0.012" },
|
||||||
|
{ nvtHighFlow, 1, "0.037", "0.034" },
|
||||||
|
{ nvtHighFlow, 2, "0.043", "0.056" }, // filament 2 defines no High Flow variant
|
||||||
|
}));
|
||||||
|
const bool adaptive = GENERATE(false, true);
|
||||||
|
DYNAMIC_SECTION(get_nozzle_volume_type_string(nozzle_volume_type) << " nozzle, filament " << filament << (adaptive ? ", adaptive" : "")) {
|
||||||
|
DynamicPrintConfig config = multifilament_config(2, {
|
||||||
|
{ "gcode_flavor", "klipper" },
|
||||||
|
{ "extruder_variant_list", "Direct Drive Standard,Direct Drive High Flow" },
|
||||||
|
// filament 1 defines Standard (0.021) and High Flow (0.037), filament 2 Standard (0.043)
|
||||||
|
{ "filament_extruder_variant", "Direct Drive Standard;Direct Drive High Flow;Direct Drive Standard" },
|
||||||
|
{ "filament_self_index", "1,1,2" },
|
||||||
|
{ "enable_pressure_advance", "1,1,1" },
|
||||||
|
{ "pressure_advance", "0.021,0.037,0.043" },
|
||||||
|
{ "adaptive_pressure_advance", adaptive ? "1,1,1" : "0,0,0" },
|
||||||
|
{ "sparse_infill_filament_id", filament },
|
||||||
|
{ "internal_solid_filament_id", filament },
|
||||||
|
{ "top_surface_filament_id", filament },
|
||||||
|
{ "bottom_surface_filament_id", filament },
|
||||||
|
{ "outer_wall_filament_id", filament },
|
||||||
|
{ "inner_wall_filament_id", filament },
|
||||||
|
{ "enable_prime_tower", 0 },
|
||||||
|
{ "skirt_loops", 0 },
|
||||||
|
{ "brim_type", "no_brim" },
|
||||||
|
// custom G-code indexes the per-filament arrays by filament
|
||||||
|
{ "machine_start_gcode", "; start pressure advance {pressure_advance[initial_extruder]}" },
|
||||||
|
});
|
||||||
|
config.option<ConfigOptionStrings>("adaptive_pressure_advance_model")->values = {
|
||||||
|
constant_pressure_advance_model("0.012"), constant_pressure_advance_model("0.034"), constant_pressure_advance_model("0.056") };
|
||||||
|
config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type", true)->values = { nozzle_volume_type };
|
||||||
|
const std::string gcode = slice({ cube(20) }, config);
|
||||||
|
|
||||||
|
// The toolchange sets the variant's pressure advance; with adaptive pressure advance, the
|
||||||
|
// prediction of the variant's model then replaces it.
|
||||||
|
std::set<std::string> expected{ pressure_advance };
|
||||||
|
if (adaptive)
|
||||||
|
expected.insert(adaptive_pressure_advance);
|
||||||
|
CHECK(pressure_advance_values(gcode) == expected);
|
||||||
|
CHECK(gcode.find("; start pressure advance " + pressure_advance + "\n") != std::string::npos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// On a printer with two extruders, a filament takes the pressure advance of the variant of the extruder
|
||||||
|
// it is mapped to, whichever filament and extruder that is.
|
||||||
|
TEST_CASE("Each filament sets the pressure advance of its extruder variant on a two-extruder printer", "[MultiFilament]")
|
||||||
|
{
|
||||||
|
auto [filament, extruder, pressure_advance, adaptive_pressure_advance] = GENERATE(table<int, int, std::string, std::string>({
|
||||||
|
{ 1, 1, "0.021", "0.012" },
|
||||||
|
{ 1, 2, "0.037", "0.034" },
|
||||||
|
{ 2, 1, "0.043", "0.056" },
|
||||||
|
{ 2, 2, "0.049", "0.078" },
|
||||||
|
}));
|
||||||
|
const bool adaptive = GENERATE(false, true);
|
||||||
|
DYNAMIC_SECTION("filament " << filament << " on extruder " << extruder << (adaptive ? ", adaptive" : "")) {
|
||||||
|
// the other filament goes on the other extruder
|
||||||
|
const std::string filament_map = filament == 1 ? std::to_string(extruder) + "," + std::to_string(3 - extruder) :
|
||||||
|
std::to_string(3 - extruder) + "," + std::to_string(extruder);
|
||||||
|
DynamicPrintConfig config = multifilament_config(2, {
|
||||||
|
{ "gcode_flavor", "klipper" },
|
||||||
|
{ "single_extruder_multi_material", 0 },
|
||||||
|
{ "nozzle_diameter", "0.4,0.4" },
|
||||||
|
{ "extruder_printable_height", "0,0" },
|
||||||
|
// extruder 1 has a Standard nozzle, extruder 2 a High Flow one
|
||||||
|
{ "printer_extruder_id", "1,2" },
|
||||||
|
{ "printer_extruder_variant", "Direct Drive Standard;Direct Drive High Flow" },
|
||||||
|
{ "extruder_variant_list", "Direct Drive Standard;Direct Drive High Flow" },
|
||||||
|
{ "filament_map", filament_map },
|
||||||
|
// both filaments define Standard and High Flow
|
||||||
|
{ "filament_extruder_variant", "Direct Drive Standard;Direct Drive High Flow;Direct Drive Standard;Direct Drive High Flow" },
|
||||||
|
{ "filament_self_index", "1,1,2,2" },
|
||||||
|
{ "enable_pressure_advance", "1,1,1,1" },
|
||||||
|
{ "pressure_advance", "0.021,0.037,0.043,0.049" },
|
||||||
|
{ "adaptive_pressure_advance", adaptive ? "1,1,1,1" : "0,0,0,0" },
|
||||||
|
{ "sparse_infill_filament_id", filament },
|
||||||
|
{ "internal_solid_filament_id", filament },
|
||||||
|
{ "top_surface_filament_id", filament },
|
||||||
|
{ "bottom_surface_filament_id", filament },
|
||||||
|
{ "outer_wall_filament_id", filament },
|
||||||
|
{ "inner_wall_filament_id", filament },
|
||||||
|
{ "enable_prime_tower", 0 },
|
||||||
|
{ "skirt_loops", 0 },
|
||||||
|
{ "brim_type", "no_brim" },
|
||||||
|
// custom G-code indexes the per-filament arrays by filament
|
||||||
|
{ "machine_start_gcode", "; start pressure advance {pressure_advance[initial_extruder]}" },
|
||||||
|
});
|
||||||
|
config.option<ConfigOptionStrings>("adaptive_pressure_advance_model")->values = {
|
||||||
|
constant_pressure_advance_model("0.012"), constant_pressure_advance_model("0.034"),
|
||||||
|
constant_pressure_advance_model("0.056"), constant_pressure_advance_model("0.078") };
|
||||||
|
config.option<ConfigOptionEnumsGeneric>("extruder_type", true)->values = { etDirectDrive, etDirectDrive };
|
||||||
|
config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type", true)->values = { nvtStandard, nvtHighFlow };
|
||||||
|
// keep the mapping above rather than grouping the filaments automatically
|
||||||
|
config.option<ConfigOptionEnum<FilamentMapMode>>("filament_map_mode", true)->value = fmmManual;
|
||||||
|
const std::string gcode = slice({ cube(20) }, config);
|
||||||
|
|
||||||
|
std::set<std::string> expected{ pressure_advance };
|
||||||
|
if (adaptive)
|
||||||
|
expected.insert(adaptive_pressure_advance);
|
||||||
|
CHECK(pressure_advance_values(gcode) == expected);
|
||||||
|
CHECK(gcode.find("; start pressure advance " + pressure_advance + "\n") != std::string::npos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ add_executable(${_TEST_NAME}_tests
|
|||||||
test_lay_on_face.cpp
|
test_lay_on_face.cpp
|
||||||
test_model.cpp
|
test_model.cpp
|
||||||
test_utils.cpp
|
test_utils.cpp
|
||||||
|
test_miniz_extension.cpp
|
||||||
test_timeutils.cpp
|
test_timeutils.cpp
|
||||||
test_voronoi.cpp
|
test_voronoi.cpp
|
||||||
test_wipe_tower_estimate.cpp
|
test_wipe_tower_estimate.cpp
|
||||||
|
|||||||
@@ -717,3 +717,15 @@ TEST_CASE("get_index_for_extruder scales the variant column by the requested str
|
|||||||
REQUIRE(col0_stride2 == 0);
|
REQUIRE(col0_stride2 == 0);
|
||||||
REQUIRE(col1_stride2 == 2);
|
REQUIRE(col1_stride2 == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A per-variant filament option read with a single value gives it to every filament variant. A project
|
||||||
|
// exported by an older CLI holds a single value for an option no loaded preset defined, such as
|
||||||
|
// filament_ironing_flow.
|
||||||
|
TEST_CASE("A per-variant filament option read with a single value gives it to every filament variant", "[Config]")
|
||||||
|
{
|
||||||
|
// filament 1 defines Standard and High Flow, filament 2 Standard
|
||||||
|
DynamicPrintConfig config;
|
||||||
|
config.option<ConfigOptionInts>("filament_self_index", true)->values = {1, 1, 2};
|
||||||
|
config.load_from_ini_string("pressure_advance = 0.021", ForwardCompatibilitySubstitutionRule::Disable);
|
||||||
|
REQUIRE(config.option<ConfigOptionFloats>("pressure_advance")->values == std::vector<double>({0.021, 0.021, 0.021}));
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,194 @@
|
|||||||
|
#include <catch2/catch_all.hpp>
|
||||||
|
|
||||||
|
#include "libslic3r/miniz_extension.hpp"
|
||||||
|
|
||||||
|
#include "test_utils.hpp"
|
||||||
|
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iterator>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using namespace Slic3r;
|
||||||
|
namespace fs = boost::filesystem;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
void write_zip(const fs::path &zip_file, const std::vector<std::pair<std::string, std::string>> &entries)
|
||||||
|
{
|
||||||
|
mz_zip_archive zip;
|
||||||
|
mz_zip_zero_struct(&zip);
|
||||||
|
REQUIRE(open_zip_writer(&zip, zip_file.string()));
|
||||||
|
for (const auto &[name, content] : entries)
|
||||||
|
REQUIRE(mz_zip_writer_add_mem(&zip, name.c_str(), content.data(), content.size(), MZ_DEFAULT_COMPRESSION));
|
||||||
|
REQUIRE(mz_zip_writer_finalize_archive(&zip));
|
||||||
|
REQUIRE(close_zip_writer(&zip));
|
||||||
|
}
|
||||||
|
|
||||||
|
// miniz refuses to write a name starting with '/', so write a placeholder of the same length and patch it in place.
|
||||||
|
void rename_entry(const fs::path &zip_file, const std::string &from, const std::string &to)
|
||||||
|
{
|
||||||
|
REQUIRE(from.size() == to.size());
|
||||||
|
std::string bytes;
|
||||||
|
{
|
||||||
|
std::ifstream in(zip_file.string(), std::ios::binary);
|
||||||
|
bytes.assign(std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>());
|
||||||
|
}
|
||||||
|
size_t count = 0;
|
||||||
|
for (size_t pos = bytes.find(from); pos != std::string::npos; pos = bytes.find(from, pos + to.size()), ++count)
|
||||||
|
bytes.replace(pos, from.size(), to);
|
||||||
|
// Once in the local header and once in the central directory.
|
||||||
|
REQUIRE(count == 2);
|
||||||
|
std::ofstream out(zip_file.string(), std::ios::binary | std::ios::trunc);
|
||||||
|
out << bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> list_dir(const fs::path &dir)
|
||||||
|
{
|
||||||
|
std::vector<std::string> names;
|
||||||
|
for (const fs::directory_entry &entry : fs::directory_iterator(dir))
|
||||||
|
names.push_back(entry.path().filename().string());
|
||||||
|
std::sort(names.begin(), names.end());
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string read_file(const fs::path &file)
|
||||||
|
{
|
||||||
|
std::ifstream in(file.string(), std::ios::binary);
|
||||||
|
return std::string(std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("Confined extraction writes a well-formed archive under the target directory", "[MinizExtension]")
|
||||||
|
{
|
||||||
|
ScopedTemporaryDir tmp;
|
||||||
|
const fs::path zip_file = tmp.path() / "bundle.zip";
|
||||||
|
const fs::path target = tmp.path() / "cache";
|
||||||
|
fs::create_directories(target);
|
||||||
|
write_zip(zip_file, {{"vendor/", ""}, {"vendor/machine/", ""}, {"vendor.json", "{\"a\":1}"}, {"vendor/machine/printer.json", "{\"b\":2}"}});
|
||||||
|
|
||||||
|
REQUIRE(extract_archive_confined(zip_file.string(), target.string()));
|
||||||
|
CHECK(fs::is_directory(target / "vendor"));
|
||||||
|
CHECK(read_file(target / "vendor.json") == "{\"a\":1}");
|
||||||
|
CHECK(read_file(target / "vendor" / "machine" / "printer.json") == "{\"b\":2}");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Confined extraction rejects an archive with an entry outside the target directory", "[MinizExtension]")
|
||||||
|
{
|
||||||
|
ScopedTemporaryDir tmp;
|
||||||
|
const fs::path zip_file = tmp.path() / "bundle.zip";
|
||||||
|
const fs::path target = tmp.path() / "cache";
|
||||||
|
fs::create_directories(target);
|
||||||
|
|
||||||
|
const std::string escaping_entry = GENERATE(std::string("../escape.txt"), std::string("..\\escape.txt"),
|
||||||
|
std::string("sub/../../escape.txt"), std::string("C:/escape.txt"),
|
||||||
|
std::string("C:escape.txt"), std::string("\\escape.txt"));
|
||||||
|
// The normal entry comes first so a per-entry check would already have written it.
|
||||||
|
write_zip(zip_file, {{"normal.json", "{}"}, {escaping_entry, "escaped"}});
|
||||||
|
|
||||||
|
CAPTURE(escaping_entry);
|
||||||
|
CHECK_FALSE(extract_archive_confined(zip_file.string(), target.string()));
|
||||||
|
CHECK_FALSE(fs::exists(tmp.path() / "escape.txt"));
|
||||||
|
CHECK(fs::is_empty(target));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Confined extraction rejects an archive with an absolute entry name", "[MinizExtension]")
|
||||||
|
{
|
||||||
|
ScopedTemporaryDir tmp;
|
||||||
|
const fs::path zip_file = tmp.path() / "bundle.zip";
|
||||||
|
const fs::path target = tmp.path() / "cache";
|
||||||
|
fs::create_directories(target);
|
||||||
|
|
||||||
|
const std::string absolute = (tmp.path() / "escape.txt").generic_string();
|
||||||
|
const std::string placeholder = "#" + absolute.substr(1);
|
||||||
|
write_zip(zip_file, {{"normal.json", "{}"}, {placeholder, "escaped"}});
|
||||||
|
rename_entry(zip_file, placeholder, absolute);
|
||||||
|
|
||||||
|
CHECK_FALSE(extract_archive_confined(zip_file.string(), target.string()));
|
||||||
|
CHECK_FALSE(fs::exists(tmp.path() / "escape.txt"));
|
||||||
|
CHECK(fs::is_empty(target));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Confined extraction rejects a directory entry outside the target directory", "[MinizExtension]")
|
||||||
|
{
|
||||||
|
ScopedTemporaryDir tmp;
|
||||||
|
const fs::path zip_file = tmp.path() / "bundle.zip";
|
||||||
|
const fs::path target = tmp.path() / "cache";
|
||||||
|
fs::create_directories(target);
|
||||||
|
write_zip(zip_file, {{"vendor/", ""}, {"../outside/", ""}});
|
||||||
|
|
||||||
|
CHECK_FALSE(extract_archive_confined(zip_file.string(), target.string()));
|
||||||
|
CHECK_FALSE(fs::exists(tmp.path() / "outside"));
|
||||||
|
CHECK(fs::is_empty(target));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Confined extraction validates zero-size entries like any other", "[MinizExtension]")
|
||||||
|
{
|
||||||
|
ScopedTemporaryDir tmp;
|
||||||
|
const fs::path zip_file = tmp.path() / "bundle.zip";
|
||||||
|
const fs::path target = tmp.path() / "cache";
|
||||||
|
fs::create_directories(target);
|
||||||
|
|
||||||
|
SECTION("an empty file inside the target does not fail the archive") {
|
||||||
|
write_zip(zip_file, {{"empty.json", ""}, {"vendor.json", "{}"}});
|
||||||
|
CHECK(extract_archive_confined(zip_file.string(), target.string()));
|
||||||
|
CHECK(read_file(target / "vendor.json") == "{}");
|
||||||
|
}
|
||||||
|
SECTION("an empty file outside the target rejects the archive") {
|
||||||
|
write_zip(zip_file, {{"vendor.json", "{}"}, {"../escape.txt", ""}});
|
||||||
|
CHECK_FALSE(extract_archive_confined(zip_file.string(), target.string()));
|
||||||
|
CHECK_FALSE(fs::exists(tmp.path() / "escape.txt"));
|
||||||
|
CHECK(fs::is_empty(target));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Confined extraction writes nothing outside the target for Windows-specific name forms", "[MinizExtension]")
|
||||||
|
{
|
||||||
|
ScopedTemporaryDir tmp;
|
||||||
|
const fs::path zip_file = tmp.path() / "bundle.zip";
|
||||||
|
const fs::path target = tmp.path() / "cache";
|
||||||
|
fs::create_directories(target);
|
||||||
|
|
||||||
|
// Windows strips trailing dots and spaces and maps device names; whether these extract depends on the
|
||||||
|
// platform, but none of them may land beside the target.
|
||||||
|
const std::string name = GENERATE(std::string("name."), std::string("name "), std::string("..."), std::string(".. "),
|
||||||
|
std::string(".. /escape.txt"), std::string(".../escape.txt"), std::string("CON"),
|
||||||
|
std::string("sub/NUL.txt"), std::string("C:escape.txt"));
|
||||||
|
write_zip(zip_file, {{name, "payload"}});
|
||||||
|
|
||||||
|
CAPTURE(name);
|
||||||
|
extract_archive_confined(zip_file.string(), target.string());
|
||||||
|
CHECK(list_dir(tmp.path()) == std::vector<std::string>{"bundle.zip", "cache"});
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
TEST_CASE("Confined extraction replaces a symlink at the destination instead of writing through it", "[MinizExtension]")
|
||||||
|
{
|
||||||
|
ScopedTemporaryDir tmp;
|
||||||
|
const fs::path zip_file = tmp.path() / "bundle.zip";
|
||||||
|
const fs::path target = tmp.path() / "cache";
|
||||||
|
const fs::path outside = tmp.path() / "outside";
|
||||||
|
fs::create_directories(target);
|
||||||
|
fs::create_directories(outside);
|
||||||
|
write_zip(zip_file, {{"vendor.json", "{\"a\":1}"}});
|
||||||
|
|
||||||
|
SECTION("a dangling symlink") {
|
||||||
|
fs::create_symlink(outside / "vendor.json", target / "vendor.json");
|
||||||
|
CHECK(extract_archive_confined(zip_file.string(), target.string()));
|
||||||
|
CHECK_FALSE(fs::exists(outside / "vendor.json"));
|
||||||
|
CHECK_FALSE(fs::is_symlink(fs::symlink_status(target / "vendor.json")));
|
||||||
|
CHECK(read_file(target / "vendor.json") == "{\"a\":1}");
|
||||||
|
}
|
||||||
|
SECTION("a symlink to an existing file") {
|
||||||
|
{ std::ofstream((outside / "vendor.json").string()) << "original"; }
|
||||||
|
fs::create_symlink(outside / "vendor.json", target / "vendor.json");
|
||||||
|
extract_archive_confined(zip_file.string(), target.string());
|
||||||
|
CHECK(read_file(outside / "vendor.json") == "original");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -5482,3 +5482,39 @@ TEST_CASE("Config import confines zip entries, preset names and bundle ids to th
|
|||||||
CHECK_FALSE(any_filename_contains(temp_dir.path(), "bundle-escape"));
|
CHECK_FALSE(any_filename_contains(temp_dir.path(), "bundle-escape"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A project saved before a key joined filament_options_with_variant stores it once per filament,
|
||||||
|
// while the keys that were already per variant store it once per filament variant. Loading such a
|
||||||
|
// project gives every variant of a filament that filament's value.
|
||||||
|
TEST_CASE("A project saved with pressure advance per filament applies it to every variant of the filament", "[Preset][Bundle]")
|
||||||
|
{
|
||||||
|
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||||
|
config.opt<ConfigOptionStrings>("filament_colour")->values = { "#FF0000", "#00FF00" };
|
||||||
|
config.opt<ConfigOptionFloats>("filament_diameter")->values = { 1.75, 1.75 };
|
||||||
|
config.option<ConfigOptionStrings>("filament_settings_id", true)->values = { "Project PLA", "Project PETG" };
|
||||||
|
// A multi-variant printer: full_print_config() leaves the list out, and the loader splits the
|
||||||
|
// variant keys per filament only when the project carries it.
|
||||||
|
config.option<ConfigOptionStrings>("extruder_variant_list", true)->values = { "Direct Drive Standard,Direct Drive High Flow" };
|
||||||
|
// filament 1 defines Standard and High Flow, filament 2 Standard
|
||||||
|
config.opt<ConfigOptionStrings>("filament_extruder_variant")->values = { "Direct Drive Standard", "Direct Drive High Flow", "Direct Drive Standard" };
|
||||||
|
config.opt<ConfigOptionInts>("filament_self_index")->values = { 1, 1, 2 };
|
||||||
|
config.opt<ConfigOptionFloatsNullable>("filament_flow_ratio")->values = { 0.95, 0.96, 0.97 }; // one per filament variant
|
||||||
|
// One per filament, read through the loader that project files go through.
|
||||||
|
config.load_from_ini_string("pressure_advance = 0.021,0.043", ForwardCompatibilitySubstitutionRule::Disable);
|
||||||
|
// The CLI slices the config as loaded.
|
||||||
|
check_double_vector(config.opt<ConfigOptionFloats>("pressure_advance")->values, { 0.021, 0.021, 0.043 });
|
||||||
|
check_double_vector(config.opt<ConfigOptionFloatsNullable>("filament_flow_ratio")->values, { 0.95, 0.96, 0.97 });
|
||||||
|
// The GUI normalizes the config before load; mirror that so only the production path runs.
|
||||||
|
Preset::normalize(config);
|
||||||
|
|
||||||
|
PresetBundle bundle;
|
||||||
|
bundle.load_config_model("test.3mf", std::move(config));
|
||||||
|
|
||||||
|
REQUIRE(bundle.filament_presets.size() == 2);
|
||||||
|
const DynamicPrintConfig &pla = bundle.filaments.find_preset(bundle.filament_presets[0], false, true)->config;
|
||||||
|
const DynamicPrintConfig &petg = bundle.filaments.find_preset(bundle.filament_presets[1], false, true)->config;
|
||||||
|
check_double_vector(pla.opt<ConfigOptionFloats>("pressure_advance")->values, { 0.021, 0.021 });
|
||||||
|
check_double_vector(petg.opt<ConfigOptionFloats>("pressure_advance")->values, { 0.043 });
|
||||||
|
check_double_vector(pla.opt<ConfigOptionFloatsNullable>("filament_flow_ratio")->values, { 0.95, 0.96 });
|
||||||
|
check_double_vector(petg.opt<ConfigOptionFloatsNullable>("filament_flow_ratio")->values, { 0.97 });
|
||||||
|
}
|
||||||
|
|||||||
@@ -243,3 +243,147 @@ TEST_CASE("find_unused_filename gives up after 999 versions", "[Utils]") {
|
|||||||
REQUIRE_FALSE(find_unused_filename(dir.path(), "model.3mf", {}, name));
|
REQUIRE_FALSE(find_unused_filename(dir.path(), "model.3mf", {}, name));
|
||||||
CHECK(name == "model(999).3mf");
|
CHECK(name == "model(999).3mf");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("is_path_within_root accepts a root given with a trailing separator", "[utils]") {
|
||||||
|
ScopedTemporaryDir tmp;
|
||||||
|
const std::string root = tmp.path().string();
|
||||||
|
const std::string with_separator = GENERATE_COPY(root + "/", root + std::string(1, static_cast<char>(boost::filesystem::path::preferred_separator)));
|
||||||
|
|
||||||
|
CAPTURE(with_separator);
|
||||||
|
CHECK(is_path_within_root("vendor.json", with_separator));
|
||||||
|
CHECK(is_path_within_root("vendor/machine/printer.json", with_separator));
|
||||||
|
CHECK_FALSE(is_path_within_root("../vendor.json", with_separator));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("is_path_within_root treats Windows-specific name forms the same on every platform", "[utils]") {
|
||||||
|
ScopedTemporaryDir tmp;
|
||||||
|
|
||||||
|
SECTION("names ending in dots or spaces stay inside the root") {
|
||||||
|
const std::string name = GENERATE(std::string("name."), std::string("name "), std::string("dir./file.json"), std::string("dir /file.json"));
|
||||||
|
CAPTURE(name);
|
||||||
|
CHECK(is_path_within_root(name, tmp.path()));
|
||||||
|
}
|
||||||
|
SECTION("drive-relative names are rejected") {
|
||||||
|
const std::string name = GENERATE(std::string("C:x"), std::string("c:x/y.json"), std::string("C:"));
|
||||||
|
CAPTURE(name);
|
||||||
|
CHECK_FALSE(is_path_within_root(name, tmp.path()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("is_path_within_root rejects a name with an embedded NUL", "[utils]") {
|
||||||
|
ScopedTemporaryDir tmp;
|
||||||
|
// The filesystem calls stop at the NUL, so they would act on a different path than the one checked.
|
||||||
|
const std::string name = GENERATE(std::string("..\0", 3), std::string("..\0x/file.json", 14), std::string("sub/..\0x", 8),
|
||||||
|
std::string("file.json\0", 10), std::string("\0file.json", 10));
|
||||||
|
CAPTURE(name.size());
|
||||||
|
CHECK_FALSE(is_path_within_root(name, tmp.path()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("is_symlink_target_within_root accepts relative targets that stay inside the root", "[utils]") {
|
||||||
|
ScopedTemporaryDir tmp;
|
||||||
|
const auto [link, target] = GENERATE(std::make_pair(std::string("Versions/Current"), std::string("A")),
|
||||||
|
std::make_pair(std::string("Foo.framework/Foo"), std::string("Versions/Current/Foo")),
|
||||||
|
std::make_pair(std::string("libfoo.so"), std::string("libfoo.so.1")),
|
||||||
|
std::make_pair(std::string("a/b/link"), std::string("c/d")));
|
||||||
|
CAPTURE(link, target);
|
||||||
|
CHECK(is_symlink_target_within_root(link, target, tmp.path()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("is_symlink_target_within_root rejects absolute targets and targets that climb out", "[utils]") {
|
||||||
|
ScopedTemporaryDir tmp;
|
||||||
|
const std::string outside = (tmp.path().parent_path() / "outside").generic_string();
|
||||||
|
const auto [link, target] = GENERATE_COPY(std::make_pair(std::string("sub/link"), outside),
|
||||||
|
std::make_pair(std::string("sub/link"), std::string("/etc/passwd")),
|
||||||
|
std::make_pair(std::string("sub/link"), std::string("\\outside")),
|
||||||
|
std::make_pair(std::string("sub/link"), std::string("C:/outside")),
|
||||||
|
std::make_pair(std::string("sub/link"), std::string("C:outside")),
|
||||||
|
std::make_pair(std::string("sub/link"), std::string("")),
|
||||||
|
std::make_pair(std::string("link"), std::string("..")),
|
||||||
|
std::make_pair(std::string("link"), std::string("../outside")),
|
||||||
|
std::make_pair(std::string("sub/link"), std::string("../../outside")),
|
||||||
|
std::make_pair(std::string("sub/link"), std::string("x/../../../outside")),
|
||||||
|
std::make_pair(std::string("sub/link"), std::string("..\\..\\outside")),
|
||||||
|
// symlink() stops at the NUL, so this target would be created as "..".
|
||||||
|
std::make_pair(std::string("link"), std::string("..\0", 3)));
|
||||||
|
CAPTURE(link, target);
|
||||||
|
CHECK_FALSE(is_symlink_target_within_root(link, target, tmp.path()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
TEST_CASE("is_symlink_target_within_root rejects a target that passes through a symlink leading out", "[utils]") {
|
||||||
|
ScopedTemporaryDir tmp;
|
||||||
|
const boost::filesystem::path root = tmp.path() / "root";
|
||||||
|
const boost::filesystem::path outside = tmp.path() / "outside";
|
||||||
|
boost::filesystem::create_directories(root);
|
||||||
|
boost::filesystem::create_directories(outside);
|
||||||
|
boost::filesystem::create_symlink(outside, root / "out");
|
||||||
|
|
||||||
|
CHECK_FALSE(is_symlink_target_within_root("link", "out/lib.so", root));
|
||||||
|
CHECK(is_symlink_target_within_root("link", "in/lib.so", root));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
TEST_CASE("is_absolute_path_within_root accepts only entries inside the root", "[utils]") {
|
||||||
|
namespace fs = boost::filesystem;
|
||||||
|
ScopedTemporaryDir outer;
|
||||||
|
const fs::path root = outer.path() / "Auxiliaries";
|
||||||
|
fs::create_directories(root / "Others");
|
||||||
|
const fs::path inside = root / "Others" / "note.txt";
|
||||||
|
const fs::path outside = outer.path() / "secret.txt";
|
||||||
|
std::ofstream(inside.string()) << "inside";
|
||||||
|
std::ofstream(outside.string()) << "outside";
|
||||||
|
|
||||||
|
SECTION("a file inside the root") {
|
||||||
|
REQUIRE(is_absolute_path_within_root(inside, root));
|
||||||
|
}
|
||||||
|
SECTION("a path inside the root whose file does not exist yet") {
|
||||||
|
REQUIRE(is_absolute_path_within_root(root / "Others" / "missing.txt", root));
|
||||||
|
}
|
||||||
|
SECTION("the root itself") {
|
||||||
|
REQUIRE_FALSE(is_absolute_path_within_root(root, root));
|
||||||
|
}
|
||||||
|
SECTION("a parent-directory escape spelled under the root") {
|
||||||
|
REQUIRE_FALSE(is_absolute_path_within_root(root / "Others" / ".." / ".." / "secret.txt", root));
|
||||||
|
}
|
||||||
|
SECTION("an absolute path elsewhere") {
|
||||||
|
REQUIRE_FALSE(is_absolute_path_within_root(outside, root));
|
||||||
|
}
|
||||||
|
SECTION("a sibling directory sharing the root's name as a prefix") {
|
||||||
|
const fs::path sibling = outer.path() / "Auxiliaries2" / "note.txt";
|
||||||
|
REQUIRE_FALSE(is_absolute_path_within_root(sibling, root));
|
||||||
|
}
|
||||||
|
SECTION("a relative path") {
|
||||||
|
REQUIRE_FALSE(is_absolute_path_within_root(fs::path("Others") / "note.txt", root));
|
||||||
|
}
|
||||||
|
SECTION("an empty path") {
|
||||||
|
REQUIRE_FALSE(is_absolute_path_within_root(fs::path(), root));
|
||||||
|
}
|
||||||
|
#ifndef _WIN32
|
||||||
|
// Creating symlinks on Windows needs elevated rights or developer mode.
|
||||||
|
SECTION("a symlink inside the root that points outside") {
|
||||||
|
const fs::path link = root / "Others" / "link.txt";
|
||||||
|
fs::create_symlink(outside, link);
|
||||||
|
REQUIRE_FALSE(is_absolute_path_within_root(link, root));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("is_safe_to_open_file_name accepts plain documents, images and models", "[utils]") {
|
||||||
|
const std::string safe = GENERATE(as<std::string>{},
|
||||||
|
"Manual.pdf", "BOM.xlsx", "BOM.csv", "guide.docx", "notes.txt", "README.md", "photo.JPG", "render.png",
|
||||||
|
"assembly.step", "part.stl", "project.3mf", "drawing.dxf", "build.mp4", "setup.exe.pdf", ".pdf");
|
||||||
|
INFO(safe);
|
||||||
|
CHECK(is_safe_to_open_file_name(safe));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("is_safe_to_open_file_name rejects programs and anything it does not know", "[utils]") {
|
||||||
|
const std::string unsafe = GENERATE(as<std::string>{},
|
||||||
|
"setup.exe", "SETUP.EXE", "Manual.pdf.exe", "run.bat", "shortcut.lnk", "site.url", "script.ps1", "help.chm",
|
||||||
|
"tool.jar", "script.py", "Install.command", "install.sh", "launcher.desktop", "Printer.AppImage",
|
||||||
|
// Documents that can carry macros or scripts.
|
||||||
|
"BOM.xls", "BOM.xlsm", "guide.doc", "guide.docm", "sheet.ods", "page.html", "logo.svg", "bundle.zip",
|
||||||
|
// No extension, an unknown one, or a name the desktop would read differently.
|
||||||
|
"readme", "pdf", "data.xyz", "", "...", "Manual.pdf.", "Manual.pdf ", "setup.exe:note.txt", "dir.pdf/readme");
|
||||||
|
INFO(unsafe);
|
||||||
|
CHECK_FALSE(is_safe_to_open_file_name(unsafe));
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ add_executable(${_TEST_NAME}_tests
|
|||||||
test_plugin_cloud_metadata.cpp
|
test_plugin_cloud_metadata.cpp
|
||||||
test_plugin_audit.cpp
|
test_plugin_audit.cpp
|
||||||
test_shortcuts.cpp
|
test_shortcuts.cpp
|
||||||
|
test_file_url.cpp
|
||||||
../fff_print/test_helpers.cpp
|
../fff_print/test_helpers.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
#include <catch2/generators/catch_generators.hpp>
|
||||||
|
|
||||||
|
#include "slic3r/GUI/GUI.hpp"
|
||||||
|
|
||||||
|
#include <boost/filesystem/path.hpp>
|
||||||
|
|
||||||
|
#include <wx/uri.h>
|
||||||
|
|
||||||
|
using namespace Slic3r::GUI;
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
TEST_CASE("A file URL keeps characters special to URLs in its path", "[FileUrl]")
|
||||||
|
{
|
||||||
|
const std::string path = GENERATE(as<std::string>{},
|
||||||
|
"/opt/test#dir/resources/web/homepage/index.html",
|
||||||
|
"/opt/test%20x/resources/web/homepage/index.html",
|
||||||
|
"/opt/Orca Slicer/resources/web/homepage/index.html",
|
||||||
|
"/opt/what?/resources/web/homepage/index.html",
|
||||||
|
"/home/Jos\xC3\xA9/\xE8\xB5\x84\xE6\xBA\x90/resources/web/homepage/index.html");
|
||||||
|
|
||||||
|
const wxString url = file_url_from_path(boost::filesystem::path(path));
|
||||||
|
CAPTURE(path, url.utf8_string());
|
||||||
|
|
||||||
|
// WebView::CreateWebView() and WebView::LoadUrl() re-parse the URL before loading it.
|
||||||
|
const wxURI uri(wxURI(url).BuildURI());
|
||||||
|
CHECK(uri.GetScheme() == "file");
|
||||||
|
CHECK(!uri.HasQuery());
|
||||||
|
CHECK(!uri.HasFragment());
|
||||||
|
CHECK(wxURI::Unescape(uri.GetPath()).utf8_string() == path);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("A file URL of a plain path is the path behind file://", "[FileUrl]")
|
||||||
|
{
|
||||||
|
const std::string path = "/opt/OrcaSlicer/resources/web/homepage/index.html";
|
||||||
|
CHECK(file_url_from_path(boost::filesystem::path(path)) == "file://" + path);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("A query appended to a file URL stays separate from its path", "[FileUrl]")
|
||||||
|
{
|
||||||
|
const std::string path = "/opt/test#dir%20x/resources/web/guide/0/index.html";
|
||||||
|
const wxURI uri(file_url_from_path(boost::filesystem::path(path)) + "?target=21&lang=de");
|
||||||
|
CHECK(wxURI::Unescape(uri.GetPath()).utf8_string() == path);
|
||||||
|
CHECK(uri.GetQuery() == "target=21&lang=de");
|
||||||
|
CHECK(!uri.HasFragment());
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
TEST_CASE("A file URL of a Windows path has a drive letter and forward slashes", "[FileUrl]")
|
||||||
|
{
|
||||||
|
const auto [path, url] = GENERATE(table<std::wstring, std::string>({
|
||||||
|
{ L"C:\\Program Files\\OrcaSlicer\\resources\\web\\homepage\\index.html",
|
||||||
|
"file:///C:/Program%20Files/OrcaSlicer/resources/web/homepage/index.html" },
|
||||||
|
{ L"D:\\#OneDrive\\OrcaSlicer\\resources\\web\\guide\\0\\index.html",
|
||||||
|
"file:///D:/%23OneDrive/OrcaSlicer/resources/web/guide/0/index.html" },
|
||||||
|
{ L"D:\\100%\\OrcaSlicer\\resources\\web\\homepage\\index.html",
|
||||||
|
"file:///D:/100%25/OrcaSlicer/resources/web/homepage/index.html" },
|
||||||
|
// Callers join the resources directory with a forward-slash relative path.
|
||||||
|
{ L"D:\\#OneDrive\\OrcaSlicer\\resources/web/homepage/index.html",
|
||||||
|
"file:///D:/%23OneDrive/OrcaSlicer/resources/web/homepage/index.html" },
|
||||||
|
{ L"\\\\server\\share\\OrcaSlicer\\resources\\web\\homepage\\index.html",
|
||||||
|
"file://server/share/OrcaSlicer/resources/web/homepage/index.html" },
|
||||||
|
}));
|
||||||
|
CHECK(file_url_from_path(boost::filesystem::path(path)).utf8_string() == url);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("A query appended to a Windows file URL stays separate from its path", "[FileUrl]")
|
||||||
|
{
|
||||||
|
const wxURI uri(file_url_from_path(boost::filesystem::path(L"D:\\#OneDrive\\OrcaSlicer\\resources\\web\\guide\\0\\index.html")) +
|
||||||
|
"?target=21&lang=de");
|
||||||
|
CHECK(wxURI::Unescape(uri.GetPath()) == "/D:/#OneDrive/OrcaSlicer/resources/web/guide/0/index.html");
|
||||||
|
CHECK(uri.GetQuery() == "target=21&lang=de");
|
||||||
|
CHECK(!uri.HasFragment());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include <wx/uri.h>
|
||||||
|
|
||||||
namespace py = pybind11;
|
namespace py = pybind11;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -210,23 +212,33 @@ TEST_CASE("A reloaded plugin page is recognised by its base URL, fragment aside"
|
|||||||
{
|
{
|
||||||
using namespace Slic3r::GUI::web_hosting;
|
using namespace Slic3r::GUI::web_hosting;
|
||||||
|
|
||||||
// A resources path holding a space, which the web view reports escaped.
|
// A resources path holding a space, which the web view may report escaped differently.
|
||||||
const Slic3r::ScopedResourcesDir resources("web content check");
|
const Slic3r::ScopedResourcesDir resources("web content check");
|
||||||
|
|
||||||
// The swapped-in page, then after an in-page anchor and a reload.
|
// The swapped-in page, then after an in-page anchor and a reload.
|
||||||
CHECK(is_content_url(content_base_url()));
|
CHECK(is_content_url(content_base_url()));
|
||||||
CHECK(is_content_url(content_base_url() + "#tab2"));
|
CHECK(is_content_url(content_base_url() + "#tab2"));
|
||||||
wxString escaped = content_base_url();
|
const wxString unescaped = wxURI::Unescape(content_base_url());
|
||||||
escaped.Replace(" ", "%20");
|
REQUIRE(unescaped != content_base_url());
|
||||||
REQUIRE(escaped != content_base_url());
|
CHECK(is_content_url(unescaped));
|
||||||
CHECK(is_content_url(escaped));
|
CHECK(is_content_url(unescaped + "#tab2"));
|
||||||
CHECK(is_content_url(escaped + "#tab2"));
|
|
||||||
// A page the plugin linked to keeps its own URL and must be left alone.
|
// A page the plugin linked to keeps its own URL and must be left alone.
|
||||||
CHECK_FALSE(is_content_url(content_base_url() + "guide.html"));
|
CHECK_FALSE(is_content_url(content_base_url() + "guide.html"));
|
||||||
CHECK_FALSE(is_content_url("https://example.com/"));
|
CHECK_FALSE(is_content_url("https://example.com/"));
|
||||||
CHECK_FALSE(is_content_url(""));
|
CHECK_FALSE(is_content_url(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("A reloaded plugin page is recognised when the resources path holds a '#'", "[PluginHost]")
|
||||||
|
{
|
||||||
|
using namespace Slic3r::GUI::web_hosting;
|
||||||
|
|
||||||
|
const Slic3r::ScopedResourcesDir resources("web#content check");
|
||||||
|
|
||||||
|
CHECK(is_content_url(content_base_url()));
|
||||||
|
CHECK(is_content_url(content_base_url() + "#tab2"));
|
||||||
|
CHECK_FALSE(is_content_url(content_base_url() + "guide.html"));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("Plugin host API exposes model geometry and structure to Python", "[PluginHost][Python]")
|
TEST_CASE("Plugin host API exposes model geometry and structure to Python", "[PluginHost][Python]")
|
||||||
{
|
{
|
||||||
using Catch::Matchers::WithinAbs;
|
using Catch::Matchers::WithinAbs;
|
||||||
|
|||||||
Reference in New Issue
Block a user