Compare commits

..
Author SHA1 Message Date
Hanif Koh 06cd4cbcc2 Reject Paths with an Embedded NUL When Confining Extraction
is_path_within_root compared each component with "..", so a name such
as "..\0" passed the check. The filesystem calls stop at the NUL and
act on a shorter path than the one that was checked: a symlink target
read from a plugin archive as raw bytes was created as "..", pointing
out of the plugin directory.

A path containing a NUL is now rejected before anything touches the
filesystem, which covers every caller, including entry names taken from
the Unicode Path extra field.
2026-09-29 22:37:43 +08:00
Hanif Koh 2ae1fdbd01 Validate Plugin Symlink Targets Before Replacing Existing Files
A symlink entry's target is now read and checked before anything already
at its destination is removed or renamed aside, so an archive rejected
for its link target leaves the installed plugin files in place.
2026-09-29 22:37:43 +08:00
Hanif Koh 78b504be0e Harden Archive Extraction Against Symlinks
The plugin installer now creates a symlink entry only when its target is
relative and, joined to the link's own directory, passes
is_path_within_root, via the new is_symlink_target_within_root helper.
Before writing any entry it checks the destination with symlink_status, so
an existing symlink, dangling or not, is replaced rather than followed, and
it creates parent directories inside the existing error handling.
extract_archive_confined replaces a symlink at a destination file the same
way.

is_path_within_root now ignores a trailing separator on the root, which
previously made every path fail the check.
2026-09-29 22:37:43 +08:00
Hanif Koh 3ef782c6e5 Confine Updater Archive Extraction to the Target Directory
The preset updater extracted downloaded archives by appending each entry
name to the cache directory, and the network plugin installer did the same
for the plugin folder, without checking that the result stays inside it.

Move the updater's extraction into libslic3r as extract_archive_confined,
which validates every entry with is_path_within_root before writing
anything and fails the whole archive if one entry resolves outside the
target. The plugin installer now rejects such an entry the same way. Well
formed archives extract exactly as before.
2026-09-29 22:36:59 +08:00
69 changed files with 288 additions and 1032 deletions
@@ -189,7 +189,8 @@ the presets to a base is in [shared-bases.md](shared-bases.md#variant-arrays-on-
2. Every key in `filament_options_with_variant` ([the full list](#the-four-key-sets)) that reaches the 2. Every key in `filament_options_with_variant` ([the full list](#the-four-key-sets)) that reaches the
preset, whether written, included or inherited, is resized to its variant count: write it at exactly preset, whether written, included or inherited, is resized to its variant count: write it at exactly
that width, or leave it out. Keys outside the set that width, or leave it out. Keys outside the set
(`filament_type`, plate temperatures, `fan_max_speed`, `slow_down_min_speed`, …) are never addressed by variant index; the preset contributes their first value however wide a (`filament_type`, plate temperatures, `fan_max_speed`, `slow_down_min_speed`, `pressure_advance`,
…) are never addressed by variant index; the preset contributes their first value however wide a
file writes them. file writes them.
3. Cover every variant the material is meant to print on across its `compatible_printers`. Leave out 3. Cover every variant the material is meant to print on across its `compatible_printers`. Leave out
a variant deliberately when the material should not be tuned for it (e.g. a TPU High Flow variant a variant deliberately when the material should not be tuned for it (e.g. a TPU High Flow variant
@@ -454,7 +455,7 @@ no set: the variant-length resize leaves it alone, and only the pair rebuild of
last for contrast; it is not a variant set. last for contrast; it is not a variant set.
`check` and `fix-variant` read the four sets from `PrintConfig.cpp` on every run, so they follow the `check` and `fix-variant` read the four sets from `PrintConfig.cpp` on every run, so they follow the
engine. The lists below are from the 2026-09-29 checkout; regenerate them from the repository root engine. The lists below are from the 2026-09-25 checkout; regenerate them from the repository root
before relying on them (the recipe strips comments, since an initializer can carry a commented-out entry): before relying on them (the recipe strips comments, since an initializer can carry a commented-out entry):
```bash ```bash
@@ -477,7 +478,7 @@ EOF
**`print_options_with_variant`**, process, stride 1 (45): `bridge_acceleration`, `bridge_speed`, `default_acceleration`, `default_jerk`, `default_junction_deviation`, `enable_overhang_speed`, `gap_infill_speed`, `infill_jerk`, `initial_layer_acceleration`, `initial_layer_infill_speed`, `initial_layer_jerk`, `initial_layer_speed`, `initial_layer_travel_acceleration`, `initial_layer_travel_jerk`, `initial_layer_travel_speed`, `inner_wall_acceleration`, `inner_wall_jerk`, `inner_wall_speed`, `internal_bridge_speed`, `internal_solid_infill_acceleration`, `internal_solid_infill_speed`, `outer_wall_acceleration`, `outer_wall_jerk`, `outer_wall_speed`, `overhang_1_4_speed`, `overhang_2_4_speed`, `overhang_3_4_speed`, `overhang_4_4_speed`, `print_extruder_id`, `print_extruder_variant`, `slowdown_for_curled_perimeters`, `small_perimeter_speed`, `small_perimeter_threshold`, `sparse_infill_acceleration`, `sparse_infill_speed`, `support_interface_speed`, `support_speed`, `top_solid_infill_flow_ratio`, `top_surface_acceleration`, `top_surface_jerk`, `top_surface_speed`, `travel_acceleration`, `travel_jerk`, `travel_speed`, `travel_speed_z` **`print_options_with_variant`**, process, stride 1 (45): `bridge_acceleration`, `bridge_speed`, `default_acceleration`, `default_jerk`, `default_junction_deviation`, `enable_overhang_speed`, `gap_infill_speed`, `infill_jerk`, `initial_layer_acceleration`, `initial_layer_infill_speed`, `initial_layer_jerk`, `initial_layer_speed`, `initial_layer_travel_acceleration`, `initial_layer_travel_jerk`, `initial_layer_travel_speed`, `inner_wall_acceleration`, `inner_wall_jerk`, `inner_wall_speed`, `internal_bridge_speed`, `internal_solid_infill_acceleration`, `internal_solid_infill_speed`, `outer_wall_acceleration`, `outer_wall_jerk`, `outer_wall_speed`, `overhang_1_4_speed`, `overhang_2_4_speed`, `overhang_3_4_speed`, `overhang_4_4_speed`, `print_extruder_id`, `print_extruder_variant`, `slowdown_for_curled_perimeters`, `small_perimeter_speed`, `small_perimeter_threshold`, `sparse_infill_acceleration`, `sparse_infill_speed`, `support_interface_speed`, `support_speed`, `top_solid_infill_flow_ratio`, `top_surface_acceleration`, `top_surface_jerk`, `top_surface_speed`, `travel_acceleration`, `travel_jerk`, `travel_speed`, `travel_speed_z`
**`filament_options_with_variant`**, filament, stride 1 (54): `activate_air_filtration`, `activate_air_filtration_during_print`, `activate_air_filtration_on_completion`, `adaptive_pressure_advance`, `adaptive_pressure_advance_bridges`, `adaptive_pressure_advance_model`, `adaptive_pressure_advance_overhangs`, `complete_print_exhaust_fan_speed`, `during_print_exhaust_fan_speed`, `enable_pressure_advance`, `filament_adaptive_volumetric_speed`, `filament_cooling_before_tower`, `filament_deretraction_speed`, `filament_extruder_variant`, `filament_flow_ratio`, `filament_flush_temp`, `filament_flush_volumetric_speed`, `filament_ironing_flow`, `filament_ironing_inset`, `filament_ironing_spacing`, `filament_ironing_speed`, `filament_long_retractions_when_cut`, `filament_max_volumetric_speed`, `filament_pre_cooling_temperature`, `filament_pre_cooling_temperature_nc`, `filament_preheat_temperature_delta`, `filament_ramming_travel_time`, `filament_ramming_travel_time_nc`, `filament_ramming_volumetric_speed`, `filament_ramming_volumetric_speed_nc`, `filament_retract_after_wipe`, `filament_retract_before_wipe`, `filament_retract_length_nc`, `filament_retract_length_toolchange`, `filament_retract_lift_above`, `filament_retract_lift_below`, `filament_retract_lift_enforce`, `filament_retract_restart_extra`, `filament_retract_restart_extra_toolchange`, `filament_retract_when_changing_layer`, `filament_retraction_distances_when_cut`, `filament_retraction_length`, `filament_retraction_minimum_travel`, `filament_retraction_speed`, `filament_wipe`, `filament_wipe_distance`, `filament_z_hop`, `filament_z_hop_types`, `long_retractions_when_ec`, `nozzle_temperature`, `nozzle_temperature_initial_layer`, `pressure_advance`, `retraction_distances_when_ec`, `volumetric_speed_coefficients` **`filament_options_with_variant`**, filament, stride 1 (48): `activate_air_filtration`, `activate_air_filtration_during_print`, `activate_air_filtration_on_completion`, `complete_print_exhaust_fan_speed`, `during_print_exhaust_fan_speed`, `filament_adaptive_volumetric_speed`, `filament_cooling_before_tower`, `filament_deretraction_speed`, `filament_extruder_variant`, `filament_flow_ratio`, `filament_flush_temp`, `filament_flush_volumetric_speed`, `filament_ironing_flow`, `filament_ironing_inset`, `filament_ironing_spacing`, `filament_ironing_speed`, `filament_long_retractions_when_cut`, `filament_max_volumetric_speed`, `filament_pre_cooling_temperature`, `filament_pre_cooling_temperature_nc`, `filament_preheat_temperature_delta`, `filament_ramming_travel_time`, `filament_ramming_travel_time_nc`, `filament_ramming_volumetric_speed`, `filament_ramming_volumetric_speed_nc`, `filament_retract_after_wipe`, `filament_retract_before_wipe`, `filament_retract_length_nc`, `filament_retract_length_toolchange`, `filament_retract_lift_above`, `filament_retract_lift_below`, `filament_retract_lift_enforce`, `filament_retract_restart_extra`, `filament_retract_restart_extra_toolchange`, `filament_retract_when_changing_layer`, `filament_retraction_distances_when_cut`, `filament_retraction_length`, `filament_retraction_minimum_travel`, `filament_retraction_speed`, `filament_wipe`, `filament_wipe_distance`, `filament_z_hop`, `filament_z_hop_types`, `long_retractions_when_ec`, `nozzle_temperature`, `nozzle_temperature_initial_layer`, `retraction_distances_when_ec`, `volumetric_speed_coefficients`
**`printer_extruder_options`**, machine, one value per extruder, not a variant set (8): **`printer_extruder_options`**, machine, one value per extruder, not a variant set (8):
`default_nozzle_volume_type`, `extruder_max_nozzle_count`, `extruder_printable_area`, `default_nozzle_volume_type`, `extruder_max_nozzle_count`, `extruder_printable_area`,
@@ -244,10 +244,10 @@ preset for a 0.2 nozzle, explicitly revisit flow limits; do not infer a pressure
required direction of change, from diameter alone. required direction of change, from diameter alone.
On a printer with extruder variants, a filament tunes these per variant too: On a printer with extruder variants, a filament tunes these per variant too:
`filament_max_volumetric_speed`, `filament_flow_ratio`, `nozzle_temperature`, pressure advance and the `filament_max_volumetric_speed`, `filament_flow_ratio`, `nozzle_temperature` and the retraction
retraction overrides carry one value per variant of `filament_extruder_variant` (Standard, High Flow, …). The overrides carry one value per variant of `filament_extruder_variant` (Standard, High Flow, …). The
exact key set is [`filament_options_with_variant`](extruder-variants.md#the-four-key-sets); exact key set is [`filament_options_with_variant`](extruder-variants.md#the-four-key-sets);
`slow_down_min_speed` and `fan_max_speed` are not in it. Keep every such array at exactly that width, even where the `slow_down_min_speed` and `pressure_advance` are not in it. Keep every such array at exactly that width, even where the
setting does not differ per variant, and measure the High Flow variant rather than copying Standard setting does not differ per variant, and measure the High Flow variant rather than copying Standard
([extruder-variants.md](extruder-variants.md#filament)). ([extruder-variants.md](extruder-variants.md#filament)).
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "OrcaFilamentLibrary", "name": "OrcaFilamentLibrary",
"version": "02.04.00.16", "version": "02.04.00.15",
"force_update": "0", "force_update": "0",
"description": "Orca Filament Library", "description": "Orca Filament Library",
"filament_list": [ "filament_list": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"1" "1"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"0" "0"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"0" "0"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1" "1"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"eng_plate_temp": [ "eng_plate_temp": [
@@ -317,7 +316,6 @@
"2" "2"
], ],
"pressure_advance": [ "pressure_advance": [
"0.02",
"0.02" "0.02"
], ],
"reduce_fan_stop_start_freq": [ "reduce_fan_stop_start_freq": [
+30 -129
View File
@@ -214,9 +214,9 @@ function ShowModelInfo( pModel )
SendWXDebugInfo("Model Name: "+sModelName); SendWXDebugInfo("Model Name: "+sModelName);
$('#ModelName').text(sModelName); $('#ModelName').html(sModelName);
$('#ModelName').attr('title',sModelName); $('#ModelName').attr('title',sModelName);
$('#ModelAuthorName').text(sModelAuthor); $('#ModelAuthorName').html(sModelAuthor);
switch(UploadType) switch(UploadType)
{ {
@@ -268,7 +268,7 @@ function ShowModelInfo( pModel )
break; break;
} }
$('#Model_Desc').empty().append( SanitizeDescHtml( html_decode(sModelDesc) ) ); $('#Model_Desc').html( html_decode(sModelDesc) );
let ModelPreviewList=pModel.preview_img; let ModelPreviewList=pModel.preview_img;
let TotalPreview=ModelPreviewList.length; let TotalPreview=ModelPreviewList.length;
@@ -281,15 +281,16 @@ function ShowModelInfo( pModel )
if(TotalPreview>0) if(TotalPreview>0)
{ {
$('#ModelPreviewList').empty(); let htmlPreview='';
for(let pn=0;pn<TotalPreview;pn++) for(let pn=0;pn<TotalPreview;pn++)
{ {
//let FTmpPath=decodeURIComponent(ModelPreviewList[pn]); //let FTmpPath=decodeURIComponent(ModelPreviewList[pn]);
let FTmpPath=ModelPreviewList[pn]['filepath']; let FTmpPath=ModelPreviewList[pn]['filepath'];
$('#ModelPreviewList').append( $('<div class="swiper-slide"></div>').append( $('<img class="Model_PrevImg" />').attr('src',FTmpPath) ) ); htmlPreview+='<div class="swiper-slide"><img class="Model_PrevImg" src="'+FTmpPath+'" /></div>';
} }
$('#ModelPreviewList').html(htmlPreview);
$('#Model_Preview_Image').viewer({ $('#Model_Preview_Image').viewer({
title: false, title: false,
fullsreen: false, fullsreen: false,
@@ -409,8 +410,7 @@ function ConstructFileHtml( ID, pItem )
{ {
let fTotal=pItem.length; let fTotal=pItem.length;
let pBoard=$('#'+ID+' .FileListBoard'); let strHtml='';
pBoard.empty();
for( let f=0;f<fTotal;f++ ) for( let f=0;f<fTotal;f++ )
{ {
let pOne=pItem[f]; let pOne=pItem[f];
@@ -443,139 +443,39 @@ function ConstructFileHtml( ID, pItem )
ImgPath='img/default.png'; ImgPath='img/default.png';
} }
//Add html. File names come from the 3MF, so build the nodes rather than concatenating markup. //Add html
let pIconImg=$('<img />').attr('src',ImgPath);
let pMenu=$('<div class="FileMenu"><img src="img/s.svg" /></div>');
if( strClass!='ImageIcon' ) if( strClass!='ImageIcon' )
{ {
pMenu.on('click', function(){ OnClickOpenFile(tPath); }); strHtml+='<div class="FileItem">'+
' <div class="'+strClass+'"><img src="'+ImgPath+'" /></div>'+
' <div class="FileText">'+
' <div class="FileName">'+tName+'</div>'+
' </div>'+
' <div class="FileMenu" onClick="OnClickOpenFile(\''+tPath+'\')"><img src="img/s.svg" /></div>'+
'</div>';
} }
else else
{ {
ImgID++; ImgID++;
let TmpImgID="AF"+ImgID; let TmpImgID="AF"+ImgID;
pIconImg.attr('id',TmpImgID); strHtml+='<div class="FileItem">'+
pMenu.on('click', function(){ OnClickOpenImage(TmpImgID); }); ' <div class="'+strClass+'"><img id="'+TmpImgID+'" src="'+ImgPath+'" /></div>'+
' <div class="FileText">'+
' <div class="FileName">'+tName+'</div>'+
' </div>'+
' <div class="FileMenu" onClick="OnClickOpenImage(\''+TmpImgID+'\')"><img src="img/s.svg" /></div>'+
'</div>';
}
} }
let pFileItem=$('<div class="FileItem"></div>'); $('#'+ID+' .FileListBoard').html(strHtml);
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==========
@@ -583,10 +483,10 @@ function ShowProfilelInfo( pProfile )
let sProfileAuthor=decodeURIComponent(pProfile.author); let sProfileAuthor=decodeURIComponent(pProfile.author);
let sProfileDesc=decodeURIComponent(pProfile.description); let sProfileDesc=decodeURIComponent(pProfile.description);
$('#ProfileName').text(sProfileName); $('#ProfileName').html(sProfileName);
$('#ProfileAuthor').text(sProfileAuthor); $('#ProfileAuthor').html(sProfileAuthor);
$('#Profile_Desc').empty().append( SanitizeDescHtml( html_decode(sProfileDesc) ) ); $('#Profile_Desc').html( html_decode(sProfileDesc) );
let ProfilePreviewList=pProfile.preview_img; let ProfilePreviewList=pProfile.preview_img;
let TotalPreview=ProfilePreviewList.length; let TotalPreview=ProfilePreviewList.length;
@@ -599,14 +499,15 @@ function ShowProfilelInfo( pProfile )
if(TotalPreview>0) if(TotalPreview>0)
{ {
$('#ProfilePreviewList').empty(); let htmlPreview='';
for(let pn=0;pn<TotalPreview;pn++) for(let pn=0;pn<TotalPreview;pn++)
{ {
let FTmpPath=ProfilePreviewList[pn]['filepath']; let FTmpPath=ProfilePreviewList[pn]['filepath'];
$('#ProfilePreviewList').append( $('<div class="swiper-slide"></div>').append( $('<img class="Model_PrevImg" />').attr('src',FTmpPath) ) ); htmlPreview+='<div class="swiper-slide"><img class="Model_PrevImg" src="'+FTmpPath+'" /></div>';
} }
$('#ProfilePreviewList').html(htmlPreview);
$('#Profile_Preview_Image').viewer({ $('#Profile_Preview_Image').viewer({
title: false, title: false,
fullsreen: false, fullsreen: false,
-30
View File
@@ -3476,14 +3476,6 @@ int CLI::run(int argc, char **argv)
} }
new_variant_counts = old_variant_counts; new_variant_counts = old_variant_counts;
//filament_variant_count = old_variant_counts; //filament_variant_count = old_variant_counts;
//ORCA: lay the per-variant options out one value per variant of the current filaments before each
// loaded filament replaces its own variants, including an option only a loaded filament
// defines, which otherwise starts as a single default value and never reaches the others.
for (const DynamicPrintConfig &config : load_filaments_config)
for (const std::string &opt_key : filament_options_with_variant)
if (opt_key != "filament_extruder_variant" && config.has(opt_key))
m_print_config.option(opt_key, true);
normalize_filament_values_to_variants(m_print_config);
for (int index = 0; index < load_filaments_config.size(); index++) { for (int index = 0; index < load_filaments_config.size(); index++) {
DynamicPrintConfig& config = load_filaments_config[index]; DynamicPrintConfig& config = load_filaments_config[index];
int filament_index = load_filaments_index[index]; int filament_index = load_filaments_index[index];
@@ -3655,25 +3647,6 @@ int CLI::run(int argc, char **argv)
} }
} }
//ORCA: a per-variant option the loaded filament does not define keeps the values of the
// variants the filament already had, and a variant new to it takes its first one's.
const int old_start = old_start_indice[filament_index - 1];
std::vector<int> kept_variant_indice = new_variant_indice;
for (int &i : kept_variant_indice)
if (i < 0)
i = old_start;
for (const std::string &opt_key : filament_options_with_variant) {
if (config.has(opt_key))
continue;
auto *opt_vec_dst = dynamic_cast<ConfigOptionVectorBase *>(m_print_config.option(opt_key));
if (opt_vec_dst == nullptr || opt_vec_dst->size() < size_t(old_start + old_variant_count))
continue;
// set_with_restore_2() pads its source in place
std::unique_ptr<ConfigOption> old_values(opt_vec_dst->clone());
opt_vec_dst->set_with_restore_2(static_cast<ConfigOptionVectorBase *>(old_values.get()), kept_variant_indice, old_start,
old_variant_count, true);
}
//update the old index //update the old index
if (old_variant_count != new_variant_count) if (old_variant_count != new_variant_count)
{ {
@@ -4108,9 +4081,6 @@ int CLI::run(int argc, char **argv)
if (printer_technology == ptFFF) { if (printer_technology == ptFFF) {
fff_print_config.apply(m_print_config, true); fff_print_config.apply(m_print_config, true);
m_print_config.apply(fff_print_config, true); m_print_config.apply(fff_print_config, true);
//ORCA: an option no preset or project defines has just come in as its single default value, and a
// command line override may hold one value per filament.
normalize_filament_values_to_variants(m_print_config);
} else { } else {
boost::nowide::cerr << "invalid printer_technology " << std::endl; boost::nowide::cerr << "invalid printer_technology " << std::endl;
record_exit_reson(outfile_dir, CLI_INVALID_PRINTER_TECH, 0, cli_errors[CLI_INVALID_PRINTER_TECH], sliced_info); record_exit_reson(outfile_dir, CLI_INVALID_PRINTER_TECH, 0, cli_errors[CLI_INVALID_PRINTER_TECH], sliced_info);
+8 -12
View File
@@ -141,7 +141,7 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s
its.indices.emplace_back(indices[0], indices[1], indices[2]); its.indices.emplace_back(indices[0], indices[1], indices[2]);
int face_index =its.indices.size() - 1; int face_index =its.indices.size() - 1;
RGBA face_color; RGBA face_color;
auto set_face_color = [&uvs, &data, &mtl_data, &obj_info, &face_color](int face_index, const std::string mtl_name, const std::array<int, 3> &corners) { auto set_face_color = [&uvs, &data, &mtl_data, &obj_info, &face_color](int face_index, const std::string mtl_name) {
if (mtl_data.new_mtl_unmap.find(mtl_name) != mtl_data.new_mtl_unmap.end()) { if (mtl_data.new_mtl_unmap.find(mtl_name) != mtl_data.new_mtl_unmap.end()) {
bool is_merge_ka_kd = true; bool is_merge_ka_kd = true;
for (size_t n = 0; n < 3; n++) { for (size_t n = 0; n < 3; n++) {
@@ -174,7 +174,7 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s
return Vec2f::Zero(); return Vec2f::Zero();
return Vec2f(data.textureCoordinates[idx * OBJ_TEXCOORD_LENGTH], data.textureCoordinates[idx * OBJ_TEXCOORD_LENGTH + 1]); return Vec2f(data.textureCoordinates[idx * OBJ_TEXCOORD_LENGTH], data.textureCoordinates[idx * OBJ_TEXCOORD_LENGTH + 1]);
}; };
std::array<Vec2f, 3> uv_array{uv_at(uvs[corners[0]]), uv_at(uvs[corners[1]]), uv_at(uvs[corners[2]])}; std::array<Vec2f, 3> uv_array{uv_at(uvs[0]), uv_at(uvs[1]), uv_at(uvs[2])};
obj_info.uvs.emplace_back(uv_array); obj_info.uvs.emplace_back(uv_array);
} }
obj_info.face_colors.emplace_back(face_color); obj_info.face_colors.emplace_back(face_color);
@@ -185,27 +185,27 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s
} }
} }
}; };
auto set_face_color_by_mtl = [&data, &set_face_color](int face_index, const std::array<int, 3> &corners) { auto set_face_color_by_mtl = [&data, &set_face_color](int face_index) {
if (data.usemtls.size() == 1) { if (data.usemtls.size() == 1) {
set_face_color(face_index, data.usemtls[0].name, corners); set_face_color(face_index, data.usemtls[0].name);
} else { } else {
for (size_t k = 0; k < data.usemtls.size(); k++) { for (size_t k = 0; k < data.usemtls.size(); k++) {
auto mtl = data.usemtls[k]; auto mtl = data.usemtls[k];
if (face_index >= mtl.face_start && face_index <= mtl.face_end) { if (face_index >= mtl.face_start && face_index <= mtl.face_end) {
set_face_color(face_index, data.usemtls[k].name, corners); set_face_color(face_index, data.usemtls[k].name);
break; break;
} }
} }
} }
}; };
if (exist_mtl) { if (exist_mtl) {
set_face_color_by_mtl(face_index, {0, 1, 2}); set_face_color_by_mtl(face_index);
} }
if (cnt == 4) { if (cnt == 4) {
its.indices.emplace_back(indices[0], indices[2], indices[3]); its.indices.emplace_back(indices[0], indices[2], indices[3]);
int face_index = its.indices.size() - 1; int face_index = its.indices.size() - 1;
if (exist_mtl) { if (exist_mtl) {
set_face_color_by_mtl(face_index, {0, 2, 3}); set_face_color_by_mtl(face_index);
} }
} }
} }
@@ -217,12 +217,8 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s
message = _L("This OBJ file couldn't be read because it's empty."); message = _L("This OBJ file couldn't be read because it's empty.");
return false; return false;
} }
if (meshptr->volume() < 0) { if (meshptr->volume() < 0)
meshptr->flip_triangles(); meshptr->flip_triangles();
// Flipping swaps corners 1 and 2 of every face, so the UVs have to follow.
for (std::array<Vec2f, 3> &uv : obj_info.uvs)
std::swap(uv[1], uv[2]);
}
// Hand the parsed material table back so callers can build a TexturedMesh from it. // Hand the parsed material table back so callers can build a TexturedMesh from it.
if (out_mtl) if (out_mtl)
*out_mtl = mtl_data; *out_mtl = mtl_data;
+68 -48
View File
@@ -1497,7 +1497,12 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
check_add_eol(toolchange_gcode_str); check_add_eol(toolchange_gcode_str);
// SoftFever: set new PA for new filament // SoftFever: set new PA for new filament
gcode += gcodegen.set_filament_pressure_advance(new_filament_id); if (gcodegen.config().enable_pressure_advance.get_at(new_filament_id)) {
gcode += gcodegen.writer().set_pressure_advance(gcodegen.config().pressure_advance.get_at(new_filament_id));
// Orca: Adaptive PA
// Reset Adaptive PA processor last PA value
gcodegen.m_pa_processor->resetPreviousPA(gcodegen.config().pressure_advance.get_at(new_filament_id));
}
// A phony move to the end position at the wipe tower. // A phony move to the end position at the wipe tower.
gcodegen.writer().travel_to_xy((end_pos + plate_origin_2d).cast<double>()); gcodegen.writer().travel_to_xy((end_pos + plate_origin_2d).cast<double>());
@@ -1821,8 +1826,12 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
check_add_eol(toolchange_gcode_str); check_add_eol(toolchange_gcode_str);
// SoftFever: set new PA for new filament // SoftFever: set new PA for new filament
if (new_extruder_id != -1) if (new_extruder_id != -1 && gcodegen.config().enable_pressure_advance.get_at(new_extruder_id)) {
gcode += gcodegen.set_filament_pressure_advance(new_extruder_id); gcode += gcodegen.writer().set_pressure_advance(gcodegen.config().pressure_advance.get_at(new_extruder_id));
// Orca: Adaptive PA
// Reset Adaptive PA processor last PA value
gcodegen.m_pa_processor->resetPreviousPA(gcodegen.config().pressure_advance.get_at(new_extruder_id));
}
// A phony move to the end position at the wipe tower. // A phony move to the end position at the wipe tower.
gcodegen.writer().travel_to_xy((end_pos + plate_origin_2d).cast<double>()); gcodegen.writer().travel_to_xy((end_pos + plate_origin_2d).cast<double>());
@@ -3223,7 +3232,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
m_cooling_buffer->set_current_extruder(initial_extruder_id, extruder_id); m_cooling_buffer->set_current_extruder(initial_extruder_id, extruder_id);
// Orca: Initialise AdaptivePA processor filter // Orca: Initialise AdaptivePA processor filter
m_pa_processor = std::make_unique<AdaptivePAProcessor>(*this); m_pa_processor = std::make_unique<AdaptivePAProcessor>(*this, tool_ordering.all_extruders());
// Update output variables after the extruders were initialized. // Update output variables after the extruders were initialized.
m_placeholder_parser_integration.init(m_writer); m_placeholder_parser_integration.init(m_writer);
@@ -3788,7 +3797,12 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
} }
} }
// Orca: add missing PA settings for initial filament // Orca: add missing PA settings for initial filament
file.write(set_filament_pressure_advance(initial_non_support_extruder_id)); if (m_config.enable_pressure_advance.get_at(initial_non_support_extruder_id)) {
file.write(m_writer.set_pressure_advance(m_config.pressure_advance.get_at(initial_non_support_extruder_id)));
// Orca: Adaptive PA
// Reset Adaptive PA processor last PA value
m_pa_processor->resetPreviousPA(m_config.pressure_advance.get_at(initial_non_support_extruder_id));
}
} }
//flush FanMover buffer to avoid modifying the start gcode if it's manual. //flush FanMover buffer to avoid modifying the start gcode if it's manual.
@@ -8376,16 +8390,12 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// Orca: Dynamic PA // Orca: Dynamic PA
// If adaptive PA is enabled, by default evaluate PA on all extrusion moves // If adaptive PA is enabled, by default evaluate PA on all extrusion moves
// The PA settings and the PA_CHANGE tags use the filament config index of the filament's extruder variant
const unsigned int pa_config_index = (unsigned int) get_filament_config_index(m_writer.filament()->id());
const bool adaptive_pa = m_config.adaptive_pressure_advance.get_at(pa_config_index) && m_config.enable_pressure_advance.get_at(pa_config_index);
const bool adaptive_pa_overhangs = adaptive_pa && m_config.adaptive_pressure_advance_overhangs.get_at(pa_config_index);
bool is_pa_calib = m_curr_print->calib_mode() == CalibMode::Calib_PA_Line || bool is_pa_calib = m_curr_print->calib_mode() == CalibMode::Calib_PA_Line ||
m_curr_print->calib_mode() == CalibMode::Calib_PA_Pattern || m_curr_print->calib_mode() == CalibMode::Calib_PA_Pattern ||
m_curr_print->calib_mode() == CalibMode::Calib_PA_Tower; m_curr_print->calib_mode() == CalibMode::Calib_PA_Tower;
bool evaluate_adaptive_pa = false; bool evaluate_adaptive_pa = false;
bool role_change = (m_last_extrusion_role != path.role()); bool role_change = (m_last_extrusion_role != path.role());
if (!is_pa_calib && adaptive_pa) { if (!is_pa_calib && FILAMENT_CONFIG(adaptive_pressure_advance) && FILAMENT_CONFIG(enable_pressure_advance)) {
evaluate_adaptive_pa = true; evaluate_adaptive_pa = true;
// If we have already emmited a PA change because the m_multi_flow_segment_path_pa_set is set // If we have already emmited a PA change because the m_multi_flow_segment_path_pa_set is set
// skip re-issuing the PA change tag. // skip re-issuing the PA change tag.
@@ -8468,7 +8478,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// Post processor flag generation code segment when option to emit only at role changes is enabled // Post processor flag generation code segment when option to emit only at role changes is enabled
// Variables published to the post processor: // Variables published to the post processor:
// 1) Tag to trigger a PA evaluation (because a role change was identified and the user has requested dynamic PA adjustments) // 1) Tag to trigger a PA evaluation (because a role change was identified and the user has requested dynamic PA adjustments)
// 2) Current filament config index (to identify the PA model for the currently used filament and its extruder variant) // 2) Current extruder ID (to identify the PA model for the currently used extruder)
// 3) mm3_per_mm value (to then multiply by the final model print speed after slowdown for cooling is applied) // 3) mm3_per_mm value (to then multiply by the final model print speed after slowdown for cooling is applied)
// 4) the current acceleration (to pass to the model for evaluation) // 4) the current acceleration (to pass to the model for evaluation)
// 5) whether this is an external perimeter (for future use) // 5) whether this is an external perimeter (for future use)
@@ -8479,7 +8489,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
if (m_multi_flow_segment_path_average_mm3_per_mm > 0) { if (m_multi_flow_segment_path_average_mm3_per_mm > 0) {
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n", sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
pa_config_index, m_writer.filament()->id(),
m_multi_flow_segment_path_average_mm3_per_mm, m_multi_flow_segment_path_average_mm3_per_mm,
acceleration_i, acceleration_i,
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)), ((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
@@ -8492,7 +8502,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// to issue a zero flow PA change command for this // to issue a zero flow PA change command for this
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n", sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
pa_config_index, m_writer.filament()->id(),
_mm3_per_mm, _mm3_per_mm,
acceleration_i, acceleration_i,
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)), ((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
@@ -8600,7 +8610,11 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// or a flow change, so emit the flag to evaluate PA for the upcomming extrusion // or a flow change, so emit the flag to evaluate PA for the upcomming extrusion
// Emit tag before new speed is set so the post processor reads the next speed immediately and uses it. // Emit tag before new speed is set so the post processor reads the next speed immediately and uses it.
// Dont emit tag if it has just already been emitted from a role change above // Dont emit tag if it has just already been emitted from a role change above
if(_mm3_per_mm >0 && adaptive_pa_overhangs && !evaluate_adaptive_pa){ if(_mm3_per_mm >0 &&
FILAMENT_CONFIG(adaptive_pressure_advance) &&
FILAMENT_CONFIG(enable_pressure_advance) &&
FILAMENT_CONFIG(adaptive_pressure_advance_overhangs) &&
!evaluate_adaptive_pa){
if(writer().get_current_speed() > F){ // Ramping down speed - use overhang logic where the minimum speed is used between current and upcoming extrusion if(writer().get_current_speed() > F){ // Ramping down speed - use overhang logic where the minimum speed is used between current and upcoming extrusion
if(m_config.gcode_comments){ if(m_config.gcode_comments){
sprintf(buf, "; Ramp down-non-variable\n"); sprintf(buf, "; Ramp down-non-variable\n");
@@ -8608,7 +8622,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
} }
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n", sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
pa_config_index, m_writer.filament()->id(),
_mm3_per_mm, _mm3_per_mm,
acceleration_i, acceleration_i,
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)), ((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
@@ -8623,7 +8637,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
} }
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n", sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
pa_config_index, m_writer.filament()->id(),
_mm3_per_mm, _mm3_per_mm,
acceleration_i, acceleration_i,
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)), ((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
@@ -8829,7 +8843,10 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// ORCA: Adaptive PA code segment when adjusting PA within the same feature // ORCA: Adaptive PA code segment when adjusting PA within the same feature
// There is a speed change or flow change so emit the flag to evaluate PA for the upcomming extrusion // There is a speed change or flow change so emit the flag to evaluate PA for the upcomming extrusion
// Emit tag before new speed is set so the post processor reads the next speed immediately and uses it. // Emit tag before new speed is set so the post processor reads the next speed immediately and uses it.
if(_mm3_per_mm >0 && adaptive_pa_overhangs){ if(_mm3_per_mm >0 &&
EXTRUDER_CONFIG(adaptive_pressure_advance) &&
EXTRUDER_CONFIG(enable_pressure_advance) &&
EXTRUDER_CONFIG(adaptive_pressure_advance_overhangs) ){
if(last_set_speed > new_speed){ // Ramping down speed - use overhang logic where the minimum speed is used between current and upcoming extrusion if(last_set_speed > new_speed){ // Ramping down speed - use overhang logic where the minimum speed is used between current and upcoming extrusion
if(m_config.gcode_comments) { if(m_config.gcode_comments) {
sprintf(buf, "; Ramp up-variable\n"); sprintf(buf, "; Ramp up-variable\n");
@@ -8837,7 +8854,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
} }
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n", sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
pa_config_index, m_writer.filament()->id(),
_mm3_per_mm, _mm3_per_mm,
acceleration_i, acceleration_i,
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)), ((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
@@ -8852,7 +8869,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
} }
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n", sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(), GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
pa_config_index, m_writer.filament()->id(),
_mm3_per_mm, _mm3_per_mm,
acceleration_i, acceleration_i,
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)), ((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
@@ -9405,29 +9422,33 @@ void GCode::update_placeholder_parser_with_variant_params()
if (num_filaments == 0) if (num_filaments == 0)
return; return;
// Helper: remap config arrays from variant index space to filament_id index space. // Helpers: remap config arrays from variant index space to filament_id index space.
// After remapping, gcode templates can use param[filament_id] directly. // After remapping, gcode templates can use param[filament_id] directly.
auto remap_by_filament = [&](const auto &src) { auto remap_floats_by_filament = [&](const auto &src) {
std::decay_t<decltype(src.values)> dst(num_filaments); std::vector<double> dst(num_filaments);
for (size_t i = 0; i < num_filaments; ++i)
dst[i] = src.get_at(get_filament_config_index(i));
return dst;
};
auto remap_ints_by_filament = [&](const auto &src) {
std::vector<int> dst(num_filaments);
for (size_t i = 0; i < num_filaments; ++i) for (size_t i = 0; i < num_filaments; ++i)
dst[i] = src.get_at(get_filament_config_index(i)); dst[i] = src.get_at(get_filament_config_index(i));
return dst; return dst;
}; };
// --- filament_options_with_variant: gcode indexes by filament_id --- // --- filament_options_with_variant: gcode indexes by filament_id ---
this->placeholder_parser().set("filament_max_volumetric_speed", new ConfigOptionFloats(remap_by_filament(m_config.filament_max_volumetric_speed))); this->placeholder_parser().set("filament_max_volumetric_speed", new ConfigOptionFloats(remap_floats_by_filament(m_config.filament_max_volumetric_speed)));
this->placeholder_parser().set("filament_pre_cooling_temperature", new ConfigOptionInts(remap_by_filament(m_config.filament_pre_cooling_temperature))); this->placeholder_parser().set("filament_pre_cooling_temperature", new ConfigOptionInts(remap_ints_by_filament(m_config.filament_pre_cooling_temperature)));
this->placeholder_parser().set("filament_pre_cooling_temperature_nc", new ConfigOptionInts(remap_by_filament(m_config.filament_pre_cooling_temperature_nc))); this->placeholder_parser().set("filament_pre_cooling_temperature_nc", new ConfigOptionInts(remap_ints_by_filament(m_config.filament_pre_cooling_temperature_nc)));
this->placeholder_parser().set("filament_cooling_before_tower", new ConfigOptionFloats(remap_by_filament(m_config.filament_cooling_before_tower))); this->placeholder_parser().set("filament_cooling_before_tower", new ConfigOptionFloats(remap_floats_by_filament(m_config.filament_cooling_before_tower)));
this->placeholder_parser().set("nozzle_temperature_initial_layer", new ConfigOptionInts(remap_by_filament(m_config.nozzle_temperature_initial_layer))); this->placeholder_parser().set("nozzle_temperature_initial_layer", new ConfigOptionInts(remap_ints_by_filament(m_config.nozzle_temperature_initial_layer)));
this->placeholder_parser().set("nozzle_temperature", new ConfigOptionInts(remap_by_filament(m_config.nozzle_temperature))); this->placeholder_parser().set("nozzle_temperature", new ConfigOptionInts(remap_ints_by_filament(m_config.nozzle_temperature)));
// first_layer_temperature is a legacy alias of nozzle_temperature_initial_layer // first_layer_temperature is a legacy alias of nozzle_temperature_initial_layer
this->placeholder_parser().set("first_layer_temperature", new ConfigOptionInts(remap_by_filament(m_config.nozzle_temperature_initial_layer))); this->placeholder_parser().set("first_layer_temperature", new ConfigOptionInts(remap_ints_by_filament(m_config.nozzle_temperature_initial_layer)));
this->placeholder_parser().set("pressure_advance", new ConfigOptionFloats(remap_by_filament(m_config.pressure_advance)));
this->placeholder_parser().set("enable_pressure_advance", new ConfigOptionBools(remap_by_filament(m_config.enable_pressure_advance)));
// --- printer_options_with_variant_1: in m_config these are already merged as filament-indexed --- // --- printer_options_with_variant_1: in m_config these are already merged as filament-indexed ---
this->placeholder_parser().set("retraction_distances_when_cut", new ConfigOptionFloats(remap_by_filament(m_config.retraction_distances_when_cut))); this->placeholder_parser().set("retraction_distances_when_cut", new ConfigOptionFloats(remap_floats_by_filament(m_config.retraction_distances_when_cut)));
// hotend_cooling_rate / hotend_heating_rate: gcode uses [filament_map[x]-1] (extruder_id), no remap needed // hotend_cooling_rate / hotend_heating_rate: gcode uses [filament_map[x]-1] (extruder_id), no remap needed
// --- filament_map: per-layer dynamic, sync from m_config to placeholder_parser --- // --- filament_map: per-layer dynamic, sync from m_config to placeholder_parser ---
@@ -9437,9 +9458,9 @@ void GCode::update_placeholder_parser_with_variant_params()
{ {
// Fast purge mode uses filament_flush_temp_fast; Default is inert. // Fast purge mode uses filament_flush_temp_fast; Default is inert.
bool use_fast_flush = m_config.prime_volume_mode == PrimeVolumeMode::pvmFast; bool use_fast_flush = m_config.prime_volume_mode == PrimeVolumeMode::pvmFast;
auto flush_v_speed = remap_by_filament(m_config.filament_flush_volumetric_speed); auto flush_v_speed = remap_floats_by_filament(m_config.filament_flush_volumetric_speed);
auto filament_max_v = remap_by_filament(m_config.filament_max_volumetric_speed); auto filament_max_v = remap_floats_by_filament(m_config.filament_max_volumetric_speed);
auto flush_temps = remap_by_filament(use_fast_flush ? m_config.filament_flush_temp_fast auto flush_temps = remap_ints_by_filament(use_fast_flush ? m_config.filament_flush_temp_fast
: m_config.filament_flush_temp); : m_config.filament_flush_temp);
for (size_t i = 0; i < num_filaments; ++i) { for (size_t i = 0; i < num_filaments; ++i) {
if (flush_v_speed[i] == 0) if (flush_v_speed[i] == 0)
@@ -9492,7 +9513,12 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
gcode += this->placeholder_parser_process("filament_start_gcode", filament_start_gcode, new_filament_id, &config); gcode += this->placeholder_parser_process("filament_start_gcode", filament_start_gcode, new_filament_id, &config);
check_add_eol(gcode); check_add_eol(gcode);
} }
gcode += set_filament_pressure_advance(new_filament_id); if (m_config.enable_pressure_advance.get_at(new_filament_id)) {
gcode += m_writer.set_pressure_advance(m_config.pressure_advance.get_at(new_filament_id));
// Orca: Adaptive PA
// Reset Adaptive PA processor last PA value
m_pa_processor->resetPreviousPA(m_config.pressure_advance.get_at(new_filament_id));
}
gcode += m_writer.toolchange(new_filament_id, new_extruder_id); gcode += m_writer.toolchange(new_filament_id, new_extruder_id);
if (Extruder *fil = m_writer.filament()) if (Extruder *fil = m_writer.filament())
@@ -9883,24 +9909,18 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
if (m_ooze_prevention.enable && !defer_temp_wait) if (m_ooze_prevention.enable && !defer_temp_wait)
gcode += m_ooze_prevention.post_toolchange(*this); gcode += m_ooze_prevention.post_toolchange(*this);
gcode += set_filament_pressure_advance(new_filament_id); if (m_config.enable_pressure_advance.get_at(new_filament_id)) {
gcode += m_writer.set_pressure_advance(m_config.pressure_advance.get_at(new_filament_id));
// Orca: Adaptive PA
// Reset Adaptive PA processor last PA value
m_pa_processor->resetPreviousPA(m_config.pressure_advance.get_at(new_filament_id));
}
//Orca: tool changer or IDEX's firmware may change Z position, so we set it to unknown/undefined //Orca: tool changer or IDEX's firmware may change Z position, so we set it to unknown/undefined
m_last_pos_defined = false; m_last_pos_defined = false;
return gcode; return gcode;
} }
std::string GCode::set_filament_pressure_advance(unsigned int filament_id)
{
const size_t fi = get_filament_config_index(filament_id);
if (!m_config.enable_pressure_advance.get_at(fi))
return {};
// Orca: Adaptive PA
// Reset Adaptive PA processor last PA value
m_pa_processor->resetPreviousPA(m_config.pressure_advance.get_at(fi));
return m_writer.set_pressure_advance(m_config.pressure_advance.get_at(fi));
}
inline std::string polygon_to_string(const Polygon &polygon, Print *print, bool is_print_space = false) { inline std::string polygon_to_string(const Polygon &polygon, Print *print, bool is_print_space = false) {
std::ostringstream gcode; std::ostringstream gcode;
gcode << "["; gcode << "[";
-2
View File
@@ -281,8 +281,6 @@ public:
// extra_retract forwards a PETG pre-extrusion over-extrusion; default 0 -> identical to the plain deretract. // extra_retract forwards a PETG pre-extrusion over-extrusion; default 0 -> identical to the plain deretract.
std::string unretract(float extra_retract = 0.f) { return m_writer.unlift() + m_writer.unretract(extra_retract); } std::string unretract(float extra_retract = 0.f) { return m_writer.unlift() + m_writer.unretract(extra_retract); }
std::string set_extruder(unsigned int extruder_id, double print_z, bool by_object=false, int toolchange_temp_override = -1, bool defer_temp_wait = false); std::string set_extruder(unsigned int extruder_id, double print_z, bool by_object=false, int toolchange_temp_override = -1, bool defer_temp_wait = false);
// Sets the pressure advance of the filament's extruder variant, if enabled for it.
std::string set_filament_pressure_advance(unsigned int filament_id);
bool is_BBL_Printer(); bool is_BBL_Printer();
WipeTowerType wipe_tower_type(); WipeTowerType wipe_tower_type();
+33 -26
View File
@@ -21,31 +21,39 @@ namespace Slic3r {
* *
* @param gcodegen A reference to the GCode object that generates the G-code. * @param gcodegen A reference to the GCode object that generates the G-code.
*/ */
AdaptivePAProcessor::AdaptivePAProcessor(GCode &gcodegen) AdaptivePAProcessor::AdaptivePAProcessor(GCode &gcodegen, const std::vector<unsigned int> &tools_used)
: m_gcodegen(gcodegen), : m_gcodegen(gcodegen),
m_config(gcodegen.config()), m_config(gcodegen.config()),
m_last_predicted_pa(0.0), m_last_predicted_pa(0.0),
m_max_next_feedrate(0.0), m_max_next_feedrate(0.0),
m_next_feedrate(0.0), m_next_feedrate(0.0),
m_current_feedrate(0.0), m_current_feedrate(0.0),
m_last_config_index(-1), m_last_extruder_id(-1),
m_pa_change_pattern(R"(; PA_CHANGE:T(\d+) MM3MM:([0-9]*\.[0-9]+) ACCEL:(\d+) BR:(\d+) RC:(\d+) OV:(\d+))"), m_pa_change_pattern(R"(; PA_CHANGE:T(\d+) MM3MM:([0-9]*\.[0-9]+) ACCEL:(\d+) BR:(\d+) RC:(\d+) OV:(\d+))"),
m_g1_f_pattern(R"(G1 F([0-9]+))") m_g1_f_pattern(R"(G1 F([0-9]+))")
{ {
// Constructor body can be used for further initialization if necessary
for (unsigned int tool : tools_used) {
// Only enable model for the tool if both PA and adaptive PA options are enabled
if(m_config.adaptive_pressure_advance.get_at(tool) && m_config.enable_pressure_advance.get_at(tool)){
auto interpolator = std::make_unique<AdaptivePAInterpolator>();
// Get calibration values from extruder
std::string pa_calibration_values = m_config.adaptive_pressure_advance_model.get_at(tool);
// Setup the model and store it in the tool-interpolation model map
interpolator->parseAndSetData(pa_calibration_values);
m_AdaptivePAInterpolators[tool] = std::move(interpolator);
}
}
} }
// Method to get the interpolator for a specific filament config index. // Method to get the interpolator for a specific tool ID
// The model is built the first time an index is requested, as the indices in use depend on AdaptivePAInterpolator* AdaptivePAProcessor::getInterpolator(unsigned int tool_id) {
// the extruder variant each filament prints with. auto it = m_AdaptivePAInterpolators.find(tool_id);
AdaptivePAInterpolator* AdaptivePAProcessor::getInterpolator(unsigned int config_index) { if (it != m_AdaptivePAInterpolators.end()) {
auto [it, inserted] = m_AdaptivePAInterpolators.try_emplace(config_index);
// Only enable model for the index if both PA and adaptive PA options are enabled
if (inserted && m_config.adaptive_pressure_advance.get_at(config_index) && m_config.enable_pressure_advance.get_at(config_index)) {
it->second = std::make_unique<AdaptivePAInterpolator>();
it->second->parseAndSetData(m_config.adaptive_pressure_advance_model.get_at(config_index));
}
return it->second.get(); return it->second.get();
} }
return nullptr; // Handle the case where the tool_id was not found
}
/** /**
* @brief Processes a layer of G-code and applies adaptive pressure advance. * @brief Processes a layer of G-code and applies adaptive pressure advance.
@@ -100,17 +108,16 @@ std::string AdaptivePAProcessor::process_layer(std::string &&gcode) {
// the PA for that material is set. As no tag below will be found for this extruder, the original PA is retained. // the PA for that material is set. As no tag below will be found for this extruder, the original PA is retained.
if (line.find("; PA_CHANGE") == 0) { // prune lines quickly before running regex check as regex is more expensive to run if (line.find("; PA_CHANGE") == 0) { // prune lines quickly before running regex check as regex is more expensive to run
if (std::regex_search(line, m_match, m_pa_change_pattern)) { if (std::regex_search(line, m_match, m_pa_change_pattern)) {
// The tag carries the filament config index, which selects the PA settings of the filament's extruder variant int extruder_id = std::stoi(m_match[1].str());
int config_index = std::stoi(m_match[1].str());
mm3mm_value = std::stod(m_match[2].str()); mm3mm_value = std::stod(m_match[2].str());
accel_value = std::stod(m_match[3].str()); accel_value = std::stod(m_match[3].str());
int isBridge = std::stoi(m_match[4].str()); int isBridge = std::stoi(m_match[4].str());
int roleChange = std::stoi(m_match[5].str()); int roleChange = std::stoi(m_match[5].str());
int isOverhang = std::stoi(m_match[6].str()); int isOverhang = std::stoi(m_match[6].str());
// Check if the filament config index has changed // Check if the extruder ID has changed
bool config_index_changed = (config_index != m_last_config_index); bool extruder_changed = (extruder_id != m_last_extruder_id);
m_last_config_index = config_index; m_last_extruder_id = extruder_id;
// Save the PA_CHANGE line to output later after finding feedrate // Save the PA_CHANGE line to output later after finding feedrate
pa_change_line = line; pa_change_line = line;
@@ -205,23 +212,23 @@ std::string AdaptivePAProcessor::process_layer(std::string &&gcode) {
// Calculate the predicted PA using the upcomming feature maximum feedrate // Calculate the predicted PA using the upcomming feature maximum feedrate
// Get the interpolator for the active tool // Get the interpolator for the active tool
AdaptivePAInterpolator* interpolator = getInterpolator(m_last_config_index); AdaptivePAInterpolator* interpolator = getInterpolator(m_last_extruder_id);
double predicted_pa = 0; double predicted_pa = 0;
double adaptive_PA_speed = 0; double adaptive_PA_speed = 0;
if(!interpolator){ // Tool not found in the interpolator map if(!interpolator){ // Tool not found in the interpolator map
// Tool not found in the PA interpolator to tool map // Tool not found in the PA interpolator to tool map
predicted_pa = m_config.enable_pressure_advance.get_at(m_last_config_index) ? m_config.pressure_advance.get_at(m_last_config_index) : 0; predicted_pa = m_config.enable_pressure_advance.get_at(m_last_extruder_id) ? m_config.pressure_advance.get_at(m_last_extruder_id) : 0;
if(m_config.gcode_comments) output << "; APA: Tool doesnt have APA enabled\n"; if(m_config.gcode_comments) output << "; APA: Tool doesnt have APA enabled\n";
} else if (!interpolator->isInitialised() || (!m_config.adaptive_pressure_advance.get_at(m_last_config_index)) ) } else if (!interpolator->isInitialised() || (!m_config.adaptive_pressure_advance.get_at(m_last_extruder_id)) )
// Check if the model is not initialised by the constructor for the active extruder // Check if the model is not initialised by the constructor for the active extruder
// Also check that adaptive PA is enabled for that extruder. This should not be needed // Also check that adaptive PA is enabled for that extruder. This should not be needed
// as the PA change flag should not be set upstream (in the GCode.cpp file) if adaptive PA is disabled // as the PA change flag should not be set upstream (in the GCode.cpp file) if adaptive PA is disabled
// however check for robustness sake. // however check for robustness sake.
{ {
// Model failed or adaptive pressure advance not enabled - use default value from m_config // Model failed or adaptive pressure advance not enabled - use default value from m_config
predicted_pa = m_config.enable_pressure_advance.get_at(m_last_config_index) ? m_config.pressure_advance.get_at(m_last_config_index) : 0; predicted_pa = m_config.enable_pressure_advance.get_at(m_last_extruder_id) ? m_config.pressure_advance.get_at(m_last_extruder_id) : 0;
if(m_config.gcode_comments) output << "; APA: Interpolator setup failed, using default pressure advance\n"; if(m_config.gcode_comments) output << "; APA: Interpolator setup failed, using default pressure advance\n";
} else { // Model setup succeeded } else { // Model setup succeeded
// Proceed to identify the print speed to use to calculate the adaptive PA value // Proceed to identify the print speed to use to calculate the adaptive PA value
@@ -242,18 +249,18 @@ std::string AdaptivePAProcessor::process_layer(std::string &&gcode) {
predicted_pa = (*interpolator)(mm3mm_value * adaptive_PA_speed, accel_value); predicted_pa = (*interpolator)(mm3mm_value * adaptive_PA_speed, accel_value);
// This is a bridge, use the dedicated PA setting. // This is a bridge, use the dedicated PA setting.
if(isBridge && m_config.adaptive_pressure_advance_bridges.get_at(m_last_config_index) > EPSILON) if(isBridge && m_config.adaptive_pressure_advance_bridges.get_at(m_last_extruder_id) > EPSILON)
predicted_pa = m_config.adaptive_pressure_advance_bridges.get_at(m_last_config_index); predicted_pa = m_config.adaptive_pressure_advance_bridges.get_at(m_last_extruder_id);
if (predicted_pa < 0) { // If extrapolation fails, fall back to the default PA for the extruder. if (predicted_pa < 0) { // If extrapolation fails, fall back to the default PA for the extruder.
predicted_pa = m_config.enable_pressure_advance.get_at(m_last_config_index) ? m_config.pressure_advance.get_at(m_last_config_index) : 0; predicted_pa = m_config.enable_pressure_advance.get_at(m_last_extruder_id) ? m_config.pressure_advance.get_at(m_last_extruder_id) : 0;
if(m_config.gcode_comments) output << "; APA: Interpolation failed, using fallback pressure advance value\n"; if(m_config.gcode_comments) output << "; APA: Interpolation failed, using fallback pressure advance value\n";
} }
} }
if(m_config.gcode_comments) { if(m_config.gcode_comments) {
// Output debug GCode comments // Output debug GCode comments
output << pa_change_line << '\n'; // Output PA change command tag output << pa_change_line << '\n'; // Output PA change command tag
if(isBridge && m_config.adaptive_pressure_advance_bridges.get_at(m_last_config_index) > EPSILON) if(isBridge && m_config.adaptive_pressure_advance_bridges.get_at(m_last_extruder_id) > EPSILON)
output << "; APA Model Override (bridge)\n"; output << "; APA Model Override (bridge)\n";
output << "; APA Current Speed: " << std::to_string(m_current_feedrate) << "\n"; output << "; APA Current Speed: " << std::to_string(m_current_feedrate) << "\n";
output << "; APA Next Speed: " << std::to_string(m_next_feedrate) << "\n"; output << "; APA Next Speed: " << std::to_string(m_next_feedrate) << "\n";
@@ -262,7 +269,7 @@ std::string AdaptivePAProcessor::process_layer(std::string &&gcode) {
output << "; APA Flow rate: " << std::to_string(mm3mm_value * m_max_next_feedrate) << "\n"; output << "; APA Flow rate: " << std::to_string(mm3mm_value * m_max_next_feedrate) << "\n";
output << "; APA Prev PA: " << std::to_string(m_last_predicted_pa) << " New PA: " << std::to_string(predicted_pa) << "\n"; output << "; APA Prev PA: " << std::to_string(m_last_predicted_pa) << " New PA: " << std::to_string(predicted_pa) << "\n";
} }
if (config_index_changed || std::fabs(predicted_pa - m_last_predicted_pa) > EPSILON) { if (extruder_changed || std::fabs(predicted_pa - m_last_predicted_pa) > EPSILON) {
output << m_gcodegen.writer().set_pressure_advance(predicted_pa); // Use m_writer to set pressure advance output << m_gcodegen.writer().set_pressure_advance(predicted_pa); // Use m_writer to set pressure advance
m_last_predicted_pa = predicted_pa; // Update the last predicted PA value m_last_predicted_pa = predicted_pa; // Update the last predicted PA value
} }
+11 -7
View File
@@ -11,6 +11,7 @@
#include <regex> #include <regex>
#include <memory> #include <memory>
#include <map> #include <map>
#include <vector>
#include "AdaptivePAInterpolator.hpp" #include "AdaptivePAInterpolator.hpp"
namespace Slic3r { namespace Slic3r {
@@ -32,7 +33,7 @@ public:
* *
* @param gcodegen A reference to the GCode object that generates the G-code. * @param gcodegen A reference to the GCode object that generates the G-code.
*/ */
AdaptivePAProcessor(GCode &gcodegen); AdaptivePAProcessor(GCode &gcodegen, const std::vector<unsigned int> &tools_used);
/** /**
* @brief Processes a layer of G-code and applies adaptive pressure advance. * @brief Processes a layer of G-code and applies adaptive pressure advance.
@@ -69,25 +70,28 @@ public:
private: private:
GCode &m_gcodegen; ///< Reference to the GCode object. GCode &m_gcodegen; ///< Reference to the GCode object.
std::unordered_map<unsigned int, std::unique_ptr<AdaptivePAInterpolator>> m_AdaptivePAInterpolators; ///< Map between Interpolator objects and filament config indices (null when adaptive PA is off) std::unordered_map<unsigned int, std::unique_ptr<AdaptivePAInterpolator>> m_AdaptivePAInterpolators; ///< Map between Interpolator objects and tool ID's
const PrintConfig &m_config; ///< Reference to the print configuration. const PrintConfig &m_config; ///< Reference to the print configuration.
double m_last_predicted_pa; ///< Last predicted pressure advance value. double m_last_predicted_pa; ///< Last predicted pressure advance value.
double m_max_next_feedrate; ///< Maximum feed rate (speed) for the upcomming island. If no speed is found, the previous island speed is used. double m_max_next_feedrate; ///< Maximum feed rate (speed) for the upcomming island. If no speed is found, the previous island speed is used.
double m_next_feedrate; ///< First feed rate (speed) for the upcomming island. double m_next_feedrate; ///< First feed rate (speed) for the upcomming island.
double m_current_feedrate; ///< Current, latest feedrate. double m_current_feedrate; ///< Current, latest feedrate.
int m_last_config_index; ///< Filament config index of the last PA_CHANGE tag. int m_last_extruder_id; ///< Last used extruder ID.
std::regex m_pa_change_pattern; ///< Regular expression to detect PA_CHANGE pattern. std::regex m_pa_change_pattern; ///< Regular expression to detect PA_CHANGE pattern.
std::regex m_g1_f_pattern; ///< Regular expression to detect G1 F pattern. std::regex m_g1_f_pattern; ///< Regular expression to detect G1 F pattern.
std::smatch m_match; ///< Match results for regular expressions. std::smatch m_match; ///< Match results for regular expressions.
/** /**
* @brief Get the PA interpolator attached to the specified filament config index. * @brief Get the PA interpolator attached to the specified tool ID.
* *
* @param config_index The filament config index (one per filament and extruder variant) for which the PA interpolation model is to be returned. * This method manually sets the adaptive PA internally held value.
* @return The Adaptive PA Interpolator object corresponding to that index, or nullptr when adaptive PA is off for it. * Call this when changing tools or in any other case where the internally assumed last PA value may be incorrect
*
* @param An integer with the tool ID for which the PA interpolation model is to be returned.
* @return The Adaptive PA Interpolator object corresponding to that tool.
*/ */
AdaptivePAInterpolator* getInterpolator(unsigned int config_index); AdaptivePAInterpolator* getInterpolator(unsigned int tool_id);
}; };
} // namespace Slic3r } // namespace Slic3r
+5 -11
View File
@@ -2439,18 +2439,12 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
if (m_default_region_config.precise_outer_wall && m_default_region_config.wall_sequence != WallSequence::InnerOuter) if (m_default_region_config.precise_outer_wall && m_default_region_config.wall_sequence != WallSequence::InnerOuter)
warn(L("The precise wall option will be ignored for outer-inner or inner-outer-inner wall sequences."), "precise_outer_wall"); warn(L("The precise wall option will be ignored for outer-inner or inner-outer-inner wall sequences."), "precise_outer_wall");
// check adaptive pressure advance model of every extruder variant column of the used filaments // check adaptive pressure advance model
const std::vector<int> &self_index = m_config.filament_self_index.values; for (unsigned int extruder_id : extruders) {
const size_t pa_columns = std::max(m_config.adaptive_pressure_advance_model.size(), size_t(extruders.back()) + 1); if (m_config.adaptive_pressure_advance.get_at(extruder_id) &&
for (size_t column = 0; column < pa_columns; ++column) { m_config.enable_pressure_advance.get_at(extruder_id)) {
// filament_self_index maps a column to its filament once the filament arrays hold one column per variant
const unsigned int filament_id = self_index.size() == pa_columns ? self_index[column] - 1 : column;
if (!std::binary_search(extruders.begin(), extruders.end(), filament_id))
continue;
if (m_config.adaptive_pressure_advance.get_at(column) &&
m_config.enable_pressure_advance.get_at(column)) {
const std::string pa_model = m_config.adaptive_pressure_advance_model.get_at(column); const std::string pa_model = m_config.adaptive_pressure_advance_model.get_at(extruder_id);
if (!pa_model.empty()) { if (!pa_model.empty()) {
std::string validation_error = AdaptivePAProcessor::validate_adaptive_pa_model(pa_model); std::string validation_error = AdaptivePAProcessor::validate_adaptive_pa_model(pa_model);
if (!validation_error.empty()) { if (!validation_error.empty()) {
-30
View File
@@ -9403,10 +9403,6 @@ void PrintConfigDef::handle_legacy_composite(DynamicPrintConfig &config)
} }
config.set_key_value("wiping_volumes_use_custom_matrix", new ConfigOptionBool(custom)); config.set_key_value("wiping_volumes_use_custom_matrix", new ConfigOptionBool(custom));
} }
// Orca: a config saved before a key joined filament_options_with_variant stores it once per filament
// rather than once per filament variant, and one exported by an older CLI may store a single value.
normalize_filament_values_to_variants(config);
} }
const PrintConfigDef print_config_def; const PrintConfigDef print_config_def;
@@ -9509,13 +9505,6 @@ std::set<std::string> filament_options_with_variant = {
"filament_ironing_spacing", "filament_ironing_spacing",
"filament_ironing_inset", "filament_ironing_inset",
"filament_ironing_speed", "filament_ironing_speed",
// Orca: pressure advance
"enable_pressure_advance",
"pressure_advance",
"adaptive_pressure_advance",
"adaptive_pressure_advance_model",
"adaptive_pressure_advance_overhangs",
"adaptive_pressure_advance_bridges",
"activate_air_filtration", "activate_air_filtration",
"activate_air_filtration_during_print", "activate_air_filtration_during_print",
"activate_air_filtration_on_completion", "activate_air_filtration_on_completion",
@@ -10723,25 +10712,6 @@ void set_variant_override(ConfigOptionVectorBase &target, const ConfigOptionVect
target.set_to_index(&source, indices, stride); target.set_to_index(&source, indices, stride);
} }
void normalize_filament_values_to_variants(DynamicPrintConfig &config)
{
const auto *self_index = config.option<ConfigOptionInts>("filament_self_index");
if (self_index == nullptr || self_index->empty())
return;
const int filament_count = *std::max_element(self_index->values.begin(), self_index->values.end());
if (filament_count <= 0 || size_t(filament_count) >= self_index->size())
return;
for (const std::string &key : filament_options_with_variant) {
auto *opt = dynamic_cast<ConfigOptionVectorBase *>(config.option(key));
if (opt == nullptr || (opt->size() != size_t(filament_count) && opt->size() != 1))
continue;
std::unique_ptr<ConfigOption> per_filament(opt->clone());
// set_at() takes the first value for a filament past the end of a single-value vector
for (size_t variant = 0; variant < self_index->size(); ++variant)
opt->set_at(per_filament.get(), variant, self_index->values[variant] - 1);
}
}
//used for object/region config //used for object/region config
//use the smallest of multiple to single //use the smallest of multiple to single
-5
View File
@@ -884,11 +884,6 @@ extern std::set<std::string> empty_options;
void set_variant_override(ConfigOptionVectorBase &target, const ConfigOptionVectorBase &source, void set_variant_override(ConfigOptionVectorBase &target, const ConfigOptionVectorBase &source,
const std::vector<int> &variant_index, int stride = 1); const std::vector<int> &variant_index, int stride = 1);
// Orca: lays every filament_options_with_variant option out one value per filament variant, as
// filament_self_index maps the variants to filaments. An option holding one value per filament, or a
// single value, gives every variant of a filament that filament's value; other lengths are left alone.
void normalize_filament_values_to_variants(DynamicPrintConfig &config);
extern std::set<std::string> filament_dev_options; extern std::set<std::string> filament_dev_options;
extern void update_static_print_config_from_dynamic(ConfigBase& config, const DynamicPrintConfig& dest_config, std::vector<int> variant_index, std::set<std::string>& key_set1, int stride = 1); extern void update_static_print_config_from_dynamic(ConfigBase& config, const DynamicPrintConfig& dest_config, std::vector<int> variant_index, std::set<std::string>& key_set1, int stride = 1);
-6
View File
@@ -263,12 +263,6 @@ extern bool is_path_within_root(const std::string &rel_path, const boost::filesy
// True if a symlink stored at link_rel_path (relative to root) with this target stays inside root: the target // 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. // 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); 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"); }
-23
View File
@@ -70,7 +70,6 @@
#include <boost/shared_ptr.hpp> #include <boost/shared_ptr.hpp>
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <boost/filesystem/path.hpp> #include <boost/filesystem/path.hpp>
#include <boost/nowide/fstream.hpp> #include <boost/nowide/fstream.hpp>
@@ -1128,28 +1127,6 @@ bool is_symlink_target_within_root(const std::string &link_rel_path, const std::
return is_path_within_root((sep == std::string::npos ? std::string() : link_rel_path.substr(0, sep + 1)) + target, root); 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");
+1 -1
View File
@@ -428,7 +428,7 @@ void AuFile::on_dclick(wxMouseEvent &evt)
if (m_type == AddFileButton) if (m_type == AddFileButton)
return; return;
else else
desktop_open_project_attachment(this, m_file_path); wxLaunchDefaultApplication(m_file_path.wstring(), 0);
} }
void AuFile::on_mouse_left_up(wxMouseEvent &evt) void AuFile::on_mouse_left_up(wxMouseEvent &evt)
+6 -8
View File
@@ -171,16 +171,14 @@ void ConfigManipulation::check_adaptive_pressure_advance_model(DynamicPrintConfi
return; return;
const auto* model = config->option<ConfigOptionStrings>("adaptive_pressure_advance_model"); const auto* model = config->option<ConfigOptionStrings>("adaptive_pressure_advance_model");
if (model == nullptr) if (model == nullptr || model->values.empty())
return; return;
// Each extruder variant holds its own model. std::string raw_model;
std::string error; for (const std::string& chunk : model->values)
for (const std::string& variant_model : model->values) { raw_model += chunk;
error = AdaptivePAProcessor::validate_adaptive_pa_model(variant_model);
if (!error.empty()) std::string error = AdaptivePAProcessor::validate_adaptive_pa_model(raw_model);
break;
}
if (!error.empty()) { if (!error.empty()) {
wxString msg_text = _L("Adaptive Pressure Advance model validation failed:\n"); wxString msg_text = _L("Adaptive Pressure Advance model validation failed:\n");
msg_text += from_u8(error); msg_text += from_u8(error);
-24
View File
@@ -30,13 +30,11 @@
#include "AboutDialog.hpp" #include "AboutDialog.hpp"
#include "MsgDialog.hpp" #include "MsgDialog.hpp"
#include "Plater.hpp"
#include "format.hpp" #include "format.hpp"
#include "WebUserLoginDialog.hpp" #include "WebUserLoginDialog.hpp"
#include "libslic3r/Print.hpp" #include "libslic3r/Print.hpp"
#include "libslic3r/Utils.hpp"
namespace Slic3r { namespace Slic3r {
@@ -645,26 +643,4 @@ void desktop_open_any_folder( const std::string& path )
} }
bool desktop_open_project_attachment(wxWindow *parent, const boost::filesystem::path &path)
{
// The auxiliary path is UTF-8, which is what boost::filesystem reads a narrow string as.
const boost::filesystem::path aux_root(wxGetApp().plater()->model().get_auxiliary_file_temp_path());
boost::system::error_code ec;
if (!is_absolute_path_within_root(path, aux_root) || !boost::filesystem::is_regular_file(path, ec))
return false;
// Attachments come with the project and carry no download mark, so the desktop would open them without a warning.
if (!is_safe_to_open_file_name(path.filename().string())) {
MessageDialog dlg(parent,
wxString::Format(_L("\"%s\" is not a plain document, image or model file. Opening it may run it as a "
"program or script on this computer.\n\n"
"Only open attachments from projects you trust. Open it anyway?"),
from_path(path.filename())),
_L("Open attachment"), wxICON_WARNING | wxYES_NO);
if (dlg.ShowModal() != wxID_YES)
return false;
}
return wxLaunchDefaultApplication(from_path(path), 0);
}
} } } }
-3
View File
@@ -86,9 +86,6 @@ extern void about();
extern void desktop_open_datadir_folder(); extern void desktop_open_datadir_folder();
// Ask the destop to open one folder // Ask the destop to open one folder
extern void desktop_open_any_folder(const std::string& path); extern void desktop_open_any_folder(const std::string& path);
// Ask the desktop to open a file from the project's auxiliary directory, after a confirmation
// unless its type is known to be plain content. Returns false if the file was not opened.
extern bool desktop_open_project_attachment(wxWindow *parent, const boost::filesystem::path &path);
} // namespace GUI } // namespace GUI
} // namespace Slic3r } // namespace Slic3r
+15 -27
View File
@@ -286,30 +286,6 @@ static void set_config_values(DynamicPrintConfig *config, const std::string &key
} }
} }
// Orca: a calibration print sets pressure advance explicitly to 0 on each extruder variant that
// has it disabled, so the value stored in the printer does not skew the result. Variants with
// pressure advance enabled keep their own value.
static void zero_pressure_advance_where_disabled(DynamicPrintConfig *filament_config)
{
auto enable_pa = filament_config->option<ConfigOptionBools>("enable_pressure_advance");
auto pa = filament_config->option<ConfigOptionFloats>("pressure_advance");
auto adaptive_pa = filament_config->option<ConfigOptionBools>("adaptive_pressure_advance");
if (!enable_pa || !pa || !adaptive_pa || pa->empty() || adaptive_pa->empty())
return;
// All variant keys share the variant count; widen a short one rather than index past it.
if (pa->size() < enable_pa->size())
pa->resize(enable_pa->size());
if (adaptive_pa->size() < enable_pa->size())
adaptive_pa->resize(enable_pa->size());
for (size_t variant = 0; variant < enable_pa->size(); ++variant) {
if (enable_pa->get_at(variant))
continue;
enable_pa->values[variant] = true;
pa->values[variant] = 0.0;
adaptive_pa->values[variant] = false;
}
}
bool Plater::has_illegal_filename_characters(const wxString& wxs_name) bool Plater::has_illegal_filename_characters(const wxString& wxs_name)
{ {
std::string name = into_u8(wxs_name); std::string name = into_u8(wxs_name);
@@ -17088,7 +17064,11 @@ void Plater::calib_input_shaping_freq(const Calib_Params& params)
set_config_values<double, ConfigOptionFloatsNullable>(print_config, "default_jerk", 0); set_config_values<double, ConfigOptionFloatsNullable>(print_config, "default_jerk", 0);
} }
zero_pressure_advance_where_disabled(filament_config); if (!filament_config->option<ConfigOptionBools>("enable_pressure_advance")->get_at(0)) {
set_config_values<bool, ConfigOptionBools>(filament_config, "enable_pressure_advance", true);
set_config_values<double, ConfigOptionFloatsNullable>(filament_config, "pressure_advance", 0.0);
set_config_values<bool, ConfigOptionBools>(filament_config, "adaptive_pressure_advance", false);
}
printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false}); printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false});
printer_config->set_key_value("input_shaping_emit", new ConfigOptionBool{false}); printer_config->set_key_value("input_shaping_emit", new ConfigOptionBool{false});
@@ -17150,7 +17130,11 @@ void Plater::calib_input_shaping_damp(const Calib_Params& params)
set_config_values<double, ConfigOptionFloatsNullable>(print_config, "default_jerk", 0); set_config_values<double, ConfigOptionFloatsNullable>(print_config, "default_jerk", 0);
} }
zero_pressure_advance_where_disabled(filament_config); if (!filament_config->option<ConfigOptionBools>("enable_pressure_advance")->get_at(0)) {
set_config_values<bool, ConfigOptionBools>(filament_config, "enable_pressure_advance", true);
set_config_values<double, ConfigOptionFloatsNullable>(filament_config, "pressure_advance", 0.0);
set_config_values<bool, ConfigOptionBools>(filament_config, "adaptive_pressure_advance", false);
}
printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false}); printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false});
printer_config->set_key_value("input_shaping_emit", new ConfigOptionBool{false}); printer_config->set_key_value("input_shaping_emit", new ConfigOptionBool{false});
@@ -17212,7 +17196,11 @@ void Plater::Calib_Cornering(const Calib_Params& params)
set_config_values<double, ConfigOptionFloatsNullable>(print_config, "default_jerk", 0); set_config_values<double, ConfigOptionFloatsNullable>(print_config, "default_jerk", 0);
} }
zero_pressure_advance_where_disabled(filament_config); if (!filament_config->option<ConfigOptionBools>("enable_pressure_advance")->get_at(0)) {
set_config_values<bool, ConfigOptionBools>(filament_config, "enable_pressure_advance", true);
set_config_values<double, ConfigOptionFloatsNullable>(filament_config, "pressure_advance", 0.0);
set_config_values<bool, ConfigOptionBools>(filament_config, "adaptive_pressure_advance", false);
}
printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false}); printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false});
printer_config->set_key_value("input_shaping_emit", new ConfigOptionBool{true}); printer_config->set_key_value("input_shaping_emit", new ConfigOptionBool{true});
+4 -2
View File
@@ -293,8 +293,10 @@ void ProjectPanel::OnScriptMessage(wxWebViewEvent& evt)
if (!accessory_path.empty()) { if (!accessory_path.empty()) {
std::string decode_path = wxGetApp().url_decode(accessory_path.ToStdString()); std::string decode_path = wxGetApp().url_decode(accessory_path.ToStdString());
fs::path path(decode_path); fs::path path(decode_path);
if (!desktop_open_project_attachment(this, path))
BOOST_LOG_TRIVIAL(warning) << "open_3mf_accessory: not opening " << decode_path; if (fs::exists(path)) {
wxLaunchDefaultApplication(path.wstring(), 0);
}
} }
} }
else if (strCmd == "request_3mf_info") { else if (strCmd == "request_3mf_info") {
+19 -20
View File
@@ -4415,15 +4415,15 @@ void TabFilament::build()
optgroup->append_single_option_line("pellet_flow_coefficient", "printer_basic_information_advanced#pellet-modded-printer"); optgroup->append_single_option_line("pellet_flow_coefficient", "printer_basic_information_advanced#pellet-modded-printer");
optgroup->append_single_option_line("filament_flow_ratio", "material_flow_ratio_and_pressure_advance#flow-ratio", 0); optgroup->append_single_option_line("filament_flow_ratio", "material_flow_ratio_and_pressure_advance#flow-ratio", 0);
optgroup->append_single_option_line("enable_pressure_advance", "material_flow_ratio_and_pressure_advance#pressure-advance", 0); optgroup->append_single_option_line("enable_pressure_advance", "material_flow_ratio_and_pressure_advance#pressure-advance");
optgroup->append_single_option_line("pressure_advance", "material_flow_ratio_and_pressure_advance#pressure-advance", 0); optgroup->append_single_option_line("pressure_advance", "material_flow_ratio_and_pressure_advance#pressure-advance");
// Orca: adaptive pressure advance and calibration model // Orca: adaptive pressure advance and calibration model
optgroup->append_single_option_line("adaptive_pressure_advance", "material_flow_ratio_and_pressure_advance#enable-adaptive-pressure-advance-beta", 0); optgroup->append_single_option_line("adaptive_pressure_advance", "material_flow_ratio_and_pressure_advance#enable-adaptive-pressure-advance-beta");
optgroup->append_single_option_line("adaptive_pressure_advance_overhangs", "material_flow_ratio_and_pressure_advance#enable-adaptive-pressure-advance-for-overhangs-beta", 0); optgroup->append_single_option_line("adaptive_pressure_advance_overhangs", "material_flow_ratio_and_pressure_advance#enable-adaptive-pressure-advance-for-overhangs-beta");
optgroup->append_single_option_line("adaptive_pressure_advance_bridges", "material_flow_ratio_and_pressure_advance#pressure-advance-for-bridges", 0); optgroup->append_single_option_line("adaptive_pressure_advance_bridges", "material_flow_ratio_and_pressure_advance#pressure-advance-for-bridges");
Option option = optgroup->get_option("adaptive_pressure_advance_model", 0); Option option = optgroup->get_option("adaptive_pressure_advance_model");
option.opt.full_width = true; option.opt.full_width = true;
option.opt.is_code = true; option.opt.is_code = true;
option.opt.height = 15; option.opt.height = 15;
@@ -4432,7 +4432,7 @@ void TabFilament::build()
DynamicPrintConfig& filament_config = m_preset_bundle->filaments.get_edited_preset().config; DynamicPrintConfig& filament_config = m_preset_bundle->filaments.get_edited_preset().config;
update_dirty(); update_dirty();
if (opt_key.substr(0, opt_key.find('#')) == "adaptive_pressure_advance_model") if (opt_key == "adaptive_pressure_advance_model")
m_config_manipulation.check_adaptive_pressure_advance_model(&filament_config); m_config_manipulation.check_adaptive_pressure_advance_model(&filament_config);
on_value_change(opt_key, value); on_value_change(opt_key, value);
@@ -4829,11 +4829,8 @@ void TabFilament::toggle_options()
} }
if (m_active_page->title() == L("Filament")) if (m_active_page->title() == L("Filament"))
{ {
const int selection = m_variant_combo ? m_variant_combo->GetSelection() : 0; bool pa = m_config->opt_bool("enable_pressure_advance", 0);
const unsigned int variant_idx = (unsigned int) std::max(selection, 0); toggle_option("pressure_advance", pa);
bool pa = m_config->opt_bool("enable_pressure_advance", variant_idx);
toggle_option("pressure_advance", pa, 0);
//Orca: Enable the plates that should be visible when multi bed support is enabled or a BBL printer is selected; otherwise, enable only the plate visible for the selected bed type. //Orca: Enable the plates that should be visible when multi bed support is enabled or a BBL printer is selected; otherwise, enable only the plate visible for the selected bed type.
DynamicConfig& proj_cfg = m_preset_bundle->project_config; DynamicConfig& proj_cfg = m_preset_bundle->project_config;
@@ -4861,12 +4858,12 @@ void TabFilament::toggle_options()
// Orca: adaptive pressure advance and calibration model // Orca: adaptive pressure advance and calibration model
// If PA is not enabled, disable adaptive pressure advance and hide the model section // If PA is not enabled, disable adaptive pressure advance and hide the model section
// If adaptive PA is not enabled, hide the adaptive PA model section // If adaptive PA is not enabled, hide the adaptive PA model section
toggle_option("adaptive_pressure_advance", pa, 0); toggle_option("adaptive_pressure_advance", pa);
toggle_option("adaptive_pressure_advance_overhangs", pa, 0); toggle_option("adaptive_pressure_advance_overhangs", pa);
bool has_adaptive_pa = m_config->opt_bool("adaptive_pressure_advance", variant_idx); bool has_adaptive_pa = m_config->opt_bool("adaptive_pressure_advance", 0);
toggle_line("adaptive_pressure_advance_overhangs", has_adaptive_pa && pa, 0); toggle_line("adaptive_pressure_advance_overhangs", has_adaptive_pa && pa);
toggle_line("adaptive_pressure_advance_model", has_adaptive_pa && pa, 0); toggle_line("adaptive_pressure_advance_model", has_adaptive_pa && pa);
toggle_line("adaptive_pressure_advance_bridges", has_adaptive_pa && pa, 0); toggle_line("adaptive_pressure_advance_bridges", has_adaptive_pa && pa);
bool is_pellet_printer = printer_cfg.opt_bool("pellet_modded_printer"); bool is_pellet_printer = printer_cfg.opt_bool("pellet_modded_printer");
toggle_line("pellet_flow_coefficient", is_pellet_printer); toggle_line("pellet_flow_coefficient", is_pellet_printer);
@@ -4874,6 +4871,8 @@ void TabFilament::toggle_options()
toggle_line("activate_chamber_temp_control", printer_cfg.opt_bool("support_chamber_temp_control")); toggle_line("activate_chamber_temp_control", printer_cfg.opt_bool("support_chamber_temp_control"));
const int selection = m_variant_combo ? m_variant_combo->GetSelection() : 0;
const unsigned int variant_idx = (unsigned int) std::max(selection, 0);
std::string volumetric_speed_cos = m_config->opt_string("volumetric_speed_coefficients", variant_idx); std::string volumetric_speed_cos = m_config->opt_string("volumetric_speed_coefficients", variant_idx);
bool enable_fit = volumetric_speed_cos != "0 0 0 0 0 0"; bool enable_fit = volumetric_speed_cos != "0 0 0 0 0 0";
toggle_option("filament_adaptive_volumetric_speed", enable_fit, 256 + variant_idx); toggle_option("filament_adaptive_volumetric_speed", enable_fit, 256 + variant_idx);
@@ -5206,6 +5205,7 @@ void TabPrinter::build_fff()
optgroup->append_single_option_line("adaptive_bed_mesh_margin", "printer_basic_information_adaptive_bed_mesh#mesh-margin"); optgroup->append_single_option_line("adaptive_bed_mesh_margin", "printer_basic_information_adaptive_bed_mesh#mesh-margin");
optgroup = page->new_optgroup(L("Accessory"), "param_accessory"); optgroup = page->new_optgroup(L("Accessory"), "param_accessory");
optgroup->append_single_option_line("nozzle_type", "printer_basic_information_accessory#nozzle-type", 0);
optgroup->append_single_option_line("nozzle_hrc", "printer_basic_information_accessory#nozzle-hrc"); optgroup->append_single_option_line("nozzle_hrc", "printer_basic_information_accessory#nozzle-hrc");
optgroup->append_single_option_line("auxiliary_fan", "printer_basic_information_accessory#auxiliary-part-cooling-fan"); optgroup->append_single_option_line("auxiliary_fan", "printer_basic_information_accessory#auxiliary-part-cooling-fan");
optgroup->append_single_option_line("fan_direction"); optgroup->append_single_option_line("fan_direction");
@@ -5720,7 +5720,6 @@ if (is_marlin_flavor)
auto optgroup = page->new_optgroup(L("Basic information"), L"param_information", -1, true); auto optgroup = page->new_optgroup(L("Basic information"), L"param_information", -1, true);
optgroup->append_single_option_line("nozzle_diameter", "printer_extruder_basic_information#nozzle-diameter", extruder_idx); optgroup->append_single_option_line("nozzle_diameter", "printer_extruder_basic_information#nozzle-diameter", extruder_idx);
optgroup->append_single_option_line("nozzle_type", "printer_basic_information_accessory#nozzle-type", extruder_idx);
//optgroup->append_single_option_line("nozzle_volume_type", "", extruder_idx); //optgroup->append_single_option_line("nozzle_volume_type", "", extruder_idx);
optgroup->append_single_option_line("nozzle_volume", "printer_extruder_basic_information#nozzle-volume", extruder_idx); optgroup->append_single_option_line("nozzle_volume", "printer_extruder_basic_information#nozzle-volume", extruder_idx);
@@ -5732,7 +5731,7 @@ if (is_marlin_flavor)
optgroup->m_on_change = [this, extruder_idx](const t_config_option_key& opt_key, boost::any value) optgroup->m_on_change = [this, extruder_idx](const t_config_option_key& opt_key, boost::any value)
{ {
bool is_SEMM = m_config->opt_bool("single_extruder_multi_material"); bool is_SEMM = m_config->opt_bool("single_extruder_multi_material");
if (is_SEMM && m_extruders_count > 1 && boost::starts_with(opt_key, "nozzle_diameter")) if (is_SEMM && m_extruders_count > 1 && opt_key.find_first_of("nozzle_diameter") != std::string::npos)
{ {
SuppressBackgroundProcessingUpdate sbpu; SuppressBackgroundProcessingUpdate sbpu;
const double new_nd = boost::any_cast<double>(value); const double new_nd = boost::any_cast<double>(value);
+3 -4
View File
@@ -31,15 +31,14 @@ int UserManager::parse_json(std::string payload)
{ {
bool restored_json = false; bool restored_json = false;
json j; json j;
//bind/unbind
try {
json j_pre = json::parse(payload); json j_pre = json::parse(payload);
if (j_pre.empty()) { if (j_pre.empty()) {
return -1; return -1;
} }
//bind/unbind
try {
if (j_pre.contains("bind")) { if (j_pre.contains("bind")) {
if (j_pre["bind"].contains("command")) { if (j_pre["bind"].contains("command")) {
+9
View File
@@ -274,4 +274,13 @@ bool Duet::start_print(wxString &msg, const std::string &filename, ConnectionTyp
return res; return res;
} }
int Duet::get_err_code_from_body(const std::string &body) const
{
pt::ptree root;
std::istringstream iss (body); // wrap returned json to istringstream
pt::read_json(iss, root);
return root.get<int>("err", 0);
}
} }
+1
View File
@@ -40,6 +40,7 @@ private:
ConnectionType connect(wxString &msg) const; ConnectionType connect(wxString &msg) const;
void disconnect(ConnectionType connectionType) const; void disconnect(ConnectionType connectionType) const;
bool start_print(wxString &msg, const std::string &filename, ConnectionType connectionType, bool simulationMode) const; bool start_print(wxString &msg, const std::string &filename, ConnectionType connectionType, bool simulationMode) const;
int get_err_code_from_body(const std::string &body) const;
}; };
} }
+9
View File
@@ -146,6 +146,15 @@ bool ESP3D::start_print(wxString& msg, const std::string& filename) const
return ret; return ret;
} }
int ESP3D::get_err_code_from_body(const std::string& body) const
{
pt::ptree root;
std::istringstream iss(body); // wrap returned json to istringstream
pt::read_json(iss, root);
return root.get<int>("err", 0);
}
// ESP3D only accepts 8.3 filenames else it crashes marlin and other undefined behaviour // ESP3D only accepts 8.3 filenames else it crashes marlin and other undefined behaviour
std::string ESP3D::get_short_name(const std::string& filename) const std::string ESP3D::get_short_name(const std::string& filename) const
{ {
+1
View File
@@ -33,6 +33,7 @@ private:
std::string m_console_port; std::string m_console_port;
bool start_print(wxString& msg, const std::string& filename) const; bool start_print(wxString& msg, const std::string& filename) const;
int get_err_code_from_body(const std::string& body) const;
std::string get_short_name(const std::string& filename) const; std::string get_short_name(const std::string& filename) const;
std::string format_command(const std::string& path, const std::string& arg, const std::string& val) const; std::string format_command(const std::string& path, const std::string& arg, const std::string& val) const;
}; };
+15 -25
View File
@@ -510,22 +510,12 @@ bool Flashforge::fetch_material_slots(std::vector<FlashforgeMaterialSlot>& slots
if (!request_local_api_json("detail", json{{"serialNumber", m_serial_number}, {"checkCode", m_check_code}}.dump(), body, msg)) if (!request_local_api_json("detail", json{{"serialNumber", m_serial_number}, {"checkCode", m_check_code}}.dump(), body, msg))
return false; return false;
if (!parse_material_slots(body, slots, supports_material_station)) { const auto parsed = json::parse(body, nullptr, false, true);
if (parsed.is_discarded()) {
msg = _(L("Flashforge returned an invalid JSON response.")); msg = _(L("Flashforge returned an invalid JSON response."));
return false; return false;
} }
return true;
}
bool Flashforge::parse_material_slots(const std::string& body, std::vector<FlashforgeMaterialSlot>& slots, bool* supports_material_station)
{
slots.clear();
const auto parsed = json::parse(body, nullptr, false, true);
if (parsed.is_discarded())
return false;
const auto& detail = parsed.contains("detail") ? parsed["detail"] : parsed; const auto& detail = parsed.contains("detail") ? parsed["detail"] : parsed;
const auto& station = detail.contains("matlStationInfo") ? detail["matlStationInfo"] : const auto& station = detail.contains("matlStationInfo") ? detail["matlStationInfo"] :
detail.contains("MatlStationInfo") ? detail["MatlStationInfo"] : json(); detail.contains("MatlStationInfo") ? detail["MatlStationInfo"] : json();
@@ -552,21 +542,12 @@ bool Flashforge::parse_material_slots(const std::string& body, std::vector<Flash
if (supports_material_station != nullptr) if (supports_material_station != nullptr)
*supports_material_station = reports_material_station; *supports_material_station = reports_material_station;
// Fields are read leniently: firmware may send numbers as strings or flags as numbers.
for (const auto& slot : slot_infos) { for (const auto& slot : slot_infos) {
if (!slot.is_object())
continue;
FlashforgeMaterialSlot info; FlashforgeMaterialSlot info;
info.slot_id = static_cast<int>(slots.size()) + 1; info.slot_id = slot.value("slotId", static_cast<int>(slots.size()) + 1);
if (const auto it = slot.find("slotId"); it != slot.end()) info.has_filament = slot.value("hasFilament", false);
try_parse_json_int(*it, info.slot_id); info.material_name = slot.value("materialName", std::string());
int has_filament = 0; info.material_color = slot.value("materialColor", std::string());
if (const auto it = slot.find("hasFilament"); it != slot.end() && try_parse_json_int(*it, has_filament))
info.has_filament = has_filament != 0;
if (const auto it = slot.find("materialName"); it != slot.end() && it->is_string())
info.material_name = it->get<std::string>();
if (const auto it = slot.find("materialColor"); it != slot.end() && it->is_string())
info.material_color = it->get<std::string>();
slots.emplace_back(std::move(info)); slots.emplace_back(std::move(info));
} }
@@ -689,4 +670,13 @@ std::string Flashforge::extract_host_name() const
return out; return out;
} }
int Flashforge::get_err_code_from_body(const std::string& body) const
{
pt::ptree root;
std::istringstream iss(body); // wrap returned json to istringstream
pt::read_json(iss, root);
return root.get<int>("err", 0);
}
} // namespace Slic3r } // namespace Slic3r
+1 -2
View File
@@ -45,8 +45,6 @@ public:
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; } PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
std::string get_host() const override { return m_host; } std::string get_host() const override { return m_host; }
bool fetch_material_slots(std::vector<FlashforgeMaterialSlot>& slots, bool* supports_material_station, wxString& msg) const; bool fetch_material_slots(std::vector<FlashforgeMaterialSlot>& slots, bool* supports_material_station, wxString& msg) const;
// Parses a local API "detail" reply. Returns false when the body is not valid JSON.
static bool parse_material_slots(const std::string& body, std::vector<FlashforgeMaterialSlot>& slots, bool* supports_material_station);
static bool discover_printers(std::vector<FlashforgeDiscoveredPrinter>& printers, wxString& msg, int timeout_ms = 10000, int idle_timeout_ms = 1500, int max_retries = 3); static bool discover_printers(std::vector<FlashforgeDiscoveredPrinter>& printers, wxString& msg, int timeout_ms = 10000, int idle_timeout_ms = 1500, int max_retries = 3);
private: private:
@@ -70,6 +68,7 @@ private:
bool request_local_api_json(const std::string& path, const std::string& body, std::string& response_body, wxString& error_msg) const; bool request_local_api_json(const std::string& path, const std::string& body, std::string& response_body, wxString& error_msg) const;
std::string make_http_url(const std::string& path) const; std::string make_http_url(const std::string& path) const;
std::string extract_host_name() const; std::string extract_host_name() const;
int get_err_code_from_body(const std::string &body) const;
bool connect(wxString& msg) const; bool connect(wxString& msg) const;
bool start_print(wxString& msg, const std::string& filename) const; bool start_print(wxString& msg, const std::string& filename) const;
}; };
+9
View File
@@ -141,4 +141,13 @@ bool MKS::start_print(wxString& msg, const std::string& filename) const
return ret; return ret;
} }
int MKS::get_err_code_from_body(const std::string& body) const
{
pt::ptree root;
std::istringstream iss(body); // wrap returned json to istringstream
pt::read_json(iss, root);
return root.get<int>("err", 0);
}
} // Slic3r } // Slic3r
+1
View File
@@ -34,6 +34,7 @@ private:
std::string get_upload_url(const std::string& filename) const; std::string get_upload_url(const std::string& filename) const;
bool start_print(wxString& msg, const std::string& filename) const; bool start_print(wxString& msg, const std::string& filename) const;
int get_err_code_from_body(const std::string& body) const;
}; };
} }
+9 -42
View File
@@ -3,13 +3,10 @@
#include <vector> #include <vector>
#include <thread> #include <thread>
#include <exception> #include <exception>
#include <sstream>
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <wx/string.h> #include <wx/string.h>
#include <wx/app.h> #include <wx/app.h>
@@ -175,20 +172,6 @@ std::string moonraker_error_reason(const std::string &body)
} // namespace } // namespace
int PrintHost::get_err_code_from_body(const std::string &body)
{
boost::property_tree::ptree root;
std::istringstream iss(body);
try {
boost::property_tree::read_json(iss, root);
} catch (const std::exception &ex) {
BOOST_LOG_TRIVIAL(error) << "PrintHost: response is not valid JSON: " << ex.what();
return -1;
}
return root.get<int>("err", 0);
}
wxString PrintHost::format_error(const std::string &body, const std::string &error, unsigned status) const wxString PrintHost::format_error(const std::string &body, const std::string &error, unsigned status) const
{ {
if (status != 0) { if (status != 0) {
@@ -432,10 +415,12 @@ void PrintHostJobQueue::priv::remove_source()
source_to_remove.clear(); source_to_remove.clear();
} }
bool PrintHostJobQueue::upload_job(PrintHostJob &job, PrintHost::ProgressFn progress_fn, PrintHost::ErrorFn error_fn, PrintHost::InfoFn info_fn) void PrintHostJobQueue::priv::perform_job(PrintHostJob the_job)
{ {
emit_progress(0); // Indicate the upload is starting
// Captured before upload_data is moved into upload() below. // Captured before upload_data is moved into upload() below.
const std::string upload_filename = job.upload_data.source_path.filename().string(); const std::string upload_filename = the_job.upload_data.source_path.filename().string();
{ {
LifecycleEventContext ctx; LifecycleEventContext ctx;
@@ -444,37 +429,19 @@ bool PrintHostJobQueue::upload_job(PrintHostJob &job, PrintHost::ProgressFn prog
fire_lifecycle_event(LifecycleEvent::UploadStarted, ctx); fire_lifecycle_event(LifecycleEvent::UploadStarted, ctx);
} }
bool success = false; bool success = the_job.printhost->upload(std::move(the_job.upload_data),
std::string error; [this](Http::Progress progress, bool &cancel) { this->progress_fn(std::move(progress), cancel); },
// A throwing upload must not stop the worker, or later jobs would stay queued forever. [this](wxString error) { this->error_fn(std::move(error)); },
try { [this](wxString tag, wxString host) { this->info_fn(std::move(tag), std::move(host)); }
success = job.printhost->upload(std::move(job.upload_data), std::move(progress_fn), error_fn, std::move(info_fn)); );
} catch (const std::exception &e) {
error = e.what();
error_fn(error);
}
{ {
LifecycleEventContext ctx; LifecycleEventContext ctx;
ctx.name = upload_filename; ctx.name = upload_filename;
ctx.code = success ? LifecycleEvtCode::Ok : LifecycleEvtCode::Error; ctx.code = success ? LifecycleEvtCode::Ok : LifecycleEvtCode::Error;
ctx.msg = error;
fire_lifecycle_event(LifecycleEvent::UploadFinished, ctx); fire_lifecycle_event(LifecycleEvent::UploadFinished, ctx);
} }
return success;
}
void PrintHostJobQueue::priv::perform_job(PrintHostJob the_job)
{
emit_progress(0); // Indicate the upload is starting
bool success = PrintHostJobQueue::upload_job(the_job,
[this](Http::Progress progress, bool &cancel) { this->progress_fn(std::move(progress), cancel); },
[this](wxString error) { this->error_fn(std::move(error)); },
[this](wxString tag, wxString host) { this->info_fn(std::move(tag), std::move(host)); }
);
if (success) { if (success) {
emit_progress(100); emit_progress(100);
if (the_job.switch_to_device_tab) { if (the_job.switch_to_device_tab) {
-6
View File
@@ -87,8 +87,6 @@ public:
static PrintHost* get_print_host(DynamicPrintConfig *config); static PrintHost* get_print_host(DynamicPrintConfig *config);
static std::string get_print_host_webui(DynamicPrintConfig *config); static std::string get_print_host_webui(DynamicPrintConfig *config);
// Reads the "err" field of a JSON reply, 0 when absent. Returns -1 when the body is not valid JSON.
static int get_err_code_from_body(const std::string &body);
//Support for cloud webui login //Support for cloud webui login
virtual bool is_cloud() const { return false; } virtual bool is_cloud() const { return false; }
@@ -152,10 +150,6 @@ public:
void enqueue(PrintHostJob job); void enqueue(PrintHostJob job);
void cancel(size_t id); void cancel(size_t id);
// Uploads the job, firing UploadStarted and a matching UploadFinished. An exception thrown by
// the upload is reported through error_fn and makes the upload fail.
static bool upload_job(PrintHostJob &job, PrintHost::ProgressFn progress_fn, PrintHost::ErrorFn error_fn, PrintHost::InfoFn info_fn);
private: private:
struct priv; struct priv;
std::shared_ptr<priv> p; std::shared_ptr<priv> p;
+9
View File
@@ -654,4 +654,13 @@ bool UltiMaker::start_print(wxString &msg, const std::string &filename, Connecti
return res; return res;
} }
int UltiMaker::get_err_code_from_body(const std::string &body) const
{
pt::ptree root;
std::istringstream iss (body); // wrap returned json to istringstream
pt::read_json(iss, root);
return root.get<int>("err", 0);
}
} }
+1
View File
@@ -64,6 +64,7 @@ private:
void set_auth(Http& http) const; void set_auth(Http& http) const;
void disconnect(ConnectionType connectionType) const; void disconnect(ConnectionType connectionType) const;
bool start_print(wxString &msg, const std::string &filename, ConnectionType connectionType) const; bool start_print(wxString &msg, const std::string &filename, ConnectionType connectionType) const;
int get_err_code_from_body(const std::string &body) const;
}; };
} }
-125
View File
@@ -768,128 +768,3 @@ TEST_CASE("Each filament prints with its variant of the extruder's variant strin
CHECK(gcode.find("; nozzle_temperature = " + resolved + "\n") != std::string::npos); 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);
}
}
@@ -717,15 +717,3 @@ 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}));
}
+9 -54
View File
@@ -19,8 +19,9 @@ struct LoadedObj
ObjInfo info; ObjInfo info;
}; };
// Loads an OBJ made of the given lines, with a material library that defines material "a". // A tetrahedron with a material and two texture coordinates, (0.25, 0.5) and (0.75, 1).
LoadedObj load_textured_obj(const std::string &body) // Only the first face and the vt lines are varied; the other three faces reference vt 1.
LoadedObj load_textured_tetrahedron(const std::string &first_face, const std::string &vts = "vt 0.25 0.5\nvt 0.75 1\n")
{ {
ScopedTemporaryFile obj(".obj"); ScopedTemporaryFile obj(".obj");
ScopedTemporaryFile mtl(".mtl"); ScopedTemporaryFile mtl(".mtl");
@@ -30,7 +31,12 @@ LoadedObj load_textured_obj(const std::string &body)
} }
{ {
boost::nowide::ofstream out(obj.string()); boost::nowide::ofstream out(obj.string());
out << "mtllib " << mtl.path().filename().string() << "\n" << body; out << "mtllib " << mtl.path().filename().string() << "\n"
<< "v 0 0 0\nv 10 0 0\nv 0 10 0\nv 0 0 10\n"
<< vts
<< "usemtl a\n"
<< first_face << "\n"
<< "f 1/1 2/1 4/1\nf 1/1 4/1 3/1\nf 2/1 3/1 4/1\n";
} }
LoadedObj loaded; LoadedObj loaded;
std::string message; std::string message;
@@ -38,21 +44,6 @@ LoadedObj load_textured_obj(const std::string &body)
return loaded; return loaded;
} }
// A tetrahedron with a material and two texture coordinates, (0.25, 0.5) and (0.75, 1).
// Only the first face and the vt lines are varied; the other three faces reference vt 1.
LoadedObj load_textured_tetrahedron(const std::string &first_face, const std::string &vts = "vt 0.25 0.5\nvt 0.75 1\n")
{
return load_textured_obj("v 0 0 0\nv 10 0 0\nv 0 10 0\nv 0 0 10\n" + vts + "usemtl a\n" + first_face + "\n" +
"f 1/1 2/1 4/1\nf 1/1 4/1 3/1\nf 2/1 3/1 4/1\n");
}
// Texture coordinate n is (n / 10, n / 20), so a UV identifies the vt it came from.
void check_uv_is_vt(const Vec2f &uv, int vt)
{
CHECK_THAT(uv.x(), WithinAbs(vt / 10., 1e-6));
CHECK_THAT(uv.y(), WithinAbs(vt / 20., 1e-6));
}
} // namespace } // namespace
TEST_CASE("An out-of-range texture index falls back to a zero UV and keeps the geometry", "[OBJ][Regression]") TEST_CASE("An out-of-range texture index falls back to a zero UV and keeps the geometry", "[OBJ][Regression]")
@@ -129,39 +120,3 @@ TEST_CASE("A texture coordinate with w does not shift the indices of the ones af
CHECK_THAT(uv[2].x(), WithinAbs(0.75, 1e-6)); CHECK_THAT(uv[2].x(), WithinAbs(0.75, 1e-6));
CHECK_THAT(uv[2].y(), WithinAbs(1., 1e-6)); CHECK_THAT(uv[2].y(), WithinAbs(1., 1e-6));
} }
TEST_CASE("Both triangles of a quad take the texture coordinates of their own corners", "[OBJ][Regression]")
{
const LoadedObj loaded = load_textured_obj("v 0 0 0\nv 10 0 0\nv 10 10 0\nv 0 10 0\n"
"vt 0.1 0.05\nvt 0.2 0.1\nvt 0.3 0.15\nvt 0.4 0.2\n"
"usemtl a\n"
"f 1/1 2/2 3/3 4/4\n");
REQUIRE(loaded.ok);
REQUIRE(loaded.mesh.facets_count() == 2);
REQUIRE(loaded.info.uvs.size() == 2);
check_uv_is_vt(loaded.info.uvs[0][0], 1);
check_uv_is_vt(loaded.info.uvs[0][1], 2);
check_uv_is_vt(loaded.info.uvs[0][2], 3);
check_uv_is_vt(loaded.info.uvs[1][0], 1);
check_uv_is_vt(loaded.info.uvs[1][1], 3);
check_uv_is_vt(loaded.info.uvs[1][2], 4);
}
TEST_CASE("Texture coordinates follow the corners of a mesh that is flipped on load", "[OBJ][Regression]")
{
// The faces wind inwards, so the loader flips them. Vertex n uses vt n.
const LoadedObj loaded = load_textured_obj("v 0 0 0\nv 10 0 0\nv 0 10 0\nv 0 0 10\n"
"vt 0.1 0.05\nvt 0.2 0.1\nvt 0.3 0.15\nvt 0.4 0.2\n"
"usemtl a\n"
"f 1/1 2/2 3/3\nf 1/1 4/4 2/2\nf 1/1 3/3 4/4\nf 2/2 4/4 3/3\n");
REQUIRE(loaded.ok);
const indexed_triangle_set &its = loaded.mesh.its;
CHECK(its_volume(its) > 0.f);
REQUIRE(its.indices.size() == 4);
REQUIRE(loaded.info.uvs.size() == 4);
for (size_t face = 0; face < its.indices.size(); ++face)
for (int corner = 0; corner < 3; ++corner)
check_uv_is_vt(loaded.info.uvs[face][corner], its.indices[face][corner] + 1);
}
@@ -5482,39 +5482,3 @@ 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 });
}
-65
View File
@@ -322,68 +322,3 @@ TEST_CASE("is_symlink_target_within_root rejects a target that passes through a
CHECK(is_symlink_target_within_root("link", "in/lib.so", root)); CHECK(is_symlink_target_within_root("link", "in/lib.so", root));
} }
#endif #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));
}
-1
View File
@@ -28,7 +28,6 @@ add_executable(${_TEST_NAME}_tests
test_plugin_audit.cpp test_plugin_audit.cpp
test_shortcuts.cpp test_shortcuts.cpp
test_file_url.cpp test_file_url.cpp
test_user_manager.cpp
../fff_print/test_helpers.cpp ../fff_print/test_helpers.cpp
) )
-161
View File
@@ -1,15 +1,8 @@
#include <catch2/catch_all.hpp> #include <catch2/catch_all.hpp>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include "libslic3r/LifecycleEvents.hpp"
#include "slic3r/Utils/PrintHost.hpp" #include "slic3r/Utils/PrintHost.hpp"
#include "slic3r/Utils/Flashforge.hpp"
using namespace Slic3r; using namespace Slic3r;
@@ -31,41 +24,6 @@ public:
std::string get_host() const override { return {}; } std::string get_host() const override { return {}; }
}; };
class ThrowingPrintHost : public TestPrintHost
{
public:
bool upload(PrintHostUpload, ProgressFn, ErrorFn, InfoFn) const override { throw std::runtime_error("reply could not be read"); }
};
struct UploadEvents
{
std::vector<LifecycleEvent> events;
std::vector<LifecycleEvtCode> codes;
std::vector<std::string> errors;
bool uploaded{false};
explicit UploadEvents(std::unique_ptr<PrintHost> host)
{
set_lifecycle_hook_fn([this](LifecycleEvent event, const LifecycleEventContext& ctx) {
events.push_back(event);
codes.push_back(ctx.code);
});
PrintHostJob job;
job.printhost = std::move(host);
job.upload_data.source_path = "plate.gcode";
try {
uploaded = PrintHostJobQueue::upload_job(job, [](Http::Progress, bool&) {},
[this](wxString error) { errors.push_back(error.ToStdString()); },
[](wxString, wxString) {});
} catch (...) {
set_lifecycle_hook_fn(nullptr);
throw;
}
set_lifecycle_hook_fn(nullptr);
}
};
std::string format_error(const std::string& body, const std::string& error, unsigned status) std::string format_error(const std::string& body, const std::string& error, unsigned status)
{ {
return TestPrintHost().format_error(body, error, status).ToStdString(); return TestPrintHost().format_error(body, error, status).ToStdString();
@@ -88,14 +46,6 @@ std::string moonraker_error(int code, const std::string& message, const std::str
constexpr const char* k_busy_file_403 = constexpr const char* k_busy_file_403 =
R"JSON({"error": {"code": 403, "message": "Forbidden", "traceback": "Traceback (most recent call last):\n\n File \"/home/lava/moonraker/moonraker/components/file_manager/file_manager.py\", line 1017, in _finish_gcode_upload\n can_start = self._handle_operation_check(check_path)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nmoonraker.utils.exceptions.ServerError: File currently in use\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n\n File \"/home/lava/moonraker/moonraker/components/application.py\", line 1069, in post\n raise tornado.web.HTTPError(\ntornado.web.HTTPError: HTTP 403: Forbidden (File is loaded, upload not permitted)\n"}})JSON"; R"JSON({"error": {"code": 403, "message": "Forbidden", "traceback": "Traceback (most recent call last):\n\n File \"/home/lava/moonraker/moonraker/components/file_manager/file_manager.py\", line 1017, in _finish_gcode_upload\n can_start = self._handle_operation_check(check_path)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nmoonraker.utils.exceptions.ServerError: File currently in use\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n\n File \"/home/lava/moonraker/moonraker/components/application.py\", line 1069, in post\n raise tornado.web.HTTPError(\ntornado.web.HTTPError: HTTP 403: Forbidden (File is loaded, upload not permitted)\n"}})JSON";
// Replies a print host can send instead of JSON: a proxy or login page, nothing, a cut-off body.
const std::vector<std::string> non_json_replies = {
"<html><body>proxy login required</body></html>",
"",
"{\"err\":",
"{\"detail\":{\"matlStationInfo\":{\"slotInfos\":[{\"slotId\":1,",
};
} // namespace } // namespace
TEST_CASE("A Klipper upload error shows its reason instead of a Python traceback", "[PrintHost][Regression]") TEST_CASE("A Klipper upload error shows its reason instead of a Python traceback", "[PrintHost][Regression]")
@@ -261,114 +211,3 @@ TEST_CASE("Error bodies that are not a Moonraker envelope are left unchanged", "
CHECK(format_error("", "curl:Could not connect", 0) == "curl:Could not connect"); CHECK(format_error("", "curl:Could not connect", 0) == "curl:Could not connect");
} }
} }
TEST_CASE("Print host error code is read from a JSON reply", "[PrintHost]")
{
CHECK(PrintHost::get_err_code_from_body(R"({"err":0})") == 0);
CHECK(PrintHost::get_err_code_from_body(R"({"err":2})") == 2);
CHECK(PrintHost::get_err_code_from_body(R"({"status":"ok"})") == 0);
}
TEST_CASE("Print host error code reports a reply that is not JSON as an error", "[PrintHost]")
{
const std::string body = GENERATE(from_range(non_json_replies));
int err = 0;
REQUIRE_NOTHROW(err = PrintHost::get_err_code_from_body(body));
CHECK(err != 0);
}
TEST_CASE("Print host error code tolerates a wrongly typed err field", "[PrintHost]")
{
const std::string body = GENERATE(as<std::string>{}, R"({"err":"busy"})", R"({"err":{"code":1}})", R"([1,2])");
CHECK_NOTHROW(PrintHost::get_err_code_from_body(body));
}
TEST_CASE("Flashforge material slots are read from a well-formed reply", "[PrintHost][Flashforge]")
{
const std::string body = R"({"code":0,"detail":{"hasMatlStation":true,"matlStationInfo":{"slotCnt":2,"slotInfos":[
{"slotId":1,"hasFilament":true,"materialName":"PLA","materialColor":"#FFFFFF"},
{"slotId":2,"hasFilament":false,"materialName":"","materialColor":""}]}}})";
std::vector<FlashforgeMaterialSlot> slots;
bool supports_station = false;
REQUIRE(Flashforge::parse_material_slots(body, slots, &supports_station));
CHECK(supports_station);
REQUIRE(slots.size() == 2);
CHECK(slots[0].slot_id == 1);
CHECK(slots[0].has_filament);
CHECK(slots[0].material_name == "PLA");
CHECK(slots[0].material_color == "#FFFFFF");
CHECK(slots[1].slot_id == 2);
CHECK_FALSE(slots[1].has_filament);
}
TEST_CASE("Flashforge material slots accept numbers as strings and flags as numbers", "[PrintHost][Flashforge]")
{
const std::string body = R"({"detail":{"matlStationInfo":{"slotInfos":[
{"slotId":"3","hasFilament":1,"materialName":null,"materialColor":7}]}}})";
std::vector<FlashforgeMaterialSlot> slots;
REQUIRE_NOTHROW(Flashforge::parse_material_slots(body, slots, nullptr));
REQUIRE(slots.size() == 1);
CHECK(slots[0].slot_id == 3);
CHECK(slots[0].has_filament);
CHECK(slots[0].material_name.empty());
CHECK(slots[0].material_color.empty());
}
TEST_CASE("Flashforge material slots skip entries that are not objects", "[PrintHost][Flashforge]")
{
const std::string body = R"({"detail":{"matlStationInfo":{"slotInfos":[5,"slot",null,[],
{"slotId":4,"hasFilament":true,"materialName":"PETG"}]}}})";
std::vector<FlashforgeMaterialSlot> slots;
REQUIRE_NOTHROW(Flashforge::parse_material_slots(body, slots, nullptr));
REQUIRE(slots.size() == 1);
CHECK(slots[0].slot_id == 4);
CHECK(slots[0].material_name == "PETG");
}
TEST_CASE("Flashforge material slots tolerate slot info that is not a list", "[PrintHost][Flashforge]")
{
const std::string body = GENERATE(as<std::string>{},
R"({"detail":{"matlStationInfo":{"slotInfos":5}}})",
R"({"detail":{"matlStationInfo":{"slotInfos":"none"}}})",
R"({"detail":{"matlStationInfo":7}})",
R"({"detail":"offline"})");
std::vector<FlashforgeMaterialSlot> slots;
bool ok = false;
REQUIRE_NOTHROW(ok = Flashforge::parse_material_slots(body, slots, nullptr));
CHECK(ok);
CHECK(slots.empty());
}
TEST_CASE("Flashforge material slots reject a reply that is not JSON", "[PrintHost][Flashforge]")
{
const std::string body = GENERATE(from_range(non_json_replies));
std::vector<FlashforgeMaterialSlot> slots;
bool ok = true;
REQUIRE_NOTHROW(ok = Flashforge::parse_material_slots(body, slots, nullptr));
CHECK_FALSE(ok);
CHECK(slots.empty());
}
TEST_CASE("An upload that throws still finishes with an error", "[PrintHost][LifecycleEvents]")
{
UploadEvents run(std::make_unique<ThrowingPrintHost>());
CHECK_FALSE(run.uploaded);
CHECK(run.errors == std::vector<std::string>{"reply could not be read"});
CHECK(run.events == std::vector<LifecycleEvent>{LifecycleEvent::UploadStarted, LifecycleEvent::UploadFinished});
CHECK(run.codes == std::vector<LifecycleEvtCode>{LifecycleEvtCode::Ok, LifecycleEvtCode::Error});
}
TEST_CASE("A successful upload finishes without an error", "[PrintHost][LifecycleEvents]")
{
UploadEvents run(std::make_unique<TestPrintHost>());
CHECK(run.uploaded);
CHECK(run.errors.empty());
CHECK(run.events == std::vector<LifecycleEvent>{LifecycleEvent::UploadStarted, LifecycleEvent::UploadFinished});
CHECK(run.codes == std::vector<LifecycleEvtCode>{LifecycleEvtCode::Ok, LifecycleEvtCode::Ok});
}
-23
View File
@@ -1,23 +0,0 @@
#include <catch2/catch_all.hpp>
#include <string>
#include "slic3r/GUI/UserManager.hpp"
using namespace Slic3r;
TEST_CASE("User message that is not JSON is rejected without throwing", "[UserManager]")
{
const std::string payload = GENERATE(as<std::string>{}, "not json", "", "<html></html>", "{\"bind\":");
UserManager manager;
int result = 0;
REQUIRE_NOTHROW(result = manager.parse_json(payload));
CHECK(result == -1);
}
TEST_CASE("User message without a successful bind is ignored", "[UserManager]")
{
const std::string payload = GENERATE(as<std::string>{}, "{}", R"({"bind":{"command":"unbind"}})", R"({"bind":"bind"})", "[1]");
UserManager manager;
CHECK(manager.parse_json(payload) == -1);
}