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