Compare commits

..
Author SHA1 Message Date
ExPikaPaka 59a94838cf Include what the new code uses
The Windows build stopped on test_kdtree.cpp: it calls std::iota without
including <numeric>, which libstdc++ happens to pull in anyway. Added
there, and the same for <limits> and <algorithm>/<cmath> where the
recent changes rely on them being included by something else.
2026-09-29 10:38:18 +02:00
ExPikaPaka ff8e854291 Fix the Windows build: near and far are macros there
bridge_over_infill's helper for splitting polygons by proximity named
its locals near and far. The Windows headers define both as macros that
expand to nothing, so "Polygons near;" declared nothing and the uses of
it did not compile. Renamed; no behaviour change.
2026-09-29 10:37:46 +02:00
ExPikaPaka 6a80f1c9cb Visit seam candidates as the search finds them
Collecting every candidate within the radius into a vector cost more
than the search itself. Same order, so the seams are unchanged;
align_seam_points ~19.6 s at 0.1 mm / 2000k, was ~21.
2026-09-29 10:37:46 +02:00
ExPikaPaka 3b0e04858a Run a layer's regions in parallel where they are independent
detect_surfaces_type, process_external_surfaces and the vertical shells
each waited on their own heaviest layer in turn. The LOTR map plate
slices in ~10.5 min at 0.1 mm / 2000k, was ~11.5; ~87 s at normal
settings, was ~97.
2026-09-29 10:37:46 +02:00
ExPikaPaka 313e28bb95 Move polygons instead of copying them on move
MultiPoint had no rvalue constructor, so the derived move constructors
bound to the const reference and copied; append reserved exactly, so
collecting pieces one by one was quadratic. Colour segmentation ~3 s at
0.1 mm / 2000k, was ~40, and ordinary prints gain too.
2026-09-29 10:37:46 +02:00
ExPikaPaka 9afe66eacf Project painted faces onto the shell layers per tile
Only the slices within the deepest shell offset decide the result, so
the work is done per tile of the face. Top and bottom segmentation
~130 s at 0.1 mm / 2000k, was ~180.
2026-09-29 10:37:46 +02:00
ExPikaPaka 4d48be793f Tile the booleans on layers of many pieces
ClipperLib slows down with the number of edges on a scan line, and a
layer cut through a fine relief has tens of thousands of pieces.
detect_surfaces_type ~50 s at 0.1 mm / 2000k, was ~145.
2026-09-29 10:37:46 +02:00
ExPikaPaka 9370f1a4af Merge colour and top/bottom regions per island
The merge took anything from 3 to 38 minutes at 0.1 mm / 2000k, now
~2.5. Every region is grouped with the islands it overlaps, so the
result is the same.
2026-09-29 10:37:46 +02:00
ExPikaPaka 405718cf1d Slice fine texture relief without stalling
A colour texture baked at 0.1 mm / 2000k made the top layers thousands
of islands and slicing never finished. Colour segmentation runs per
island, the merge subtracts piece by piece, the support check tests only
nearby islands, and the travel ordering finds crossings through a grid.
2026-09-29 10:37:46 +02:00
ExPikaPaka 8b65e095f2 Generate walls and split solid infill in parallel
Same output, ~2.2 min for the LOTR map plate, was ~2.6.
2026-09-29 10:37:45 +02:00
ExPikaPaka f5126af9ac Run colour segmentation and vertical shells in parallel
Same output, ~2.6 min for the LOTR map plate, was ~2.9.
2026-09-29 10:37:45 +02:00
ExPikaPaka 88c3b07163 Faster slicing of colour-painted layers
A layer split into ~1000 colour fragments (a colour texture baked over a
large top face) made several per-fragment loops redo whole-layer ClipperLib
work, so slicing took ~33 min; it now takes ~3 min with the same output.

- make_fills: clip the layer's no-overlap area to each expolygon's box
  before intersecting
- discover_vertical_shells: small-piece filter compares only against the
  nearby part of the layer
- bridge_over_infill: whole-layer union/diff/intersections restricted to the
  candidate's neighbourhood; fill boundary expanded once per spacing; anchor
  tree built only from lines crossing the scan range; bbox pre-check in the
  collision test; limiting outline taken directly instead of through
  expand(..., 0.3 * flow.spacing()), which offsets by 0.135 scaled units
  (flow.spacing() is in mm) and only cost a whole-layer pass per candidate
2026-09-29 10:37:45 +02:00
109 changed files with 1812 additions and 2218 deletions
@@ -189,7 +189,8 @@ the presets to a base is in [shared-bases.md](shared-bases.md#variant-arrays-on-
2. Every key in `filament_options_with_variant` ([the full list](#the-four-key-sets)) that reaches the
preset, whether written, included or inherited, is resized to its variant count: write it at exactly
that width, or leave it out. Keys outside the set
(`filament_type`, plate temperatures, `fan_max_speed`, `slow_down_min_speed`, …) are never addressed by variant index; the preset contributes their first value however wide a
(`filament_type`, plate temperatures, `fan_max_speed`, `slow_down_min_speed`, `pressure_advance`,
…) are never addressed by variant index; the preset contributes their first value however wide a
file writes them.
3. Cover every variant the material is meant to print on across its `compatible_printers`. Leave out
a variant deliberately when the material should not be tuned for it (e.g. a TPU High Flow variant
@@ -454,7 +455,7 @@ no set: the variant-length resize leaves it alone, and only the pair rebuild of
last for contrast; it is not a variant set.
`check` and `fix-variant` read the four sets from `PrintConfig.cpp` on every run, so they follow the
engine. The lists below are from the 2026-09-29 checkout; regenerate them from the repository root
engine. The lists below are from the 2026-09-25 checkout; regenerate them from the repository root
before relying on them (the recipe strips comments, since an initializer can carry a commented-out entry):
```bash
@@ -477,7 +478,7 @@ EOF
**`print_options_with_variant`**, process, stride 1 (45): `bridge_acceleration`, `bridge_speed`, `default_acceleration`, `default_jerk`, `default_junction_deviation`, `enable_overhang_speed`, `gap_infill_speed`, `infill_jerk`, `initial_layer_acceleration`, `initial_layer_infill_speed`, `initial_layer_jerk`, `initial_layer_speed`, `initial_layer_travel_acceleration`, `initial_layer_travel_jerk`, `initial_layer_travel_speed`, `inner_wall_acceleration`, `inner_wall_jerk`, `inner_wall_speed`, `internal_bridge_speed`, `internal_solid_infill_acceleration`, `internal_solid_infill_speed`, `outer_wall_acceleration`, `outer_wall_jerk`, `outer_wall_speed`, `overhang_1_4_speed`, `overhang_2_4_speed`, `overhang_3_4_speed`, `overhang_4_4_speed`, `print_extruder_id`, `print_extruder_variant`, `slowdown_for_curled_perimeters`, `small_perimeter_speed`, `small_perimeter_threshold`, `sparse_infill_acceleration`, `sparse_infill_speed`, `support_interface_speed`, `support_speed`, `top_solid_infill_flow_ratio`, `top_surface_acceleration`, `top_surface_jerk`, `top_surface_speed`, `travel_acceleration`, `travel_jerk`, `travel_speed`, `travel_speed_z`
**`filament_options_with_variant`**, filament, stride 1 (54): `activate_air_filtration`, `activate_air_filtration_during_print`, `activate_air_filtration_on_completion`, `adaptive_pressure_advance`, `adaptive_pressure_advance_bridges`, `adaptive_pressure_advance_model`, `adaptive_pressure_advance_overhangs`, `complete_print_exhaust_fan_speed`, `during_print_exhaust_fan_speed`, `enable_pressure_advance`, `filament_adaptive_volumetric_speed`, `filament_cooling_before_tower`, `filament_deretraction_speed`, `filament_extruder_variant`, `filament_flow_ratio`, `filament_flush_temp`, `filament_flush_volumetric_speed`, `filament_ironing_flow`, `filament_ironing_inset`, `filament_ironing_spacing`, `filament_ironing_speed`, `filament_long_retractions_when_cut`, `filament_max_volumetric_speed`, `filament_pre_cooling_temperature`, `filament_pre_cooling_temperature_nc`, `filament_preheat_temperature_delta`, `filament_ramming_travel_time`, `filament_ramming_travel_time_nc`, `filament_ramming_volumetric_speed`, `filament_ramming_volumetric_speed_nc`, `filament_retract_after_wipe`, `filament_retract_before_wipe`, `filament_retract_length_nc`, `filament_retract_length_toolchange`, `filament_retract_lift_above`, `filament_retract_lift_below`, `filament_retract_lift_enforce`, `filament_retract_restart_extra`, `filament_retract_restart_extra_toolchange`, `filament_retract_when_changing_layer`, `filament_retraction_distances_when_cut`, `filament_retraction_length`, `filament_retraction_minimum_travel`, `filament_retraction_speed`, `filament_wipe`, `filament_wipe_distance`, `filament_z_hop`, `filament_z_hop_types`, `long_retractions_when_ec`, `nozzle_temperature`, `nozzle_temperature_initial_layer`, `pressure_advance`, `retraction_distances_when_ec`, `volumetric_speed_coefficients`
**`filament_options_with_variant`**, filament, stride 1 (48): `activate_air_filtration`, `activate_air_filtration_during_print`, `activate_air_filtration_on_completion`, `complete_print_exhaust_fan_speed`, `during_print_exhaust_fan_speed`, `filament_adaptive_volumetric_speed`, `filament_cooling_before_tower`, `filament_deretraction_speed`, `filament_extruder_variant`, `filament_flow_ratio`, `filament_flush_temp`, `filament_flush_volumetric_speed`, `filament_ironing_flow`, `filament_ironing_inset`, `filament_ironing_spacing`, `filament_ironing_speed`, `filament_long_retractions_when_cut`, `filament_max_volumetric_speed`, `filament_pre_cooling_temperature`, `filament_pre_cooling_temperature_nc`, `filament_preheat_temperature_delta`, `filament_ramming_travel_time`, `filament_ramming_travel_time_nc`, `filament_ramming_volumetric_speed`, `filament_ramming_volumetric_speed_nc`, `filament_retract_after_wipe`, `filament_retract_before_wipe`, `filament_retract_length_nc`, `filament_retract_length_toolchange`, `filament_retract_lift_above`, `filament_retract_lift_below`, `filament_retract_lift_enforce`, `filament_retract_restart_extra`, `filament_retract_restart_extra_toolchange`, `filament_retract_when_changing_layer`, `filament_retraction_distances_when_cut`, `filament_retraction_length`, `filament_retraction_minimum_travel`, `filament_retraction_speed`, `filament_wipe`, `filament_wipe_distance`, `filament_z_hop`, `filament_z_hop_types`, `long_retractions_when_ec`, `nozzle_temperature`, `nozzle_temperature_initial_layer`, `retraction_distances_when_ec`, `volumetric_speed_coefficients`
**`printer_extruder_options`**, machine, one value per extruder, not a variant set (8):
`default_nozzle_volume_type`, `extruder_max_nozzle_count`, `extruder_printable_area`,
@@ -244,10 +244,10 @@ preset for a 0.2 nozzle, explicitly revisit flow limits; do not infer a pressure
required direction of change, from diameter alone.
On a printer with extruder variants, a filament tunes these per variant too:
`filament_max_volumetric_speed`, `filament_flow_ratio`, `nozzle_temperature`, pressure advance and the
retraction overrides carry one value per variant of `filament_extruder_variant` (Standard, High Flow, …). The
`filament_max_volumetric_speed`, `filament_flow_ratio`, `nozzle_temperature` and the retraction
overrides carry one value per variant of `filament_extruder_variant` (Standard, High Flow, …). The
exact key set is [`filament_options_with_variant`](extruder-variants.md#the-four-key-sets);
`slow_down_min_speed` and `fan_max_speed` are not in it. Keep every such array at exactly that width, even where the
`slow_down_min_speed` and `pressure_advance` are not in it. Keep every such array at exactly that width, even where the
setting does not differ per variant, and measure the High Flow variant rather than copying Standard
([extruder-variants.md](extruder-variants.md#filament)).
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "OrcaFilamentLibrary",
"version": "02.04.00.16",
"version": "02.04.00.15",
"force_update": "0",
"description": "Orca Filament Library",
"filament_list": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"1"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"0"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"0"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
@@ -111,7 +111,6 @@
"1"
],
"enable_pressure_advance": [
"0",
"0"
],
"eng_plate_temp": [
@@ -317,7 +316,6 @@
"2"
],
"pressure_advance": [
"0.02",
"0.02"
],
"reduce_fan_stop_start_freq": [
+30 -129
View File
@@ -214,9 +214,9 @@ function ShowModelInfo( pModel )
SendWXDebugInfo("Model Name: "+sModelName);
$('#ModelName').text(sModelName);
$('#ModelName').html(sModelName);
$('#ModelName').attr('title',sModelName);
$('#ModelAuthorName').text(sModelAuthor);
$('#ModelAuthorName').html(sModelAuthor);
switch(UploadType)
{
@@ -268,7 +268,7 @@ function ShowModelInfo( pModel )
break;
}
$('#Model_Desc').empty().append( SanitizeDescHtml( html_decode(sModelDesc) ) );
$('#Model_Desc').html( html_decode(sModelDesc) );
let ModelPreviewList=pModel.preview_img;
let TotalPreview=ModelPreviewList.length;
@@ -281,15 +281,16 @@ function ShowModelInfo( pModel )
if(TotalPreview>0)
{
$('#ModelPreviewList').empty();
let htmlPreview='';
for(let pn=0;pn<TotalPreview;pn++)
{
//let FTmpPath=decodeURIComponent(ModelPreviewList[pn]);
let FTmpPath=ModelPreviewList[pn]['filepath'];
$('#ModelPreviewList').append( $('<div class="swiper-slide"></div>').append( $('<img class="Model_PrevImg" />').attr('src',FTmpPath) ) );
htmlPreview+='<div class="swiper-slide"><img class="Model_PrevImg" src="'+FTmpPath+'" /></div>';
}
$('#ModelPreviewList').html(htmlPreview);
$('#Model_Preview_Image').viewer({
title: false,
fullsreen: false,
@@ -409,8 +410,7 @@ function ConstructFileHtml( ID, pItem )
{
let fTotal=pItem.length;
let pBoard=$('#'+ID+' .FileListBoard');
pBoard.empty();
let strHtml='';
for( let f=0;f<fTotal;f++ )
{
let pOne=pItem[f];
@@ -443,139 +443,39 @@ function ConstructFileHtml( ID, pItem )
ImgPath='img/default.png';
}
//Add html. File names come from the 3MF, so build the nodes rather than concatenating markup.
let pIconImg=$('<img />').attr('src',ImgPath);
let pMenu=$('<div class="FileMenu"><img src="img/s.svg" /></div>');
//Add html
if( strClass!='ImageIcon' )
{
pMenu.on('click', function(){ OnClickOpenFile(tPath); });
strHtml+='<div class="FileItem">'+
' <div class="'+strClass+'"><img src="'+ImgPath+'" /></div>'+
' <div class="FileText">'+
' <div class="FileName">'+tName+'</div>'+
' </div>'+
' <div class="FileMenu" onClick="OnClickOpenFile(\''+tPath+'\')"><img src="img/s.svg" /></div>'+
'</div>';
}
else
{
ImgID++;
let TmpImgID="AF"+ImgID;
pIconImg.attr('id',TmpImgID);
pMenu.on('click', function(){ OnClickOpenImage(TmpImgID); });
strHtml+='<div class="FileItem">'+
' <div class="'+strClass+'"><img id="'+TmpImgID+'" src="'+ImgPath+'" /></div>'+
' <div class="FileText">'+
' <div class="FileName">'+tName+'</div>'+
' </div>'+
' <div class="FileMenu" onClick="OnClickOpenImage(\''+TmpImgID+'\')"><img src="img/s.svg" /></div>'+
'</div>';
}
}
let pFileItem=$('<div class="FileItem"></div>');
pFileItem.append( $('<div></div>').addClass(strClass).append(pIconImg) );
pFileItem.append( $('<div class="FileText"></div>').append( $('<div class="FileName"></div>').text(tName).attr('title',tName) ) );
pFileItem.append( pMenu );
pBoard.append( pFileItem );
}
$('#'+ID+' .FileListBoard').html(strHtml);
if( fTotal>0 )
$('#'+ID).show();
}
// Descriptions are untrusted 3MF metadata that may carry rich-text HTML (e.g. from MakerWorld).
// Rebuild them from an inert parse, keeping only plain formatting tags and http(s) links and images.
var DescAllowedTags=['P','BR','B','STRONG','I','EM','U','S','STRIKE','DEL','INS','SUB','SUP','SMALL','MARK',
'Q','ABBR','KBD','WBR','H1','H2','H3','H4','H5','H6','UL','OL','LI','DL','DT','DD','BLOCKQUOTE','PRE','CODE',
'HR','SPAN','DIV','FIGURE','FIGCAPTION','TABLE','CAPTION','THEAD','TBODY','TFOOT','TR','TH','TD','A','IMG'];
// Plain attributes kept per tag; the numeric ones must be plain non-negative integers.
var DescAllowedAttrs={'IMG':['alt','title','width','height'],'TD':['colspan','rowspan'],'TH':['colspan','rowspan'],'OL':['start']};
var DescNumericAttrs=['width','height','colspan','rowspan','start'];
// Dropped together with their content; any other unknown tag is unwrapped to its children.
var DescDroppedTags=['SCRIPT','STYLE','TEMPLATE','NOSCRIPT','TEXTAREA','TITLE','IFRAME','FRAME','OBJECT','EMBED','SVG','MATH'];
function IsHttpUrl( strUrl )
{
// The scheme must be written as is, so nothing the URL parser would strip can precede or split it.
if( typeof strUrl!='string' || !/^https?:/i.test(strUrl) )
return false;
try
{
let sProtocol=new URL(strUrl).protocol;
return sProtocol=='http:' || sProtocol=='https:';
}
catch(e)
{
return false;
}
}
// Images load as soon as the page opens, so only https sources are kept: no plain-http requests to the local network.
function IsHttpsUrl( strUrl )
{
return IsHttpUrl(strUrl) && new URL(strUrl).protocol=='https:';
}
// Embedded YouTube players become a plain link to the video.
function GetYouTubeEmbedUrl( pNode )
{
let sSrc=pNode.getAttribute('src');
if( !IsHttpUrl(sSrc) )
return null;
let pUrl=new URL(sSrc);
return ( pUrl.origin=='https://www.youtube.com' && pUrl.pathname.indexOf('/embed/')==0 ) ? pUrl.href : null;
}
function CopyDescNodes( pSrc, pDst )
{
for( let pNode=pSrc.firstChild;pNode!=null;pNode=pNode.nextSibling )
{
if( pNode.nodeType==Node.TEXT_NODE )
{
pDst.appendChild( document.createTextNode(pNode.nodeValue) );
continue;
}
if( pNode.nodeType!=Node.ELEMENT_NODE )
continue;
let sTag=pNode.nodeName.toUpperCase();
if( sTag=='IFRAME' )
{
let sVideoUrl=GetYouTubeEmbedUrl(pNode);
if( sVideoUrl!=null )
{
let pLink=document.createElement('A');
pLink.setAttribute('href',sVideoUrl);
pLink.textContent=sVideoUrl;
pDst.appendChild(pLink);
}
continue;
}
if( $.inArray(sTag,DescDroppedTags)>=0 )
continue;
if( $.inArray(sTag,DescAllowedTags)<0 )
{
CopyDescNodes(pNode,pDst);
continue;
}
let pElem=document.createElement(sTag);
if( sTag=='A' && IsHttpUrl(pNode.getAttribute('href')) )
pElem.setAttribute('href',pNode.getAttribute('href'));
else if( sTag=='IMG' )
{
if( !IsHttpsUrl(pNode.getAttribute('src')) )
continue;
pElem.setAttribute('src',pNode.getAttribute('src'));
}
$.each( DescAllowedAttrs[sTag]||[], function(i,sAttr){
let sValue=pNode.getAttribute(sAttr);
if( sValue!=null && ( $.inArray(sAttr,DescNumericAttrs)<0 || /^\d+$/.test(sValue) ) )
pElem.setAttribute(sAttr,sValue);
});
CopyDescNodes(pNode,pElem);
pDst.appendChild(pElem);
}
}
function SanitizeDescHtml( strHtml )
{
let pFragment=document.createDocumentFragment();
// A DOMParser document is inert: it runs no scripts and loads no resources.
let pDoc=new DOMParser().parseFromString(strHtml,'text/html');
if( pDoc && pDoc.body )
CopyDescNodes(pDoc.body,pFragment);
return pFragment;
}
function ShowProfilelInfo( pProfile )
{
//==========Profile Info==========
@@ -583,10 +483,10 @@ function ShowProfilelInfo( pProfile )
let sProfileAuthor=decodeURIComponent(pProfile.author);
let sProfileDesc=decodeURIComponent(pProfile.description);
$('#ProfileName').text(sProfileName);
$('#ProfileAuthor').text(sProfileAuthor);
$('#ProfileName').html(sProfileName);
$('#ProfileAuthor').html(sProfileAuthor);
$('#Profile_Desc').empty().append( SanitizeDescHtml( html_decode(sProfileDesc) ) );
$('#Profile_Desc').html( html_decode(sProfileDesc) );
let ProfilePreviewList=pProfile.preview_img;
let TotalPreview=ProfilePreviewList.length;
@@ -599,14 +499,15 @@ function ShowProfilelInfo( pProfile )
if(TotalPreview>0)
{
$('#ProfilePreviewList').empty();
let htmlPreview='';
for(let pn=0;pn<TotalPreview;pn++)
{
let FTmpPath=ProfilePreviewList[pn]['filepath'];
$('#ProfilePreviewList').append( $('<div class="swiper-slide"></div>').append( $('<img class="Model_PrevImg" />').attr('src',FTmpPath) ) );
htmlPreview+='<div class="swiper-slide"><img class="Model_PrevImg" src="'+FTmpPath+'" /></div>';
}
$('#ProfilePreviewList').html(htmlPreview);
$('#Profile_Preview_Image').viewer({
title: false,
fullsreen: false,
-30
View File
@@ -3476,14 +3476,6 @@ int CLI::run(int argc, char **argv)
}
new_variant_counts = old_variant_counts;
//filament_variant_count = old_variant_counts;
//ORCA: lay the per-variant options out one value per variant of the current filaments before each
// loaded filament replaces its own variants, including an option only a loaded filament
// defines, which otherwise starts as a single default value and never reaches the others.
for (const DynamicPrintConfig &config : load_filaments_config)
for (const std::string &opt_key : filament_options_with_variant)
if (opt_key != "filament_extruder_variant" && config.has(opt_key))
m_print_config.option(opt_key, true);
normalize_filament_values_to_variants(m_print_config);
for (int index = 0; index < load_filaments_config.size(); index++) {
DynamicPrintConfig& config = load_filaments_config[index];
int filament_index = load_filaments_index[index];
@@ -3655,25 +3647,6 @@ int CLI::run(int argc, char **argv)
}
}
//ORCA: a per-variant option the loaded filament does not define keeps the values of the
// variants the filament already had, and a variant new to it takes its first one's.
const int old_start = old_start_indice[filament_index - 1];
std::vector<int> kept_variant_indice = new_variant_indice;
for (int &i : kept_variant_indice)
if (i < 0)
i = old_start;
for (const std::string &opt_key : filament_options_with_variant) {
if (config.has(opt_key))
continue;
auto *opt_vec_dst = dynamic_cast<ConfigOptionVectorBase *>(m_print_config.option(opt_key));
if (opt_vec_dst == nullptr || opt_vec_dst->size() < size_t(old_start + old_variant_count))
continue;
// set_with_restore_2() pads its source in place
std::unique_ptr<ConfigOption> old_values(opt_vec_dst->clone());
opt_vec_dst->set_with_restore_2(static_cast<ConfigOptionVectorBase *>(old_values.get()), kept_variant_indice, old_start,
old_variant_count, true);
}
//update the old index
if (old_variant_count != new_variant_count)
{
@@ -4108,9 +4081,6 @@ int CLI::run(int argc, char **argv)
if (printer_technology == ptFFF) {
fff_print_config.apply(m_print_config, true);
m_print_config.apply(fff_print_config, true);
//ORCA: an option no preset or project defines has just come in as its single default value, and a
// command line override may hold one value per filament.
normalize_filament_values_to_variants(m_print_config);
} else {
boost::nowide::cerr << "invalid printer_technology " << std::endl;
record_exit_reson(outfile_dir, CLI_INVALID_PRINTER_TECH, 0, cli_errors[CLI_INVALID_PRINTER_TECH], sliced_info);
+67
View File
@@ -1,7 +1,11 @@
#include <algorithm>
#include <cmath>
#include <limits>
#include <numeric>
#include <unordered_map>
#include <tbb/parallel_for.h>
#include "ClipperUtils.hpp"
#include "Geometry.hpp"
#include "ShortestPath.hpp"
@@ -813,6 +817,69 @@ Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r
{ return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::SurfacesProvider(subject), ClipperUtils::SurfacesProvider(clip), do_safety_offset); }
Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset)
{ return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::SurfacesPtrProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); }
namespace ClipperUtils {
std::vector<ExPolygonsTile> tile_expolygons(const ExPolygons &expolygons, size_t per_tile)
{
BoundingBox extent;
std::vector<BoundingBox> bboxes;
bboxes.reserve(expolygons.size());
for (const ExPolygon &expoly : expolygons) {
bboxes.emplace_back(get_extents(expoly));
extent.merge(bboxes.back());
}
if (! extent.defined)
return {};
const int tiles = std::clamp(int(std::sqrt(double(expolygons.size()) / double(std::max<size_t>(per_tile, 1)))), 1, 32);
const Point size = extent.size();
const coord_t tile_w = std::max<coord_t>(1, size.x() / tiles + 1), tile_h = std::max<coord_t>(1, size.y() / tiles + 1);
std::vector<ExPolygonsTile> out(size_t(tiles * tiles));
for (size_t i = 0; i < expolygons.size(); ++ i) {
const Point c = bboxes[i].center();
ExPolygonsTile &tile = out[size_t(std::clamp(int((c.y() - extent.min.y()) / tile_h), 0, tiles - 1) * tiles +
std::clamp(int((c.x() - extent.min.x()) / tile_w), 0, tiles - 1))];
tile.members.emplace_back(i);
tile.bbox.merge(bboxes[i]);
}
out.erase(std::remove_if(out.begin(), out.end(), [](const ExPolygonsTile &tile) { return tile.members.empty(); }), out.end());
return out;
}
}
static Slic3r::ExPolygons clipper_ex_by_piece(ClipperLib::ClipType clipType, const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
{
// A few dozen subject ExPolygons to a tile, each tile one ClipperLib call with the clip cut to the tile's box.
const std::vector<ClipperUtils::ExPolygonsTile> tiles = ClipperUtils::tile_expolygons(subject, 32);
std::vector<BoundingBox> clip_bboxes;
clip_bboxes.reserve(clip.size());
for (const Polygon &polygon : clip)
clip_bboxes.emplace_back(get_extents(polygon));
std::vector<Slic3r::ExPolygons> out_tiles(tiles.size());
tbb::parallel_for(size_t(0), tiles.size(), [&](size_t tile_idx) {
const ClipperUtils::ExPolygonsTile &tile = tiles[tile_idx];
Slic3r::ExPolygons local_subject;
local_subject.reserve(tile.members.size());
for (size_t i : tile.members)
local_subject.emplace_back(subject[i]);
// Grown so that the cut edges of the clip stay clear of the subject, also after the safety offset.
const BoundingBox bbox = tile.bbox.inflated(SCALED_EPSILON);
Polygons local_clip;
for (size_t i = 0; i < clip.size(); ++i)
if (clip_bboxes[i].overlap(bbox))
if (Polygon clipped = ClipperUtils::clip_clipper_polygon_with_subject_bbox(clip[i], bbox); ! clipped.empty())
local_clip.emplace_back(std::move(clipped));
out_tiles[tile_idx] = _clipper_ex(clipType, ClipperUtils::ExPolygonsProvider(local_subject), ClipperUtils::PolygonsProvider(local_clip), do_safety_offset);
});
Slic3r::ExPolygons out;
for (Slic3r::ExPolygons &out_tile : out_tiles)
append(out, std::move(out_tile));
return out;
}
Slic3r::ExPolygons diff_ex_by_piece(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
{ return clipper_ex_by_piece(ClipperLib::ctDifference, subject, clip, do_safety_offset); }
Slic3r::ExPolygons intersection_ex_by_piece(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
{ return clipper_ex_by_piece(ClipperLib::ctIntersection, subject, clip, do_safety_offset); }
// May be used to "heal" unusual models (3DLabPrints etc.) by providing fill_type (pftEvenOdd, pftNonZero, pftPositive, pftNegative).
Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFillType fill_type)
{ return _clipper_ex(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No, fill_type); }
+15
View File
@@ -2,6 +2,7 @@
#define slic3r_ClipperUtils_hpp_
#include "libslic3r.h"
#include "BoundingBox.hpp"
#include "clipper.hpp"
#include "ExPolygon.hpp"
#include "Polygon.hpp"
@@ -321,6 +322,15 @@ namespace ClipperUtils {
[[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const ExPolygon &src, const BoundingBox &bbox, const bool get_entire_polygons = false);
[[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const ExPolygons &src, const BoundingBox &bbox, const bool get_entire_polygons = false);
// Splits ExPolygons into tiles by the centres of their boxes, about `per_tile` of them to a tile, to run ClipperLib on a
// layer of many pieces tile by tile. Returns the non-empty tiles, each with the indices of its ExPolygons and their box.
struct ExPolygonsTile
{
BoundingBox bbox;
std::vector<size_t> members;
};
[[nodiscard]] std::vector<ExPolygonsTile> tile_expolygons(const ExPolygons &expolygons, size_t per_tile);
}
// Perform union of input polygons using the non-zero rule, convert to ExPolygons.
@@ -518,6 +528,11 @@ Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r
Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
// diff_ex() / intersection_ex() of the subject split into tiles, each against only the part of the clip near it, the tiles in
// parallel. The same area as the operation on the whole subject when its ExPolygons do not overlap, and much faster for a
// subject of thousands of pieces spread over a layer: ClipperLib slows down with the number of edges crossing a scan line.
Slic3r::ExPolygons diff_ex_by_piece(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::ExPolygons intersection_ex_by_piece(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygon &clip);
Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &clip);
Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip);
+31 -12
View File
@@ -9,6 +9,8 @@
#include "../PrintConfig.hpp"
#include "../Surface.hpp"
#include <tbb/parallel_for.h>
#include "AABBTreeLines.hpp"
#include "ExtrusionEntity.hpp"
#include "Fill.hpp"
@@ -630,24 +632,28 @@ void split_solid_surface(size_t layer_id, const SurfaceFill &fill, ExPolygons &n
if (!line_based_pattern) {
const coord_t scaled_spacing = scaled<coord_t>(fill.params.spacing);
for (const ExPolygon &expolygon : fill.expolygons) {
// Each expolygon is split on its own, so they run in parallel and are collected in their original order.
std::vector<std::pair<ExPolygons, ExPolygons>> split_parts(fill.expolygons.size()); // normal, narrow
tbb::parallel_for(size_t(0), fill.expolygons.size(), [&](size_t idx) {
const ExPolygon &expolygon = fill.expolygons[idx];
Polygons filled_area = to_polygons(expolygon);
// "Core" area: open (erode+dilate) to drop thin features, then clamp back to the original polygon.
Polygons inner_area = intersection(filled_area, opening(filled_area, scaled_spacing, scaled_spacing));
if (inner_area.empty()) {
narrow_infill.emplace_back(expolygon);
continue;
split_parts[idx].second.emplace_back(expolygon);
return;
}
ExPolygons inner_ex = union_ex(inner_area);
ExPolygons expolys{expolygon};
ExPolygons narrow_ex = diff_ex(expolys, inner_ex);
ExPolygons normal_ex = intersection_ex(expolys, inner_ex);
append(normal_infill, normal_ex); // normal infill area
append(narrow_infill, narrow_ex); // narrow infill area
split_parts[idx].second = diff_ex(expolys, inner_ex); // narrow infill area
split_parts[idx].first = intersection_ex(expolys, inner_ex); // normal infill area
});
for (auto &[normal_ex, narrow_ex] : split_parts) {
append(normal_infill, std::move(normal_ex));
append(narrow_infill, std::move(narrow_ex));
}
return;
@@ -669,7 +675,10 @@ void split_solid_surface(size_t layer_id, const SurfaceFill &fill, ExPolygons &n
}
const double aligning_angle = -base_angle + PI;
for (const ExPolygon &expolygon : fill.expolygons) {
// Each expolygon is reconstructed on its own, so they run in parallel and are collected in their original order.
std::vector<Polygons> split_reconstructed(fill.expolygons.size());
tbb::parallel_for(size_t(0), fill.expolygons.size(), [&](size_t expolygon_idx) {
const ExPolygon &expolygon = fill.expolygons[expolygon_idx];
Polygons filled_area = to_polygons(expolygon);
polygons_rotate(filled_area, aligning_angle);
BoundingBox bb = get_extents(filled_area);
@@ -800,8 +809,10 @@ void split_solid_surface(size_t layer_id, const SurfaceFill &fill, ExPolygons &n
}
}
polygons_append(normal_fill_areas, reconstructed_area);
}
split_reconstructed[expolygon_idx] = std::move(reconstructed_area);
});
for (Polygons &reconstructed_area : split_reconstructed)
polygons_append(normal_fill_areas, std::move(reconstructed_area));
polygons_rotate(normal_fill_areas, -aligning_angle);
@@ -1409,7 +1420,15 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
// Orca: Reuse the body origin used for bridge anchoring, resetting it for each surface.
f->set_bounding_box(infill_bounding_box(*this, surface_fill, expoly, bbox));
f->no_overlap_expolygons = intersection_ex(surface_fill.no_overlap_expolygons, ExPolygons() = {expoly}, ApplySafetyOffset::Yes);
// Only the part of the layer-wide no-overlap area under this expolygon matters, so clip it to the
// expolygon's box first (padded past the safety offset, which grows the clip side). The result is
// identical; the cost is not: a layer split into many small fills, e.g. by colour painting,
// otherwise intersects every one of them with the whole layer.
BoundingBox no_overlap_bbox = get_extents(expoly);
no_overlap_bbox.offset(SCALED_EPSILON);
f->no_overlap_expolygons = intersection_ex(
ClipperUtils::clip_clipper_polygons_with_subject_bbox(surface_fill.no_overlap_expolygons, no_overlap_bbox),
ExPolygons() = {expoly}, ApplySafetyOffset::Yes);
if (params.symmetric_infill_y_axis) {
params.symmetric_y_axis = f->extended_object_bounding_box().center().x();
expoly.symmetric_y(params.symmetric_y_axis);
+8 -12
View File
@@ -141,7 +141,7 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s
its.indices.emplace_back(indices[0], indices[1], indices[2]);
int face_index =its.indices.size() - 1;
RGBA face_color;
auto set_face_color = [&uvs, &data, &mtl_data, &obj_info, &face_color](int face_index, const std::string mtl_name, const std::array<int, 3> &corners) {
auto set_face_color = [&uvs, &data, &mtl_data, &obj_info, &face_color](int face_index, const std::string mtl_name) {
if (mtl_data.new_mtl_unmap.find(mtl_name) != mtl_data.new_mtl_unmap.end()) {
bool is_merge_ka_kd = true;
for (size_t n = 0; n < 3; n++) {
@@ -174,7 +174,7 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s
return Vec2f::Zero();
return Vec2f(data.textureCoordinates[idx * OBJ_TEXCOORD_LENGTH], data.textureCoordinates[idx * OBJ_TEXCOORD_LENGTH + 1]);
};
std::array<Vec2f, 3> uv_array{uv_at(uvs[corners[0]]), uv_at(uvs[corners[1]]), uv_at(uvs[corners[2]])};
std::array<Vec2f, 3> uv_array{uv_at(uvs[0]), uv_at(uvs[1]), uv_at(uvs[2])};
obj_info.uvs.emplace_back(uv_array);
}
obj_info.face_colors.emplace_back(face_color);
@@ -185,27 +185,27 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s
}
}
};
auto set_face_color_by_mtl = [&data, &set_face_color](int face_index, const std::array<int, 3> &corners) {
auto set_face_color_by_mtl = [&data, &set_face_color](int face_index) {
if (data.usemtls.size() == 1) {
set_face_color(face_index, data.usemtls[0].name, corners);
set_face_color(face_index, data.usemtls[0].name);
} else {
for (size_t k = 0; k < data.usemtls.size(); k++) {
auto mtl = data.usemtls[k];
if (face_index >= mtl.face_start && face_index <= mtl.face_end) {
set_face_color(face_index, data.usemtls[k].name, corners);
set_face_color(face_index, data.usemtls[k].name);
break;
}
}
}
};
if (exist_mtl) {
set_face_color_by_mtl(face_index, {0, 1, 2});
set_face_color_by_mtl(face_index);
}
if (cnt == 4) {
its.indices.emplace_back(indices[0], indices[2], indices[3]);
int face_index = its.indices.size() - 1;
if (exist_mtl) {
set_face_color_by_mtl(face_index, {0, 2, 3});
set_face_color_by_mtl(face_index);
}
}
}
@@ -217,12 +217,8 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s
message = _L("This OBJ file couldn't be read because it's empty.");
return false;
}
if (meshptr->volume() < 0) {
if (meshptr->volume() < 0)
meshptr->flip_triangles();
// Flipping swaps corners 1 and 2 of every face, so the UVs have to follow.
for (std::array<Vec2f, 3> &uv : obj_info.uvs)
std::swap(uv[1], uv[2]);
}
// Hand the parsed material table back so callers can build a TexturedMesh from it.
if (out_mtl)
*out_mtl = mtl_data;
+68 -48
View File
@@ -1497,7 +1497,12 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
check_add_eol(toolchange_gcode_str);
// SoftFever: set new PA for new filament
gcode += gcodegen.set_filament_pressure_advance(new_filament_id);
if (gcodegen.config().enable_pressure_advance.get_at(new_filament_id)) {
gcode += gcodegen.writer().set_pressure_advance(gcodegen.config().pressure_advance.get_at(new_filament_id));
// Orca: Adaptive PA
// Reset Adaptive PA processor last PA value
gcodegen.m_pa_processor->resetPreviousPA(gcodegen.config().pressure_advance.get_at(new_filament_id));
}
// A phony move to the end position at the wipe tower.
gcodegen.writer().travel_to_xy((end_pos + plate_origin_2d).cast<double>());
@@ -1821,8 +1826,12 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
check_add_eol(toolchange_gcode_str);
// SoftFever: set new PA for new filament
if (new_extruder_id != -1)
gcode += gcodegen.set_filament_pressure_advance(new_extruder_id);
if (new_extruder_id != -1 && gcodegen.config().enable_pressure_advance.get_at(new_extruder_id)) {
gcode += gcodegen.writer().set_pressure_advance(gcodegen.config().pressure_advance.get_at(new_extruder_id));
// Orca: Adaptive PA
// Reset Adaptive PA processor last PA value
gcodegen.m_pa_processor->resetPreviousPA(gcodegen.config().pressure_advance.get_at(new_extruder_id));
}
// A phony move to the end position at the wipe tower.
gcodegen.writer().travel_to_xy((end_pos + plate_origin_2d).cast<double>());
@@ -3223,7 +3232,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
m_cooling_buffer->set_current_extruder(initial_extruder_id, extruder_id);
// Orca: Initialise AdaptivePA processor filter
m_pa_processor = std::make_unique<AdaptivePAProcessor>(*this);
m_pa_processor = std::make_unique<AdaptivePAProcessor>(*this, tool_ordering.all_extruders());
// Update output variables after the extruders were initialized.
m_placeholder_parser_integration.init(m_writer);
@@ -3788,7 +3797,12 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
}
}
// Orca: add missing PA settings for initial filament
file.write(set_filament_pressure_advance(initial_non_support_extruder_id));
if (m_config.enable_pressure_advance.get_at(initial_non_support_extruder_id)) {
file.write(m_writer.set_pressure_advance(m_config.pressure_advance.get_at(initial_non_support_extruder_id)));
// Orca: Adaptive PA
// Reset Adaptive PA processor last PA value
m_pa_processor->resetPreviousPA(m_config.pressure_advance.get_at(initial_non_support_extruder_id));
}
}
//flush FanMover buffer to avoid modifying the start gcode if it's manual.
@@ -8376,16 +8390,12 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// Orca: Dynamic PA
// If adaptive PA is enabled, by default evaluate PA on all extrusion moves
// The PA settings and the PA_CHANGE tags use the filament config index of the filament's extruder variant
const unsigned int pa_config_index = (unsigned int) get_filament_config_index(m_writer.filament()->id());
const bool adaptive_pa = m_config.adaptive_pressure_advance.get_at(pa_config_index) && m_config.enable_pressure_advance.get_at(pa_config_index);
const bool adaptive_pa_overhangs = adaptive_pa && m_config.adaptive_pressure_advance_overhangs.get_at(pa_config_index);
bool is_pa_calib = m_curr_print->calib_mode() == CalibMode::Calib_PA_Line ||
m_curr_print->calib_mode() == CalibMode::Calib_PA_Pattern ||
m_curr_print->calib_mode() == CalibMode::Calib_PA_Tower;
bool evaluate_adaptive_pa = false;
bool role_change = (m_last_extrusion_role != path.role());
if (!is_pa_calib && adaptive_pa) {
if (!is_pa_calib && FILAMENT_CONFIG(adaptive_pressure_advance) && FILAMENT_CONFIG(enable_pressure_advance)) {
evaluate_adaptive_pa = true;
// If we have already emmited a PA change because the m_multi_flow_segment_path_pa_set is set
// skip re-issuing the PA change tag.
@@ -8468,7 +8478,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// Post processor flag generation code segment when option to emit only at role changes is enabled
// Variables published to the post processor:
// 1) Tag to trigger a PA evaluation (because a role change was identified and the user has requested dynamic PA adjustments)
// 2) Current filament config index (to identify the PA model for the currently used filament and its extruder variant)
// 2) Current extruder ID (to identify the PA model for the currently used extruder)
// 3) mm3_per_mm value (to then multiply by the final model print speed after slowdown for cooling is applied)
// 4) the current acceleration (to pass to the model for evaluation)
// 5) whether this is an external perimeter (for future use)
@@ -8479,7 +8489,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
if (m_multi_flow_segment_path_average_mm3_per_mm > 0) {
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
pa_config_index,
m_writer.filament()->id(),
m_multi_flow_segment_path_average_mm3_per_mm,
acceleration_i,
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
@@ -8492,7 +8502,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// to issue a zero flow PA change command for this
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
pa_config_index,
m_writer.filament()->id(),
_mm3_per_mm,
acceleration_i,
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
@@ -8600,7 +8610,11 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// or a flow change, so emit the flag to evaluate PA for the upcomming extrusion
// Emit tag before new speed is set so the post processor reads the next speed immediately and uses it.
// Dont emit tag if it has just already been emitted from a role change above
if(_mm3_per_mm >0 && adaptive_pa_overhangs && !evaluate_adaptive_pa){
if(_mm3_per_mm >0 &&
FILAMENT_CONFIG(adaptive_pressure_advance) &&
FILAMENT_CONFIG(enable_pressure_advance) &&
FILAMENT_CONFIG(adaptive_pressure_advance_overhangs) &&
!evaluate_adaptive_pa){
if(writer().get_current_speed() > F){ // Ramping down speed - use overhang logic where the minimum speed is used between current and upcoming extrusion
if(m_config.gcode_comments){
sprintf(buf, "; Ramp down-non-variable\n");
@@ -8608,7 +8622,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
}
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
pa_config_index,
m_writer.filament()->id(),
_mm3_per_mm,
acceleration_i,
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
@@ -8623,7 +8637,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
}
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
pa_config_index,
m_writer.filament()->id(),
_mm3_per_mm,
acceleration_i,
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
@@ -8829,7 +8843,10 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// ORCA: Adaptive PA code segment when adjusting PA within the same feature
// There is a speed change or flow change so emit the flag to evaluate PA for the upcomming extrusion
// Emit tag before new speed is set so the post processor reads the next speed immediately and uses it.
if(_mm3_per_mm >0 && adaptive_pa_overhangs){
if(_mm3_per_mm >0 &&
EXTRUDER_CONFIG(adaptive_pressure_advance) &&
EXTRUDER_CONFIG(enable_pressure_advance) &&
EXTRUDER_CONFIG(adaptive_pressure_advance_overhangs) ){
if(last_set_speed > new_speed){ // Ramping down speed - use overhang logic where the minimum speed is used between current and upcoming extrusion
if(m_config.gcode_comments) {
sprintf(buf, "; Ramp up-variable\n");
@@ -8837,7 +8854,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
}
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
pa_config_index,
m_writer.filament()->id(),
_mm3_per_mm,
acceleration_i,
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
@@ -8852,7 +8869,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
}
sprintf(buf, ";%sT%u MM3MM:%g ACCEL:%u BR:%d RC:%d OV:%d\n",
GCodeProcessor::reserved_tag(GCodeProcessor::ETags::PA_Change).c_str(),
pa_config_index,
m_writer.filament()->id(),
_mm3_per_mm,
acceleration_i,
((path.role() == erBridgeInfill) ||(path.role() == erOverhangPerimeter)),
@@ -9405,29 +9422,33 @@ void GCode::update_placeholder_parser_with_variant_params()
if (num_filaments == 0)
return;
// Helper: remap config arrays from variant index space to filament_id index space.
// Helpers: remap config arrays from variant index space to filament_id index space.
// After remapping, gcode templates can use param[filament_id] directly.
auto remap_by_filament = [&](const auto &src) {
std::decay_t<decltype(src.values)> dst(num_filaments);
auto remap_floats_by_filament = [&](const auto &src) {
std::vector<double> dst(num_filaments);
for (size_t i = 0; i < num_filaments; ++i)
dst[i] = src.get_at(get_filament_config_index(i));
return dst;
};
auto remap_ints_by_filament = [&](const auto &src) {
std::vector<int> dst(num_filaments);
for (size_t i = 0; i < num_filaments; ++i)
dst[i] = src.get_at(get_filament_config_index(i));
return dst;
};
// --- filament_options_with_variant: gcode indexes by filament_id ---
this->placeholder_parser().set("filament_max_volumetric_speed", new ConfigOptionFloats(remap_by_filament(m_config.filament_max_volumetric_speed)));
this->placeholder_parser().set("filament_pre_cooling_temperature", new ConfigOptionInts(remap_by_filament(m_config.filament_pre_cooling_temperature)));
this->placeholder_parser().set("filament_pre_cooling_temperature_nc", new ConfigOptionInts(remap_by_filament(m_config.filament_pre_cooling_temperature_nc)));
this->placeholder_parser().set("filament_cooling_before_tower", new ConfigOptionFloats(remap_by_filament(m_config.filament_cooling_before_tower)));
this->placeholder_parser().set("nozzle_temperature_initial_layer", new ConfigOptionInts(remap_by_filament(m_config.nozzle_temperature_initial_layer)));
this->placeholder_parser().set("nozzle_temperature", new ConfigOptionInts(remap_by_filament(m_config.nozzle_temperature)));
this->placeholder_parser().set("filament_max_volumetric_speed", new ConfigOptionFloats(remap_floats_by_filament(m_config.filament_max_volumetric_speed)));
this->placeholder_parser().set("filament_pre_cooling_temperature", new ConfigOptionInts(remap_ints_by_filament(m_config.filament_pre_cooling_temperature)));
this->placeholder_parser().set("filament_pre_cooling_temperature_nc", new ConfigOptionInts(remap_ints_by_filament(m_config.filament_pre_cooling_temperature_nc)));
this->placeholder_parser().set("filament_cooling_before_tower", new ConfigOptionFloats(remap_floats_by_filament(m_config.filament_cooling_before_tower)));
this->placeholder_parser().set("nozzle_temperature_initial_layer", new ConfigOptionInts(remap_ints_by_filament(m_config.nozzle_temperature_initial_layer)));
this->placeholder_parser().set("nozzle_temperature", new ConfigOptionInts(remap_ints_by_filament(m_config.nozzle_temperature)));
// first_layer_temperature is a legacy alias of nozzle_temperature_initial_layer
this->placeholder_parser().set("first_layer_temperature", new ConfigOptionInts(remap_by_filament(m_config.nozzle_temperature_initial_layer)));
this->placeholder_parser().set("pressure_advance", new ConfigOptionFloats(remap_by_filament(m_config.pressure_advance)));
this->placeholder_parser().set("enable_pressure_advance", new ConfigOptionBools(remap_by_filament(m_config.enable_pressure_advance)));
this->placeholder_parser().set("first_layer_temperature", new ConfigOptionInts(remap_ints_by_filament(m_config.nozzle_temperature_initial_layer)));
// --- printer_options_with_variant_1: in m_config these are already merged as filament-indexed ---
this->placeholder_parser().set("retraction_distances_when_cut", new ConfigOptionFloats(remap_by_filament(m_config.retraction_distances_when_cut)));
this->placeholder_parser().set("retraction_distances_when_cut", new ConfigOptionFloats(remap_floats_by_filament(m_config.retraction_distances_when_cut)));
// hotend_cooling_rate / hotend_heating_rate: gcode uses [filament_map[x]-1] (extruder_id), no remap needed
// --- filament_map: per-layer dynamic, sync from m_config to placeholder_parser ---
@@ -9437,9 +9458,9 @@ void GCode::update_placeholder_parser_with_variant_params()
{
// Fast purge mode uses filament_flush_temp_fast; Default is inert.
bool use_fast_flush = m_config.prime_volume_mode == PrimeVolumeMode::pvmFast;
auto flush_v_speed = remap_by_filament(m_config.filament_flush_volumetric_speed);
auto filament_max_v = remap_by_filament(m_config.filament_max_volumetric_speed);
auto flush_temps = remap_by_filament(use_fast_flush ? m_config.filament_flush_temp_fast
auto flush_v_speed = remap_floats_by_filament(m_config.filament_flush_volumetric_speed);
auto filament_max_v = remap_floats_by_filament(m_config.filament_max_volumetric_speed);
auto flush_temps = remap_ints_by_filament(use_fast_flush ? m_config.filament_flush_temp_fast
: m_config.filament_flush_temp);
for (size_t i = 0; i < num_filaments; ++i) {
if (flush_v_speed[i] == 0)
@@ -9492,7 +9513,12 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
gcode += this->placeholder_parser_process("filament_start_gcode", filament_start_gcode, new_filament_id, &config);
check_add_eol(gcode);
}
gcode += set_filament_pressure_advance(new_filament_id);
if (m_config.enable_pressure_advance.get_at(new_filament_id)) {
gcode += m_writer.set_pressure_advance(m_config.pressure_advance.get_at(new_filament_id));
// Orca: Adaptive PA
// Reset Adaptive PA processor last PA value
m_pa_processor->resetPreviousPA(m_config.pressure_advance.get_at(new_filament_id));
}
gcode += m_writer.toolchange(new_filament_id, new_extruder_id);
if (Extruder *fil = m_writer.filament())
@@ -9883,24 +9909,18 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
if (m_ooze_prevention.enable && !defer_temp_wait)
gcode += m_ooze_prevention.post_toolchange(*this);
gcode += set_filament_pressure_advance(new_filament_id);
if (m_config.enable_pressure_advance.get_at(new_filament_id)) {
gcode += m_writer.set_pressure_advance(m_config.pressure_advance.get_at(new_filament_id));
// Orca: Adaptive PA
// Reset Adaptive PA processor last PA value
m_pa_processor->resetPreviousPA(m_config.pressure_advance.get_at(new_filament_id));
}
//Orca: tool changer or IDEX's firmware may change Z position, so we set it to unknown/undefined
m_last_pos_defined = false;
return gcode;
}
std::string GCode::set_filament_pressure_advance(unsigned int filament_id)
{
const size_t fi = get_filament_config_index(filament_id);
if (!m_config.enable_pressure_advance.get_at(fi))
return {};
// Orca: Adaptive PA
// Reset Adaptive PA processor last PA value
m_pa_processor->resetPreviousPA(m_config.pressure_advance.get_at(fi));
return m_writer.set_pressure_advance(m_config.pressure_advance.get_at(fi));
}
inline std::string polygon_to_string(const Polygon &polygon, Print *print, bool is_print_space = false) {
std::ostringstream gcode;
gcode << "[";
-2
View File
@@ -281,8 +281,6 @@ public:
// extra_retract forwards a PETG pre-extrusion over-extrusion; default 0 -> identical to the plain deretract.
std::string unretract(float extra_retract = 0.f) { return m_writer.unlift() + m_writer.unretract(extra_retract); }
std::string set_extruder(unsigned int extruder_id, double print_z, bool by_object=false, int toolchange_temp_override = -1, bool defer_temp_wait = false);
// Sets the pressure advance of the filament's extruder variant, if enabled for it.
std::string set_filament_pressure_advance(unsigned int filament_id);
bool is_BBL_Printer();
WipeTowerType wipe_tower_type();
+33 -26
View File
@@ -21,31 +21,39 @@ namespace Slic3r {
*
* @param gcodegen A reference to the GCode object that generates the G-code.
*/
AdaptivePAProcessor::AdaptivePAProcessor(GCode &gcodegen)
AdaptivePAProcessor::AdaptivePAProcessor(GCode &gcodegen, const std::vector<unsigned int> &tools_used)
: m_gcodegen(gcodegen),
m_config(gcodegen.config()),
m_last_predicted_pa(0.0),
m_max_next_feedrate(0.0),
m_next_feedrate(0.0),
m_current_feedrate(0.0),
m_last_config_index(-1),
m_last_extruder_id(-1),
m_pa_change_pattern(R"(; PA_CHANGE:T(\d+) MM3MM:([0-9]*\.[0-9]+) ACCEL:(\d+) BR:(\d+) RC:(\d+) OV:(\d+))"),
m_g1_f_pattern(R"(G1 F([0-9]+))")
{
// Constructor body can be used for further initialization if necessary
for (unsigned int tool : tools_used) {
// Only enable model for the tool if both PA and adaptive PA options are enabled
if(m_config.adaptive_pressure_advance.get_at(tool) && m_config.enable_pressure_advance.get_at(tool)){
auto interpolator = std::make_unique<AdaptivePAInterpolator>();
// Get calibration values from extruder
std::string pa_calibration_values = m_config.adaptive_pressure_advance_model.get_at(tool);
// Setup the model and store it in the tool-interpolation model map
interpolator->parseAndSetData(pa_calibration_values);
m_AdaptivePAInterpolators[tool] = std::move(interpolator);
}
}
}
// Method to get the interpolator for a specific filament config index.
// The model is built the first time an index is requested, as the indices in use depend on
// the extruder variant each filament prints with.
AdaptivePAInterpolator* AdaptivePAProcessor::getInterpolator(unsigned int config_index) {
auto [it, inserted] = m_AdaptivePAInterpolators.try_emplace(config_index);
// Only enable model for the index if both PA and adaptive PA options are enabled
if (inserted && m_config.adaptive_pressure_advance.get_at(config_index) && m_config.enable_pressure_advance.get_at(config_index)) {
it->second = std::make_unique<AdaptivePAInterpolator>();
it->second->parseAndSetData(m_config.adaptive_pressure_advance_model.get_at(config_index));
}
// Method to get the interpolator for a specific tool ID
AdaptivePAInterpolator* AdaptivePAProcessor::getInterpolator(unsigned int tool_id) {
auto it = m_AdaptivePAInterpolators.find(tool_id);
if (it != m_AdaptivePAInterpolators.end()) {
return it->second.get();
}
return nullptr; // Handle the case where the tool_id was not found
}
/**
* @brief Processes a layer of G-code and applies adaptive pressure advance.
@@ -100,17 +108,16 @@ std::string AdaptivePAProcessor::process_layer(std::string &&gcode) {
// the PA for that material is set. As no tag below will be found for this extruder, the original PA is retained.
if (line.find("; PA_CHANGE") == 0) { // prune lines quickly before running regex check as regex is more expensive to run
if (std::regex_search(line, m_match, m_pa_change_pattern)) {
// The tag carries the filament config index, which selects the PA settings of the filament's extruder variant
int config_index = std::stoi(m_match[1].str());
int extruder_id = std::stoi(m_match[1].str());
mm3mm_value = std::stod(m_match[2].str());
accel_value = std::stod(m_match[3].str());
int isBridge = std::stoi(m_match[4].str());
int roleChange = std::stoi(m_match[5].str());
int isOverhang = std::stoi(m_match[6].str());
// Check if the filament config index has changed
bool config_index_changed = (config_index != m_last_config_index);
m_last_config_index = config_index;
// Check if the extruder ID has changed
bool extruder_changed = (extruder_id != m_last_extruder_id);
m_last_extruder_id = extruder_id;
// Save the PA_CHANGE line to output later after finding feedrate
pa_change_line = line;
@@ -205,23 +212,23 @@ std::string AdaptivePAProcessor::process_layer(std::string &&gcode) {
// Calculate the predicted PA using the upcomming feature maximum feedrate
// Get the interpolator for the active tool
AdaptivePAInterpolator* interpolator = getInterpolator(m_last_config_index);
AdaptivePAInterpolator* interpolator = getInterpolator(m_last_extruder_id);
double predicted_pa = 0;
double adaptive_PA_speed = 0;
if(!interpolator){ // Tool not found in the interpolator map
// Tool not found in the PA interpolator to tool map
predicted_pa = m_config.enable_pressure_advance.get_at(m_last_config_index) ? m_config.pressure_advance.get_at(m_last_config_index) : 0;
predicted_pa = m_config.enable_pressure_advance.get_at(m_last_extruder_id) ? m_config.pressure_advance.get_at(m_last_extruder_id) : 0;
if(m_config.gcode_comments) output << "; APA: Tool doesnt have APA enabled\n";
} else if (!interpolator->isInitialised() || (!m_config.adaptive_pressure_advance.get_at(m_last_config_index)) )
} else if (!interpolator->isInitialised() || (!m_config.adaptive_pressure_advance.get_at(m_last_extruder_id)) )
// Check if the model is not initialised by the constructor for the active extruder
// Also check that adaptive PA is enabled for that extruder. This should not be needed
// as the PA change flag should not be set upstream (in the GCode.cpp file) if adaptive PA is disabled
// however check for robustness sake.
{
// Model failed or adaptive pressure advance not enabled - use default value from m_config
predicted_pa = m_config.enable_pressure_advance.get_at(m_last_config_index) ? m_config.pressure_advance.get_at(m_last_config_index) : 0;
predicted_pa = m_config.enable_pressure_advance.get_at(m_last_extruder_id) ? m_config.pressure_advance.get_at(m_last_extruder_id) : 0;
if(m_config.gcode_comments) output << "; APA: Interpolator setup failed, using default pressure advance\n";
} else { // Model setup succeeded
// Proceed to identify the print speed to use to calculate the adaptive PA value
@@ -242,18 +249,18 @@ std::string AdaptivePAProcessor::process_layer(std::string &&gcode) {
predicted_pa = (*interpolator)(mm3mm_value * adaptive_PA_speed, accel_value);
// This is a bridge, use the dedicated PA setting.
if(isBridge && m_config.adaptive_pressure_advance_bridges.get_at(m_last_config_index) > EPSILON)
predicted_pa = m_config.adaptive_pressure_advance_bridges.get_at(m_last_config_index);
if(isBridge && m_config.adaptive_pressure_advance_bridges.get_at(m_last_extruder_id) > EPSILON)
predicted_pa = m_config.adaptive_pressure_advance_bridges.get_at(m_last_extruder_id);
if (predicted_pa < 0) { // If extrapolation fails, fall back to the default PA for the extruder.
predicted_pa = m_config.enable_pressure_advance.get_at(m_last_config_index) ? m_config.pressure_advance.get_at(m_last_config_index) : 0;
predicted_pa = m_config.enable_pressure_advance.get_at(m_last_extruder_id) ? m_config.pressure_advance.get_at(m_last_extruder_id) : 0;
if(m_config.gcode_comments) output << "; APA: Interpolation failed, using fallback pressure advance value\n";
}
}
if(m_config.gcode_comments) {
// Output debug GCode comments
output << pa_change_line << '\n'; // Output PA change command tag
if(isBridge && m_config.adaptive_pressure_advance_bridges.get_at(m_last_config_index) > EPSILON)
if(isBridge && m_config.adaptive_pressure_advance_bridges.get_at(m_last_extruder_id) > EPSILON)
output << "; APA Model Override (bridge)\n";
output << "; APA Current Speed: " << std::to_string(m_current_feedrate) << "\n";
output << "; APA Next Speed: " << std::to_string(m_next_feedrate) << "\n";
@@ -262,7 +269,7 @@ std::string AdaptivePAProcessor::process_layer(std::string &&gcode) {
output << "; APA Flow rate: " << std::to_string(mm3mm_value * m_max_next_feedrate) << "\n";
output << "; APA Prev PA: " << std::to_string(m_last_predicted_pa) << " New PA: " << std::to_string(predicted_pa) << "\n";
}
if (config_index_changed || std::fabs(predicted_pa - m_last_predicted_pa) > EPSILON) {
if (extruder_changed || std::fabs(predicted_pa - m_last_predicted_pa) > EPSILON) {
output << m_gcodegen.writer().set_pressure_advance(predicted_pa); // Use m_writer to set pressure advance
m_last_predicted_pa = predicted_pa; // Update the last predicted PA value
}
+11 -7
View File
@@ -11,6 +11,7 @@
#include <regex>
#include <memory>
#include <map>
#include <vector>
#include "AdaptivePAInterpolator.hpp"
namespace Slic3r {
@@ -32,7 +33,7 @@ public:
*
* @param gcodegen A reference to the GCode object that generates the G-code.
*/
AdaptivePAProcessor(GCode &gcodegen);
AdaptivePAProcessor(GCode &gcodegen, const std::vector<unsigned int> &tools_used);
/**
* @brief Processes a layer of G-code and applies adaptive pressure advance.
@@ -69,25 +70,28 @@ public:
private:
GCode &m_gcodegen; ///< Reference to the GCode object.
std::unordered_map<unsigned int, std::unique_ptr<AdaptivePAInterpolator>> m_AdaptivePAInterpolators; ///< Map between Interpolator objects and filament config indices (null when adaptive PA is off)
std::unordered_map<unsigned int, std::unique_ptr<AdaptivePAInterpolator>> m_AdaptivePAInterpolators; ///< Map between Interpolator objects and tool ID's
const PrintConfig &m_config; ///< Reference to the print configuration.
double m_last_predicted_pa; ///< Last predicted pressure advance value.
double m_max_next_feedrate; ///< Maximum feed rate (speed) for the upcomming island. If no speed is found, the previous island speed is used.
double m_next_feedrate; ///< First feed rate (speed) for the upcomming island.
double m_current_feedrate; ///< Current, latest feedrate.
int m_last_config_index; ///< Filament config index of the last PA_CHANGE tag.
int m_last_extruder_id; ///< Last used extruder ID.
std::regex m_pa_change_pattern; ///< Regular expression to detect PA_CHANGE pattern.
std::regex m_g1_f_pattern; ///< Regular expression to detect G1 F pattern.
std::smatch m_match; ///< Match results for regular expressions.
/**
* @brief Get the PA interpolator attached to the specified filament config index.
* @brief Get the PA interpolator attached to the specified tool ID.
*
* @param config_index The filament config index (one per filament and extruder variant) for which the PA interpolation model is to be returned.
* @return The Adaptive PA Interpolator object corresponding to that index, or nullptr when adaptive PA is off for it.
* This method manually sets the adaptive PA internally held value.
* Call this when changing tools or in any other case where the internally assumed last PA value may be incorrect
*
* @param An integer with the tool ID for which the PA interpolation model is to be returned.
* @return The Adaptive PA Interpolator object corresponding to that tool.
*/
AdaptivePAInterpolator* getInterpolator(unsigned int config_index);
AdaptivePAInterpolator* getInterpolator(unsigned int tool_id);
};
} // namespace Slic3r
+66 -1
View File
@@ -10,6 +10,7 @@
#include <limits>
#include <numeric>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
@@ -134,15 +135,79 @@ bool tsp_remove_crossings(std::vector<size_t>& path, const Points& centers)
return {std::numeric_limits<size_t>::max(), std::numeric_limits<size_t>::max()};
};
// For many islands, the same scan with the edges binned in a uniform grid over their boxes, so each edge is only tested against the edges sharing a
// cell with it - two edges whose boxes overlap always do. It returns the same crossing as the all-pairs scan
// (smallest i, then smallest j), so the result is unchanged; with thousands of islands on a layer the all-pairs
// scan, repeated after every reversal, never finished. Rebuilding the grid costs more than it saves on small inputs.
constexpr size_t grid_min_size = 500;
BoundingBox extent;
for (size_t idx : path)
extent.merge(centers[idx]);
const int grid_n = std::clamp(int(std::sqrt(double(pn))), 1, 256);
const coord_t cell_w = std::max<coord_t>(1, (extent.max.x() - extent.min.x()) / grid_n + 1);
const coord_t cell_h = std::max<coord_t>(1, (extent.max.y() - extent.min.y()) / grid_n + 1);
const auto for_cells = [&](const Point& a, const Point& b, auto&& fn) {
const int x0 = int((std::min(a.x(), b.x()) - extent.min.x()) / cell_w), x1 = int((std::max(a.x(), b.x()) - extent.min.x()) / cell_w);
const int y0 = int((std::min(a.y(), b.y()) - extent.min.y()) / cell_h), y1 = int((std::max(a.y(), b.y()) - extent.min.y()) / cell_h);
for (int y = y0; y <= y1; ++y)
for (int x = x0; x <= x1; ++x)
fn(y * grid_n + x);
};
std::vector<std::vector<size_t>> edge_cells(size_t(grid_n) * grid_n);
auto find_crossing_grid = [&]() -> std::pair<size_t, size_t> {
for (std::vector<size_t>& cell : edge_cells)
cell.clear();
for (size_t j = 0; j < n_edges; ++j)
for_cells(centers[path[j]], centers[path[(j + 1) % pn]], [&](int cell) { edge_cells[cell].emplace_back(j); });
for (size_t i = 0; i < n_edges; ++i) {
const Point& ai = centers[path[i]];
const Point& bi = centers[path[(i + 1) % pn]];
size_t first_j = std::numeric_limits<size_t>::max();
for_cells(ai, bi, [&](int cell) {
for (size_t j : edge_cells[cell]) {
if (j < i + 2 || j >= first_j) continue;
// Skip the (0, pn-1) pair: edges (0,1) and (pn-1,0) share node 0.
if (i == 0 && j == pn - 1) continue;
const Point& aj = centers[path[j]];
const Point& bj = centers[path[(j + 1) % pn]];
if (!bboxes_overlap(ai, bi, aj, bj)) continue;
if (Geometry::segments_intersect(ai, bi, aj, bj))
first_j = j;
}
});
if (first_j != std::numeric_limits<size_t>::max())
return {i, first_j};
}
return {std::numeric_limits<size_t>::max(), std::numeric_limits<size_t>::max()};
};
// Process crossings one at a time: find first, reverse it, restart scan.
// Cap iterations to prevent infinite loops on collinear/overlapping segments.
int max_iters = static_cast<int>(pn * pn);
bool improved = false;
// Reversing between two segments that only touch or overlap along a line need not remove the intersection, and on
// islands laid out on a regular grid (a tiled texture, an array of parts) the loop cycled through the same orderings
// until the pn * pn cap - effectively forever. Stop as soon as an ordering repeats: until then this is the same loop.
std::unordered_set<uint64_t> seen_paths;
const auto path_hash = [&path]() {
uint64_t h = 1469598103934665603ull; // FNV-1a
for (size_t idx : path)
h = (h ^ uint64_t(idx)) * 1099511628211ull;
return h;
};
seen_paths.insert(path_hash());
while (max_iters-- > 0) {
auto [ci, cj] = find_crossing();
auto [ci, cj] = pn >= grid_min_size ? find_crossing_grid() : find_crossing();
if (ci == std::numeric_limits<size_t>::max()) break;
improved = true;
std::reverse(path.begin() + ci + 1, path.begin() + cj + 1);
if (!seen_paths.insert(path_hash()).second)
break;
}
return improved;
}
+17 -12
View File
@@ -8,6 +8,7 @@
#include <boost/log/trivial.hpp>
#include <random>
#include <algorithm>
#include <limits>
#include <queue>
#include <unordered_map>
@@ -1178,21 +1179,21 @@ std::optional<std::pair<size_t, size_t>> SeamPlacer::find_next_seam_in_layer(
const size_t layer_idx, const float max_distance,
const SeamPlacerImpl::SeamComparator &comparator) const {
using namespace SeamPlacerImpl;
std::vector<size_t> nearby_points_indices = find_nearby_points(*layers[layer_idx].points_tree, projected_position,
max_distance);
if (nearby_points_indices.empty()) {
return {};
// Find the best nearby point and the nearest one. A layer of a fine relief has tens of thousands of candidates within
// the radius, so they are looked at as the search finds them rather than collected into a vector first.
constexpr size_t none = std::numeric_limits<size_t>::max();
size_t best_nearby_point_index = none;
size_t nearest_point_index = none;
visit_nearby_points(*layers[layer_idx].points_tree, projected_position, max_distance,
[&layers, &comparator, &projected_position, layer_idx, &best_nearby_point_index, &nearest_point_index]
(size_t nearby_point_index) {
if (best_nearby_point_index == none) {
// The first point found starts both, as the first of the collected ones did.
best_nearby_point_index = nearest_point_index = nearby_point_index;
}
size_t best_nearby_point_index = nearby_points_indices[0];
size_t nearest_point_index = nearby_points_indices[0];
// Now find best nearby point, nearest point, and corresponding indices
for (const size_t &nearby_point_index : nearby_points_indices) {
const SeamCandidate &point = layers[layer_idx].points[nearby_point_index];
if (point.perimeter.finalized) {
continue; // skip over finalized perimeters, try to find some that is not finalized
return; // skip over finalized perimeters, try to find some that is not finalized
}
if (comparator.is_first_better(point, layers[layer_idx].points[best_nearby_point_index],
projected_position.head<2>())
@@ -1204,6 +1205,10 @@ std::optional<std::pair<size_t, size_t>> SeamPlacer::find_next_seam_in_layer(
|| layers[layer_idx].points[nearest_point_index].perimeter.finalized) {
nearest_point_index = nearby_point_index;
}
});
if (best_nearby_point_index == none) {
return {};
}
const SeamCandidate &best_nearby_point = layers[layer_idx].points[best_nearby_point_index];
+30
View File
@@ -313,6 +313,36 @@ std::vector<size_t> find_nearby_points(const KDTreeIndirectType &kdtree, const P
return visitor.result;
}
// Visits the points within max_distance of center, in the order find_nearby_points() would collect them, and hands
// each of them to `visitor_fn` instead of returning them all: a search over a dense set spends more on collecting the
// points into a vector than on the search itself, and its caller usually keeps only a few of them.
template<typename KDTreeIndirectType, typename PointType, typename VisitorFn>
void visit_nearby_points(const KDTreeIndirectType &kdtree, const PointType &center,
const typename KDTreeIndirectType::CoordType &max_distance, VisitorFn visitor_fn)
{
using CoordType = typename KDTreeIndirectType::CoordType;
struct Visitor {
const KDTreeIndirectType &kdtree;
const PointType center;
const CoordType max_distance_squared;
VisitorFn visitor_fn;
unsigned int operator()(size_t idx, size_t dimension) {
auto dist = CoordType(0);
for (size_t i = 0; i < KDTreeIndirectType::NumDimensions; ++i) {
CoordType d = center[i] - kdtree.coordinate(idx, i);
dist += d * d;
}
if (dist < max_distance_squared)
visitor_fn(idx);
return kdtree.descent_mask(center[dimension], max_distance_squared, idx, dimension);
}
} visitor { kdtree, center, max_distance * max_distance, visitor_fn };
kdtree.visit(visitor);
}
template<typename KDTreeIndirectType, typename PointType>
std::vector<size_t> find_nearby_points(const KDTreeIndirectType &kdtree, const PointType &center,
const typename KDTreeIndirectType::CoordType& max_distance)
+2 -1
View File
@@ -72,10 +72,11 @@ void LayerRegion::slices_to_fill_surfaces_clipped()
by_surface[size_t(surface.surface_type)].emplace_back(&surface);
// Trim surfaces by the fill_boundaries.
this->fill_surfaces.surfaces.clear();
const Polygons fill_boundaries = to_polygons(this->fill_expolygons);
for (size_t surface_type = 0; surface_type < size_t(stCount); ++ surface_type) {
const SurfacesPtr &this_surfaces = by_surface[surface_type];
if (! this_surfaces.empty())
this->fill_surfaces.append(intersection_ex(this_surfaces, this->fill_expolygons), SurfaceType(surface_type));
this->fill_surfaces.append(intersection_ex_by_piece(to_expolygons(this_surfaces), fill_boundaries), SurfaceType(surface_type));
}
}
+270 -68
View File
@@ -8,6 +8,7 @@
#include "MutablePolygon.hpp"
#include "format.hpp"
#include <numeric>
#include <utility>
#include <unordered_set>
@@ -1311,10 +1312,15 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
}
#endif // MM_SEGMENTATION_DEBUG_TOP_BOTTOM
// When the upper surface of an object is occluded, it should no longer be considered the upper surface
// When the upper surface of an object is occluded, it should no longer be considered the upper surface.
// Every (colour, layer) pair is trimmed on its own, so they all run at once: the painted faces of a finely
// textured part project hundreds of thousands of triangles onto one layer, which used to be trimmed serially.
{
for (size_t extruder_idx = 0; extruder_idx < num_facets_states; ++extruder_idx) {
for (size_t layer_idx = 0; layer_idx < layers.size(); ++layer_idx) {
const size_t occluded_pairs = num_facets_states * layers.size();
tbb::parallel_for(tbb::blocked_range<size_t>(0, occluded_pairs), [&](const tbb::blocked_range<size_t> &range) {
for (size_t pair_idx = range.begin(); pair_idx < range.end(); ++pair_idx) {
const size_t extruder_idx = pair_idx / layers.size();
const size_t layer_idx = pair_idx % layers.size();
if (!top_raw[extruder_idx].empty() && !top_raw[extruder_idx][layer_idx].empty() && layer_idx + 1 < layers.size()) {
top_raw[extruder_idx][layer_idx] = diff(top_raw[extruder_idx][layer_idx], input_expolygons[layer_idx + 1]);
}
@@ -1322,7 +1328,7 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
bottom_raw[extruder_idx][layer_idx] = diff(bottom_raw[extruder_idx][layer_idx], input_expolygons[layer_idx - 1]);
}
}
}
});
}
std::vector<std::vector<ExPolygons>> triangles_by_color_bottom(num_facets_states);
@@ -1378,13 +1384,62 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
return out;
};
// Projects a painted top or bottom face `ex` of layer `layer_idx` onto the shell layers below or above it (in
// `shell_layers`, nearest first), one more perimeter in on each, stopping at the first layer where nothing is left.
// Only the slices within the deepest offset of `ex` (three times that with the miter joins) decide the result, so the
// work is done per tile of `ex`'s ExPolygons on the slices cut to the tile's box grown by that much: the same result, but
// each ClipperLib call stays the size of a tile rather than of a layer cut through a fine relief, and the tiles run in
// parallel.
const auto project_to_shells = [&input_expolygons](const ExPolygons &ex, size_t layer_idx, const std::vector<size_t> &shell_layers,
const LayerColorStat &stat, std::vector<ExPolygons> &dst, size_t dst_offset) {
std::vector<float> offsets(shell_layers.size());
float offset = 0.f;
for (size_t i = 0; i < shell_layers.size(); ++i) {
//BBS: offset width should be 2*spacing to avoid too narrow area which has overlap of wall line
offset -= (stat.extrusion_spacing + stat.extrusion_width);
offsets[i] = offset;
}
if (offsets.empty())
return;
const coord_t reach = coord_t(std::ceil(DefaultMiterLimit * std::abs(offsets.back()))) + 10 * SCALED_EPSILON;
const std::vector<ClipperUtils::ExPolygonsTile> tiles = ClipperUtils::tile_expolygons(ex, 16);
// [shell layer][tile]
std::vector<std::vector<ExPolygons>> shells(shell_layers.size(), std::vector<ExPolygons>(tiles.size()));
tbb::parallel_for(size_t(0), tiles.size(), [&](size_t tile_idx) {
const ClipperUtils::ExPolygonsTile &tile = tiles[tile_idx];
const BoundingBox bbox = tile.bbox.inflated(reach);
ExPolygons tile_ex;
tile_ex.reserve(tile.members.size());
for (size_t i : tile.members)
tile_ex.emplace_back(ex[i]);
Polygons layer_slices_trimmed = ClipperUtils::clip_clipper_polygons_with_subject_bbox(input_expolygons[layer_idx], bbox);
for (size_t i = 0; i < shell_layers.size() && ! layer_slices_trimmed.empty(); ++i) {
const ExPolygons trimmed = intersection_ex(layer_slices_trimmed, ClipperUtils::clip_clipper_polygons_with_subject_bbox(input_expolygons[shell_layers[i]], bbox));
shells[i][tile_idx] = opening_ex(intersection_ex(tile_ex, offset_ex(trimmed, offsets[i])), stat.small_region_threshold);
layer_slices_trimmed = to_polygons(trimmed);
}
});
for (size_t i = 0; i < shell_layers.size(); ++i) {
bool empty = true;
for (ExPolygons &shell : shells[i])
if (! shell.empty()) {
append(dst[shell_layers[i] + dst_offset], std::move(shell));
empty = false;
}
if (empty)
break;
}
};
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers, granularity), [&granularity, &num_layers, &num_facets_states, &layer_color_stat, &top_raw, &triangles_by_color_top,
&throw_on_cancel_callback, &input_expolygons, &bottom_raw, &triangles_by_color_bottom,
&throw_on_cancel_callback, &bottom_raw, &triangles_by_color_bottom, &project_to_shells,
&shell_triangles_by_color_top, &shell_triangles_by_color_bottom](const tbb::blocked_range<size_t> &range) {
size_t group_idx = range.begin() / granularity;
size_t layer_idx_offset = (group_idx & 1) * num_layers;
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
for (size_t color_idx = 0; color_idx < num_facets_states; ++color_idx) {
// Each colour writes only its own vectors, so the colours run in parallel: a painted top or bottom face
// projects onto a single layer, which otherwise did all of its colours on one thread.
tbb::parallel_for(size_t(0), size_t(num_facets_states), [&](size_t color_idx) {
throw_on_cancel_callback();
LayerColorStat stat = layer_color_stat(layer_idx, color_idx);
if (std::vector<Polygons> &top = top_raw[color_idx]; ! top.empty() && ! top[layer_idx].empty())
@@ -1393,18 +1448,10 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
top_ex = opening_ex(top_ex, stat.small_region_threshold);
if (! top_ex.empty()) {
append(triangles_by_color_top[color_idx][layer_idx + layer_idx_offset], top_ex);
float offset = 0.f;
ExPolygons layer_slices_trimmed = input_expolygons[layer_idx];
for (int last_idx = int(layer_idx) - 1; last_idx > std::max(int(layer_idx - stat.top_shell_layers), int(0)); --last_idx) {
//BBS: offset width should be 2*spacing to avoid too narrow area which has overlap of wall line
//offset -= stat.extrusion_width ;
offset -= (stat.extrusion_spacing + stat.extrusion_width);
layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]);
ExPolygons last = opening_ex(intersection_ex(top_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold);
if (last.empty())
break;
append(shell_triangles_by_color_top[color_idx][last_idx + layer_idx_offset], std::move(last));
}
std::vector<size_t> shell_layers;
for (int last_idx = int(layer_idx) - 1; last_idx > std::max(int(layer_idx - stat.top_shell_layers), int(0)); --last_idx)
shell_layers.emplace_back(size_t(last_idx));
project_to_shells(top_ex, layer_idx, shell_layers, stat, shell_triangles_by_color_top[color_idx], layer_idx_offset);
}
}
if (std::vector<Polygons> &bottom = bottom_raw[color_idx]; ! bottom.empty() && ! bottom[layer_idx].empty())
@@ -1413,21 +1460,13 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
bottom_ex = opening_ex(bottom_ex, stat.small_region_threshold);
if (! bottom_ex.empty()) {
append(triangles_by_color_bottom[color_idx][layer_idx + layer_idx_offset], bottom_ex);
float offset = 0.f;
ExPolygons layer_slices_trimmed = input_expolygons[layer_idx];
for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + stat.bottom_shell_layers, num_layers); ++last_idx) {
//BBS: offset width should be 2*spacing to avoid too narrow area which has overlap of wall line
//offset -= stat.extrusion_width;
offset -= (stat.extrusion_spacing + stat.extrusion_width);
layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]);
ExPolygons last = opening_ex(intersection_ex(bottom_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold);
if (last.empty())
break;
append(shell_triangles_by_color_bottom[color_idx][last_idx + layer_idx_offset], std::move(last));
}
}
std::vector<size_t> shell_layers;
for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + stat.bottom_shell_layers, num_layers); ++last_idx)
shell_layers.emplace_back(last_idx);
project_to_shells(bottom_ex, layer_idx, shell_layers, stat, shell_triangles_by_color_bottom[color_idx], layer_idx_offset);
}
}
});
}
});
@@ -1437,22 +1476,25 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
&shell_triangles_by_color_top, &shell_triangles_by_color_bottom](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
throw_on_cancel_callback();
ExPolygons painted_exploys;
for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx) {
// The per-colour unions below are independent of each other, so they run in parallel (a painted top or
// bottom face puts all of its colours on one layer); whatever combines the colours stays in colour order.
const auto merge_colour_union = [&](size_t color_idx) {
auto &self = triangles_by_color_merged[color_idx][layer_idx];
append(self, std::move(triangles_by_color_bottom[color_idx][layer_idx]));
append(self, std::move(triangles_by_color_bottom[color_idx][layer_idx + num_layers]));
append(self, std::move(triangles_by_color_top[color_idx][layer_idx]));
append(self, std::move(triangles_by_color_top[color_idx][layer_idx + num_layers]));
self = union_ex(self);
};
tbb::parallel_for(size_t(0), triangles_by_color_merged.size(), merge_colour_union);
append(painted_exploys, self);
}
ExPolygons painted_exploys;
for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx)
append(painted_exploys, triangles_by_color_merged[color_idx][layer_idx]);
painted_exploys = union_ex(painted_exploys);
//BBS: merge the top and bottom shell layers
for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx) {
tbb::parallel_for(size_t(0), triangles_by_color_merged.size(), [&](size_t color_idx) {
auto &self = triangles_by_color_merged[color_idx][layer_idx];
auto top_area = diff_ex(union_ex(shell_triangles_by_color_top[color_idx][layer_idx],
@@ -1466,7 +1508,7 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
append(self, top_area);
append(self, bottom_area);
self = union_ex(self);
}
});
// Trim one region by the other if some of the regions overlap.
ExPolygons painted_regions;
for (size_t color_idx = 1; color_idx < triangles_by_color_merged.size(); ++color_idx) {
@@ -1833,7 +1875,69 @@ static void remove_multiple_edges_in_vertices(MMU_Graph &graph, const std::vecto
}
}
static std::vector<std::vector<ExPolygons>> merge_segmented_layers(const std::vector<std::vector<ExPolygons>> &segmented_regions,
// Finds the islands (layer ExPolygons) a region piece overlaps. A top or bottom region is projected from the neighbouring
// layers and may reach past the island it belongs to, or over several islands.
class IslandLocator
{
public:
explicit IslandLocator(const ExPolygons &islands) : m_islands(islands)
{
m_bboxes.reserve(islands.size());
for (const ExPolygon &island : islands) {
m_bboxes.emplace_back(get_extents(island));
m_extent.merge(m_bboxes.back());
}
if (!m_extent.defined)
return;
const Point size = m_extent.size();
m_cell_w = std::max<coord_t>(1, size.x() / GRID + 1);
m_cell_h = std::max<coord_t>(1, size.y() / GRID + 1);
m_grid.assign(GRID * GRID, {});
for (size_t i = 0; i < m_bboxes.size(); ++i)
for_cells(m_bboxes[i], [&](int cell) { m_grid[cell].emplace_back(i); });
}
void find(const ExPolygon &piece, std::vector<size_t> &out) const
{
out.clear();
const BoundingBox bbox = get_extents(piece);
if (!m_extent.defined || !m_extent.overlap(bbox))
return;
for_cells(bbox, [&](int cell) {
for (size_t i : m_grid[cell])
if (m_bboxes[i].overlap(bbox))
out.emplace_back(i);
});
sort_remove_duplicates(out);
if (out.size() > 1)
out.erase(std::remove_if(out.begin(), out.end(), [&](size_t i) {
const BoundingBox common(m_bboxes[i].min.cwiseMax(bbox.min), m_bboxes[i].max.cwiseMin(bbox.max));
return intersection(ClipperUtils::clip_clipper_polygons_with_subject_bbox(piece, common.inflated(SCALED_EPSILON)),
ClipperUtils::clip_clipper_polygons_with_subject_bbox(m_islands[i], common.inflated(SCALED_EPSILON))).empty();
}), out.end());
}
private:
static constexpr int GRID = 64;
template<typename Fn> void for_cells(const BoundingBox &bb, Fn &&fn) const
{
const int x0 = std::clamp(int((bb.min.x() - m_extent.min.x()) / m_cell_w), 0, GRID - 1), x1 = std::clamp(int((bb.max.x() - m_extent.min.x()) / m_cell_w), 0, GRID - 1);
const int y0 = std::clamp(int((bb.min.y() - m_extent.min.y()) / m_cell_h), 0, GRID - 1), y1 = std::clamp(int((bb.max.y() - m_extent.min.y()) / m_cell_h), 0, GRID - 1);
for (int y = y0; y <= y1; ++y)
for (int x = x0; x <= x1; ++x)
fn(y * GRID + x);
}
const ExPolygons &m_islands;
std::vector<BoundingBox> m_bboxes;
BoundingBox m_extent;
coord_t m_cell_w = 1, m_cell_h = 1;
std::vector<std::vector<size_t>> m_grid;
};
static std::vector<std::vector<ExPolygons>> merge_segmented_layers(const std::vector<ExPolygons> &input_expolygons,
const std::vector<std::vector<ExPolygons>> &segmented_regions,
std::vector<std::vector<ExPolygons>> &&top_and_bottom_layers,
const size_t num_facets_states,
const std::function<void()> &throw_on_cancel_callback)
@@ -1844,33 +1948,91 @@ static std::vector<std::vector<ExPolygons>> merge_segmented_layers(const std::ve
assert(!top_and_bottom_layers.size() || num_facets_states == top_and_bottom_layers.size());
BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Merging segmented layers in parallel - Begin";
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&segmented_regions, &top_and_bottom_layers, &segmented_regions_merged, &num_facets_states, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
// Every region of a layer is merged together with the regions of the islands it overlaps, and the islands are further
// apart than the dimple removal below reaches, so this gives the same result as merging the layer at once. On a layer
// cut through a fine relief every region shares thousands of hole contours with every other, and ClipperLib, splitting
// and re-linking one huge polygon over and over, took anything up to half an hour for a layer; per island each operation
// stays the size of the island, and the islands run in parallel.
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
assert(segmented_regions[layer_idx].size() == num_facets_states);
// Zero is skipped because it is the default color of the volume
for (size_t extruder_id = 1; extruder_id < num_facets_states; ++extruder_id) {
throw_on_cancel_callback();
if (!segmented_regions[layer_idx][extruder_id].empty()) {
ExPolygons segmented_regions_trimmed = segmented_regions[layer_idx][extruder_id];
if (!top_and_bottom_layers.empty()) {
for (const std::vector<ExPolygons> &top_and_bottom_by_extruder : top_and_bottom_layers) {
if (!top_and_bottom_by_extruder[layer_idx].empty() && !segmented_regions_trimmed.empty()) {
segmented_regions_trimmed = diff_ex(segmented_regions_trimmed, top_and_bottom_by_extruder[layer_idx]);
}
}
// Group the islands joined by a region overlapping several of them; the last group takes the regions lying
// outside every island.
const ExPolygons &islands = input_expolygons[layer_idx];
const IslandLocator locator(islands);
std::vector<size_t> parent(islands.size() + 1);
std::iota(parent.begin(), parent.end(), 0);
const auto root = [&parent](size_t i) {
while (parent[i] != i)
i = parent[i] = parent[parent[i]];
return i;
};
// Islands of every piece: side regions of colours 1.., then top/bottom regions of colours 0..
std::vector<const ExPolygon *> pieces;
for (size_t extruder_id = 1; extruder_id < num_facets_states; ++extruder_id)
for (const ExPolygon &piece : segmented_regions[layer_idx][extruder_id])
pieces.emplace_back(&piece);
if (!top_and_bottom_layers.empty())
for (size_t color_idx = 0; color_idx < num_facets_states; ++color_idx)
for (const ExPolygon &piece : top_and_bottom_layers[color_idx][layer_idx])
pieces.emplace_back(&piece);
std::vector<std::vector<size_t>> overlapped(pieces.size());
tbb::parallel_for(size_t(0), pieces.size(), [&](size_t i) { locator.find(*pieces[i], overlapped[i]); });
std::vector<size_t> piece_island(pieces.size());
for (size_t i = 0; i < pieces.size(); ++i) {
piece_island[i] = overlapped[i].empty() ? islands.size() : overlapped[i].front();
for (size_t island : overlapped[i])
parent[root(island)] = root(piece_island[i]);
}
std::vector<size_t> bucket_of(parent.size(), size_t(-1));
size_t num_buckets = 0;
for (size_t i = 0; i < parent.size(); ++i)
if (size_t &b = bucket_of[root(i)]; b == size_t(-1))
b = num_buckets++;
segmented_regions_merged[layer_idx][extruder_id - 1] = std::move(segmented_regions_trimmed);
}
// [bucket][colour]
std::vector<std::vector<ExPolygons>> sides(num_buckets, std::vector<ExPolygons>(num_facets_states));
std::vector<std::vector<ExPolygons>> tops(num_buckets, std::vector<ExPolygons>(num_facets_states));
size_t piece_idx = 0;
for (size_t extruder_id = 1; extruder_id < num_facets_states; ++extruder_id)
for (const ExPolygon &piece : segmented_regions[layer_idx][extruder_id])
sides[bucket_of[root(piece_island[piece_idx++])]][extruder_id].emplace_back(piece);
if (!top_and_bottom_layers.empty())
for (size_t color_idx = 0; color_idx < num_facets_states; ++color_idx)
for (const ExPolygon &piece : top_and_bottom_layers[color_idx][layer_idx])
tops[bucket_of[root(piece_island[piece_idx++])]][color_idx].emplace_back(piece);
if (!top_and_bottom_layers.empty() && !top_and_bottom_layers[extruder_id][layer_idx].empty()) {
bool was_top_and_bottom_empty = segmented_regions_merged[layer_idx][extruder_id - 1].empty();
append(segmented_regions_merged[layer_idx][extruder_id - 1], top_and_bottom_layers[extruder_id][layer_idx]);
// Side regions minus the top/bottom regions of every colour.
std::vector<std::vector<ExPolygons>> merged(num_buckets, std::vector<ExPolygons>(num_facets_states));
tbb::parallel_for(size_t(0), num_buckets, [&](size_t bucket) {
Polygons tops_all;
for (const ExPolygons &t : tops[bucket])
polygons_append(tops_all, t);
for (size_t extruder_id = 1; extruder_id < num_facets_states; ++extruder_id)
if (!sides[bucket][extruder_id].empty())
merged[bucket][extruder_id] = tops_all.empty() ? std::move(sides[bucket][extruder_id]) :
diff_ex_by_piece(sides[bucket][extruder_id], tops_all);
});
// Remove dimples (#7235) appearing after merging side segmentation of the model with tops and bottoms painted layers.
if (!was_top_and_bottom_empty)
segmented_regions_merged[layer_idx][extruder_id - 1] = offset2_ex(union_ex(segmented_regions_merged[layer_idx][extruder_id - 1]), float(SCALED_EPSILON), -float(SCALED_EPSILON));
// Then this colour's top/bottom regions, with the dimples removed (#7235) when the layer has side regions left.
for (size_t extruder_id = 1; extruder_id < num_facets_states; ++extruder_id) {
if (top_and_bottom_layers.empty() || top_and_bottom_layers[extruder_id][layer_idx].empty()) {
for (size_t bucket = 0; bucket < num_buckets; ++bucket)
append(segmented_regions_merged[layer_idx][extruder_id - 1], std::move(merged[bucket][extruder_id]));
continue;
}
bool was_top_and_bottom_empty = true;
for (size_t bucket = 0; bucket < num_buckets && was_top_and_bottom_empty; ++bucket)
was_top_and_bottom_empty = merged[bucket][extruder_id].empty();
tbb::parallel_for(size_t(0), num_buckets, [&](size_t bucket) {
ExPolygons &region = merged[bucket][extruder_id];
append(region, tops[bucket][extruder_id]);
if (!was_top_and_bottom_empty && !region.empty())
region = offset2_ex(union_ex(region), float(SCALED_EPSILON), -float(SCALED_EPSILON));
});
for (size_t bucket = 0; bucket < num_buckets; ++bucket)
append(segmented_regions_merged[layer_idx][extruder_id - 1], std::move(merged[bucket][extruder_id]));
}
}
}); // end of parallel_for
@@ -2157,16 +2319,56 @@ std::vector<std::vector<ExPolygons>> segmentation_by_painting(const PrintObject
assert(!color_poly.empty());
assert(!color_poly.front().empty());
if (has_layer_only_one_color(color_poly)) {
// If the whole layer is painted using the same color, it is not needed to construct a Voronoi diagram for the segmentation of this layer.
segmented_regions[layer_idx][size_t(color_poly.front().front().color)] = input_expolygons[layer_idx];
} else {
MMU_Graph graph = build_graph(layer_idx, color_poly);
remove_multiple_edges_in_vertices(graph, color_poly);
graph.remove_nodes_with_one_arc();
segmented_regions[layer_idx] = extract_colored_segments(graph, num_facets_states);
//segmented_regions[layer_idx] = extract_colored_segments(color_poly, num_extruders, layer_idx);
// Each island (an ExPolygon with its holes) is segmented on its own. Any point of an island is closer to
// that island's contours than to any other island's - the way out crosses its own boundary first - so its
// Voronoi cells, and with them its colour regions, depend on nothing else. A layer cut through a fine relief
// has thousands of islands, and one Voronoi diagram over all of them degenerated into overlapping regions
// that every boolean afterwards had to untangle. Per island the diagrams stay small and the islands run in
// parallel; an island in a single colour needs no diagram at all.
const ExPolygons &islands = input_expolygons[layer_idx];
std::vector<std::pair<size_t, size_t>> island_contours(islands.size()); // [first, last) into color_poly
{
// The same order EdgeGrid::Grid::create() lists the contours in, and so colorize_contours().
size_t idx = 0;
for (size_t island_idx = 0; island_idx < islands.size(); ++island_idx) {
const size_t first = idx;
if (!islands[island_idx].contour.empty())
++idx;
for (const Polygon &hole : islands[island_idx].holes)
if (!hole.empty())
++idx;
island_contours[island_idx] = {first, idx};
}
assert(idx == color_poly.size());
}
std::vector<std::vector<ExPolygons>> island_regions(islands.size());
tbb::parallel_for(size_t(0), islands.size(), [&](size_t island_idx) {
const auto [first, last] = island_contours[island_idx];
if (first == last)
return;
const std::vector<ColoredLines> island_poly(color_poly.begin() + first, color_poly.begin() + last);
std::vector<ExPolygons> &regions = island_regions[island_idx];
if (has_layer_only_one_color(island_poly)) {
regions.assign(num_facets_states, ExPolygons());
regions[size_t(island_poly.front().front().color)].emplace_back(islands[island_idx]);
} else {
MMU_Graph graph = build_graph(layer_idx, island_poly);
remove_multiple_edges_in_vertices(graph, island_poly);
graph.remove_nodes_with_one_arc();
regions = extract_colored_segments(graph, num_facets_states);
// The faces of one colour tile it without overlapping; merged here, where an island is small,
// every later boolean gets a few regions instead of thousands of faces sharing their edges. An
// island with many holes keeps its faces: merged, each colour would be one region with thousands
// of holes, and subtracting from that is far slower than from the faces one at a time.
if (island_poly.size() <= 64)
for (ExPolygons &faces : regions)
if (faces.size() > 1)
faces = union_ex(faces);
}
});
for (std::vector<ExPolygons> &regions : island_regions)
for (size_t color_idx = 0; color_idx < regions.size(); ++color_idx)
append(segmented_regions[layer_idx][color_idx], std::move(regions[color_idx]));
#ifdef MM_SEGMENTATION_DEBUG_REGIONS
export_regions_to_svg(debug_out_path("3-mm-regions-sides-%d-%d.svg", layer_idx, iRun), segmented_regions[layer_idx], input_expolygons[layer_idx]);
@@ -2189,7 +2391,7 @@ std::vector<std::vector<ExPolygons>> segmentation_by_painting(const PrintObject
throw_on_cancel_callback();
}
std::vector<std::vector<ExPolygons>> segmented_regions_merged = merge_segmented_layers(segmented_regions, std::move(top_and_bottom_layers), num_facets_states, throw_on_cancel_callback);
std::vector<std::vector<ExPolygons>> segmented_regions_merged = merge_segmented_layers(input_expolygons, segmented_regions, std::move(top_and_bottom_layers), num_facets_states, throw_on_cancel_callback);
throw_on_cancel_callback();
#ifdef MM_SEGMENTATION_DEBUG_REGIONS
+4 -2
View File
@@ -19,11 +19,13 @@ public:
MultiPoint() {}
MultiPoint(const MultiPoint &other) : points(other.points) {}
MultiPoint(MultiPoint &&other) : points(std::move(other.points)) {}
MultiPoint(MultiPoint &&other) noexcept : points(std::move(other.points)) {}
MultiPoint(std::initializer_list<Point> list) : points(list) {}
explicit MultiPoint(const Points &_points) : points(_points) {}
// Without it, the derived classes' move constructors passing std::move(points) here copied them.
explicit MultiPoint(Points &&_points) noexcept : points(std::move(_points)) {}
MultiPoint& operator=(const MultiPoint &other) { points = other.points; return *this; }
MultiPoint& operator=(MultiPoint &&other) { points = std::move(other.points); return *this; }
MultiPoint& operator=(MultiPoint &&other) noexcept { points = std::move(other.points); return *this; }
virtual ~MultiPoint() = default;
void scale(double factor);
void scale(double factor_x, double factor_y);
+30 -6
View File
@@ -17,6 +17,8 @@
#include <cassert>
#include <unordered_set>
#include <thread>
#include <tbb/blocked_range.h>
#include <tbb/parallel_for.h>
#include "libslic3r/AABBTreeLines.hpp"
#include "Print.hpp"
static const int overhang_sampling_number = 6;
@@ -2481,7 +2483,22 @@ void PerimeterGenerator::process_arachne()
const bool only_one_wall_first_layer = this->config->only_one_wall_first_layer && has_bottom_shell_layers(*this->config);
// we need to process each island separately because we might have different
// extra perimeters for each one
for (const Surface& surface : all_surfaces) {
// Each island is independent up to its outputs, so they are generated in parallel - a layer split into thousands
// of islands (e.g. by colour painting) otherwise ran on one thread - and the outputs are then committed in the
// original island order, which is what the extra overhang perimeters (applied to the last island's loops and to
// all fill surfaces so far) depend on.
struct ArachneSurfaceResult
{
ExtrusionEntityCollection loops;
bool has_loops = false;
ExPolygons infill;
ExPolygons no_overlap;
};
std::vector<ArachneSurfaceResult> results(all_surfaces.size());
tbb::parallel_for(tbb::blocked_range<size_t>(0, all_surfaces.size()), [&](const tbb::blocked_range<size_t> &range) {
for (size_t surface_idx = range.begin(); surface_idx < range.end(); ++surface_idx) {
const Surface &surface = all_surfaces[surface_idx];
ArachneSurfaceResult &result = results[surface_idx];
coord_t bead_width_0 = ext_perimeter_spacing;
// detect how many perimeters must be generated for this island
int loop_number = this->config->wall_loops + surface.extra_perimeters - 1; // 0-indexed loops
@@ -2838,7 +2855,8 @@ void PerimeterGenerator::process_arachne()
this->config->overhang_reverse_internal_only);
}
defer_unsupported_loops(*this, extrusion_coll);
this->loops->append(extrusion_coll);
result.loops = std::move(extrusion_coll);
result.has_loops = true;
}
const coord_t spacing = (perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing;
@@ -2881,9 +2899,7 @@ void PerimeterGenerator::process_arachne()
if (!top_expolygons.empty()) {
infill_exp = union_ex(infill_exp, offset_ex(top_expolygons, double(top_inset)));
}
this->fill_surfaces->append(infill_exp, stInternal);
apply_extra_perimeters(infill_exp);
result.infill = std::move(infill_exp);
// BBS: get the no-overlap infill expolygons
{
@@ -2894,9 +2910,17 @@ void PerimeterGenerator::process_arachne()
float(+min_perimeter_infill_spacing / 2.));
if (!top_expolygons.empty())
polyWithoutOverlap = union_ex(polyWithoutOverlap, top_expolygons);
this->fill_no_overlap->insert(this->fill_no_overlap->end(), polyWithoutOverlap.begin(), polyWithoutOverlap.end());
result.no_overlap = std::move(polyWithoutOverlap);
}
}
});
for (ArachneSurfaceResult &result : results) {
if (result.has_loops)
this->loops->append(result.loops);
this->fill_surfaces->append(result.infill, stInternal);
apply_extra_perimeters(result.infill);
this->fill_no_overlap->insert(this->fill_no_overlap->end(), result.no_overlap.begin(), result.no_overlap.end());
}
}
bool PerimeterGeneratorLoop::is_internal_contour() const
+2 -2
View File
@@ -27,7 +27,7 @@ public:
explicit Polygon(const Points &points) : MultiPoint(points) {}
Polygon(std::initializer_list<Point> points) : MultiPoint(points) {}
Polygon(const Polygon &other) : MultiPoint(other.points) {}
Polygon(Polygon &&other) : MultiPoint(std::move(other.points)) {}
Polygon(Polygon &&other) noexcept : MultiPoint(std::move(other.points)) {}
static Polygon new_scale(const std::vector<Vec2d> &points) {
Polygon pgn;
pgn.points.reserve(points.size());
@@ -36,7 +36,7 @@ public:
return pgn;
}
Polygon& operator=(const Polygon &other) { points = other.points; return *this; }
Polygon& operator=(Polygon &&other) { points = std::move(other.points); return *this; }
Polygon& operator=(Polygon &&other) noexcept { points = std::move(other.points); return *this; }
Point& operator[](Points::size_type idx) { return this->points[idx]; }
const Point& operator[](Points::size_type idx) const { return this->points[idx]; }
+2 -2
View File
@@ -20,7 +20,7 @@ class Polyline : public MultiPoint {
public:
Polyline() {};
Polyline(const Polyline& other) : MultiPoint(other.points), fitting_result(other.fitting_result) {}
Polyline(Polyline &&other) : MultiPoint(std::move(other.points)), fitting_result(std::move(other.fitting_result)) {}
Polyline(Polyline &&other) noexcept : MultiPoint(std::move(other.points)), fitting_result(std::move(other.fitting_result)) {}
Polyline(std::initializer_list<Point> list) : MultiPoint(list) {
fitting_result.clear();
}
@@ -41,7 +41,7 @@ public:
fitting_result = other.fitting_result;
return *this;
}
Polyline& operator=(Polyline&& other) {
Polyline& operator=(Polyline&& other) noexcept {
points = std::move(other.points);
fitting_result = std::move(other.fitting_result);
return *this;
+5 -11
View File
@@ -2439,18 +2439,12 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
if (m_default_region_config.precise_outer_wall && m_default_region_config.wall_sequence != WallSequence::InnerOuter)
warn(L("The precise wall option will be ignored for outer-inner or inner-outer-inner wall sequences."), "precise_outer_wall");
// check adaptive pressure advance model of every extruder variant column of the used filaments
const std::vector<int> &self_index = m_config.filament_self_index.values;
const size_t pa_columns = std::max(m_config.adaptive_pressure_advance_model.size(), size_t(extruders.back()) + 1);
for (size_t column = 0; column < pa_columns; ++column) {
// filament_self_index maps a column to its filament once the filament arrays hold one column per variant
const unsigned int filament_id = self_index.size() == pa_columns ? self_index[column] - 1 : column;
if (!std::binary_search(extruders.begin(), extruders.end(), filament_id))
continue;
if (m_config.adaptive_pressure_advance.get_at(column) &&
m_config.enable_pressure_advance.get_at(column)) {
// check adaptive pressure advance model
for (unsigned int extruder_id : extruders) {
if (m_config.adaptive_pressure_advance.get_at(extruder_id) &&
m_config.enable_pressure_advance.get_at(extruder_id)) {
const std::string pa_model = m_config.adaptive_pressure_advance_model.get_at(column);
const std::string pa_model = m_config.adaptive_pressure_advance_model.get_at(extruder_id);
if (!pa_model.empty()) {
std::string validation_error = AdaptivePAProcessor::validate_adaptive_pa_model(pa_model);
if (!validation_error.empty()) {
-30
View File
@@ -9403,10 +9403,6 @@ void PrintConfigDef::handle_legacy_composite(DynamicPrintConfig &config)
}
config.set_key_value("wiping_volumes_use_custom_matrix", new ConfigOptionBool(custom));
}
// Orca: a config saved before a key joined filament_options_with_variant stores it once per filament
// rather than once per filament variant, and one exported by an older CLI may store a single value.
normalize_filament_values_to_variants(config);
}
const PrintConfigDef print_config_def;
@@ -9509,13 +9505,6 @@ std::set<std::string> filament_options_with_variant = {
"filament_ironing_spacing",
"filament_ironing_inset",
"filament_ironing_speed",
// Orca: pressure advance
"enable_pressure_advance",
"pressure_advance",
"adaptive_pressure_advance",
"adaptive_pressure_advance_model",
"adaptive_pressure_advance_overhangs",
"adaptive_pressure_advance_bridges",
"activate_air_filtration",
"activate_air_filtration_during_print",
"activate_air_filtration_on_completion",
@@ -10723,25 +10712,6 @@ void set_variant_override(ConfigOptionVectorBase &target, const ConfigOptionVect
target.set_to_index(&source, indices, stride);
}
void normalize_filament_values_to_variants(DynamicPrintConfig &config)
{
const auto *self_index = config.option<ConfigOptionInts>("filament_self_index");
if (self_index == nullptr || self_index->empty())
return;
const int filament_count = *std::max_element(self_index->values.begin(), self_index->values.end());
if (filament_count <= 0 || size_t(filament_count) >= self_index->size())
return;
for (const std::string &key : filament_options_with_variant) {
auto *opt = dynamic_cast<ConfigOptionVectorBase *>(config.option(key));
if (opt == nullptr || (opt->size() != size_t(filament_count) && opt->size() != 1))
continue;
std::unique_ptr<ConfigOption> per_filament(opt->clone());
// set_at() takes the first value for a filament past the end of a single-value vector
for (size_t variant = 0; variant < self_index->size(); ++variant)
opt->set_at(per_filament.get(), variant, self_index->values[variant] - 1);
}
}
//used for object/region config
//use the smallest of multiple to single
-5
View File
@@ -884,11 +884,6 @@ extern std::set<std::string> empty_options;
void set_variant_override(ConfigOptionVectorBase &target, const ConfigOptionVectorBase &source,
const std::vector<int> &variant_index, int stride = 1);
// Orca: lays every filament_options_with_variant option out one value per filament variant, as
// filament_self_index maps the variants to filaments. An option holding one value per filament, or a
// single value, gives every variant of a filament that filament's value; other lengths are left alone.
void normalize_filament_values_to_variants(DynamicPrintConfig &config);
extern std::set<std::string> filament_dev_options;
extern void update_static_print_config_from_dynamic(ConfigBase& config, const DynamicPrintConfig& dest_config, std::vector<int> variant_index, std::set<std::string>& key_set1, int stride = 1);
+339 -168
View File
@@ -30,6 +30,7 @@
#include <cstddef>
#include <float.h>
#include <array>
#include <iterator>
#include <mutex>
#include <string>
@@ -42,6 +43,7 @@
#include <boost/log/trivial.hpp>
#include <tbb/parallel_for.h>
#include <tbb/parallel_invoke.h>
#include <tbb/spin_mutex.h>
#include <tbb/concurrent_unordered_set.h>
@@ -1664,7 +1666,9 @@ void PrintObject::detect_surfaces_type()
bool interface_shells = ! spiral_mode && m_config.interface_shells.value;
size_t num_layers = spiral_mode ? std::min(size_t(this->printing_region(0).config().bottom_shell_layers), m_layers.size()) : m_layers.size();
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) {
// The regions of a layer do not see each other here, and a layer cut through a fine relief takes far longer than the
// others, so the regions run next to each other instead of one after another, each still over all layers.
tbb::parallel_for(size_t(0), this->num_printing_regions(), [&](size_t region_id) {
BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << region_id << " in parallel - start";
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
for (Layer *layer : m_layers)
@@ -1722,7 +1726,7 @@ void PrintObject::detect_surfaces_type()
if (upper_layer) {
ExPolygons upper_slices = interface_shells ?
diff_ex(layerm_slices_surfaces, upper_layer->m_regions[region_id]->slices.surfaces, ApplySafetyOffset::Yes) :
diff_ex(layerm_slices_surfaces, upper_layer->lslices, ApplySafetyOffset::Yes);
diff_ex_by_piece(layerm_slices_surfaces, to_polygons(upper_layer->lslices), ApplySafetyOffset::Yes);
surfaces_append(top, opening_ex(upper_slices, offset), stTop);
} else {
// if no upper layer, all surfaces of this one are solid
@@ -1748,7 +1752,7 @@ void PrintObject::detect_surfaces_type()
surfaces_append(
bottom,
opening_ex(
diff_ex(layerm_slices_surfaces, lower_layer->lslices, ApplySafetyOffset::Yes),
diff_ex_by_piece(layerm_slices_surfaces, to_polygons(lower_layer->lslices), ApplySafetyOffset::Yes),
offset),
surface_type_bottom_other);
// if user requested internal shells, we need to identify surfaces
@@ -1779,34 +1783,44 @@ void PrintObject::detect_surfaces_type()
// and top surfaces; let's do an intersection to discover them and consider them
// as bottom surfaces (to allow for bridge detection)
if (! top.empty() && ! bottom.empty()) {
const auto cracks = intersection_ex(top, bottom);
const auto cracks = intersection_ex_by_piece(to_expolygons(top), to_polygons(bottom));
if (!cracks.empty()) {
if (lower_layer) { // Only detect small cracks for non-first layer, because first layer should always be bottom
const float small_crack_threshold = -layerm->flow(frExternalPerimeter).scaled_width() * 1.5;
// Only the bottom surfaces near a crack can take part: one that contains it must contain its box,
// and one whose box misses the grown crack is left unchanged by removing it. A layer cut through
// a fine relief has thousands of both, which made this loop quadratic.
for (const auto& crack : cracks) {
if (offset_ex(crack, small_crack_threshold).empty()) {
// For small cracks, if it's part of a large bottom surface, then it should be added to bottom as well
if (std::any_of(bottom.begin(), bottom.end(), [&crack, small_crack_threshold](const Surface& s) {
const BoundingBox crack_bbox = get_extents(crack);
if (std::any_of(bottom.begin(), bottom.end(), [&crack, &crack_bbox, small_crack_threshold](const Surface& s) {
const auto& se = s.expolygon;
return diff_ex(crack, se, ApplySafetyOffset::Yes).empty()
return get_extents(se).inflated(SCALED_EPSILON).contains(crack_bbox)
&& diff_ex(crack, se, ApplySafetyOffset::Yes).empty()
&& se.area() > crack.area() * 2
&& !offset_ex(diff_ex(se, crack), small_crack_threshold).empty();
})) continue;
// Crack too small, leave it as part of the top surface, remove it from bottom surfaces
const ExPolygons grown_crack = offset_ex(crack, -small_crack_threshold);
const BoundingBox grown_bbox = get_extents(grown_crack);
Surfaces bot_tmp;
for (auto& b : bottom) {
surfaces_append(bot_tmp, diff_ex(b.expolygon, offset_ex(crack, -small_crack_threshold)), b.surface_type);
if (get_extents(b.expolygon).overlap(grown_bbox))
surfaces_append(bot_tmp, diff_ex(b.expolygon, grown_crack), b.surface_type);
else
bot_tmp.emplace_back(std::move(b));
}
bottom = std::move(bot_tmp);
}
}
}
Polygons top_polygons = to_polygons(std::move(top));
ExPolygons top_expolygons = to_expolygons(std::move(top));
top.clear();
surfaces_append(top, diff_ex(top_polygons, bottom), stTop);
surfaces_append(top, diff_ex_by_piece(top_expolygons, to_polygons(bottom)), stTop);
}
}
@@ -1897,7 +1911,7 @@ void PrintObject::detect_surfaces_type()
{
Polygons topbottom = to_polygons(top);
polygons_append(topbottom, to_polygons(bottom));
surfaces_append(surfaces_out, diff_ex(surfaces_prev_expolys, topbottom), stInternal);
surfaces_append(surfaces_out, diff_ex_by_piece(surfaces_prev_expolys, topbottom), stInternal);
}
surfaces_append(surfaces_out, std::move(top));
@@ -2074,29 +2088,31 @@ void PrintObject::detect_surfaces_type()
}
}
);
// ==============================================================================================================
// === ORCA: Interim workaround - for now the new stInternalAfterExternalBridge surfaace is re-classified ==============
// === back to a bottom bridge. As a starting point, this improves bridging reliability as it extrudes ==========
// === two external bridge layers. However, TODO: Implement a new surface type throughout the codebase ==========
// ==============================================================================================================
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) {
tbb::parallel_for( tbb::blocked_range<size_t>(0, m_layers.size()), [this, region_id](const tbb::blocked_range<size_t> &range) {
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) {
Surfaces &surfs = m_layers[idx_layer]->m_regions[region_id]->slices.surfaces;
for (Surface &s : surfs) {
if (s.surface_type == stInternalAfterExternalBridge) {
s.surface_type = stBottomBridge;
}
}
}
}
);
}
}
// ==============================================================================================================
// === ORCA: End of second external bridge layer changes =======================================================
// ==============================================================================================================
}); // for each this->print->region_count
// ==============================================================================================================
// === ORCA: Interim workaround - for now the new stInternalAfterExternalBridge surfaace is re-classified ==============
// === back to a bottom bridge. As a starting point, this improves bridging reliability as it extrudes ==========
// === two external bridge layers. However, TODO: Implement a new surface type throughout the codebase ==========
// ==============================================================================================================
// Once all the regions have their second bridge layer, and before their slices are trimmed into fill surfaces below.
if ((this->config().enable_extra_bridge_layer.value == eblApplyToAll) || (this->config().enable_extra_bridge_layer.value == eblExternalBridgeOnly)) {
tbb::parallel_for(tbb::blocked_range<size_t>(0, m_layers.size()), [this](const tbb::blocked_range<size_t> &range) {
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer)
for (LayerRegion *layerm : m_layers[idx_layer]->regions())
for (Surface &s : layerm->slices.surfaces)
if (s.surface_type == stInternalAfterExternalBridge)
s.surface_type = stBottomBridge;
});
m_print->throw_if_canceled();
}
tbb::parallel_for(size_t(0), this->num_printing_regions(), [&](size_t region_id) {
BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << region_id << " - clipping in parallel - start";
// Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces.
tbb::parallel_for(
@@ -2113,7 +2129,7 @@ void PrintObject::detect_surfaces_type()
});
m_print->throw_if_canceled();
BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << region_id << " - clipping in parallel - end";
} // for each this->print->region_count
});
// Mark the object to have the region slices classified (typed, which also means they are split based on whether they are supported, bridging, top layers etc.)
m_typed_slices = true;
@@ -2180,8 +2196,10 @@ void PrintObject::process_external_surfaces()
BOOST_LOG_TRIVIAL(debug) << "Collecting surfaces covered with extrusions in parallel - end";
}
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) {
BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces for region " << region_id << " in parallel - start";
BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces in parallel - start";
// The regions of a layer do not see each other here, and a layer cut through a fine relief takes far longer than the
// others, so the regions run next to each other instead of one after another, each still over all layers.
tbb::parallel_for(size_t(0), this->num_printing_regions(), [this, &surfaces_covered](size_t region_id) {
tbb::parallel_for(
tbb::blocked_range<size_t>(0, m_layers.size()),
[this, &surfaces_covered, region_id](const tbb::blocked_range<size_t>& range) {
@@ -2196,9 +2214,9 @@ void PrintObject::process_external_surfaces()
}
}
);
});
m_print->throw_if_canceled();
BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces for region " << region_id << " in parallel - end";
}
BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces in parallel - end";
}
void PrintObject::discover_vertical_shells()
@@ -2237,10 +2255,10 @@ void PrintObject::discover_vertical_shells()
// The "ensure vertical wall thickness" feature is not applicable to any of the regions. Quit.
return;
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - start : cache top / bottom";
//FIXME Improve the heuristics for a grain size.
size_t grain_size = std::max(num_layers / 16, size_t(1));
// One layer per task: on a layer cut through a fine relief the unions below take far longer than elsewhere, and a
// few such layers next to each other must not end up in one task.
tbb::parallel_for(
tbb::blocked_range<size_t>(0, num_layers, grain_size),
tbb::blocked_range<size_t>(0, num_layers, 1),
[this, &cache_top_botom_regions](const tbb::blocked_range<size_t>& range) {
const std::initializer_list<SurfaceType> surfaces_bottom { stBottom, stBottomBridge };
const size_t num_regions = this->num_printing_regions();
@@ -2248,26 +2266,38 @@ void PrintObject::discover_vertical_shells()
m_print->throw_if_canceled();
const Layer &layer = *m_layers[idx_layer];
DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[idx_layer];
// Simulate single set of perimeters over all merged regions.
float perimeter_offset = 0.f;
float perimeter_min_spacing = FLT_MAX;
const auto top_bottom_expansion = [&layer](size_t region_id) {
return float(layer.m_regions[region_id]->flow(frSolidInfill).scaled_spacing()) * top_bottom_expansion_coeff;
};
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
static size_t debug_idx = 0;
++ debug_idx;
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
for (size_t region_id = 0; region_id < num_regions; ++ region_id) {
LayerRegion &layerm = *layer.m_regions[region_id];
float top_bottom_expansion = float(layerm.flow(frSolidInfill).scaled_spacing()) * top_bottom_expansion_coeff;
// Top surfaces.
append(cache.top_surfaces, offset(layerm.slices.filter_by_type(stTop), top_bottom_expansion));
// The top surfaces, the bottom surfaces and the holes are independent of each other.
tbb::parallel_invoke(
[&]() {
for (size_t region_id = 0; region_id < num_regions; ++ region_id)
append(cache.top_surfaces, offset(layer.m_regions[region_id]->slices.filter_by_type(stTop), top_bottom_expansion(region_id)));
// append(cache.top_surfaces, offset(layerm.fill_surfaces.filter_by_type(stTop), top_bottom_expansion));
// Bottom surfaces.
append(cache.bottom_surfaces, offset(layerm.slices.filter_by_types(surfaces_bottom), top_bottom_expansion));
// Save some computing time by reducing the number of polygons.
cache.top_surfaces = union_(cache.top_surfaces);
},
[&]() {
for (size_t region_id = 0; region_id < num_regions; ++ region_id)
append(cache.bottom_surfaces, offset(layer.m_regions[region_id]->slices.filter_by_types(surfaces_bottom), top_bottom_expansion(region_id)));
// append(cache.bottom_surfaces, offset(layerm.fill_surfaces.filter_by_types(surfaces_bottom), top_bottom_expansion));
cache.bottom_surfaces = union_(cache.bottom_surfaces);
},
[&]() {
// Simulate single set of perimeters over all merged regions.
float perimeter_offset = 0.f;
float perimeter_min_spacing = FLT_MAX;
for (size_t region_id = 0; region_id < num_regions; ++ region_id) {
const LayerRegion &layerm = *layer.m_regions[region_id];
// Calculate the maximum perimeter offset as if the slice was extruded with a single extruder only.
// First find the maxium number of perimeters per region slice.
unsigned int perimeters = 0;
for (Surface &s : layerm.slices.surfaces)
for (const Surface &s : layerm.slices.surfaces)
perimeters = std::max<unsigned int>(perimeters, s.extra_perimeters);
perimeters += layerm.region().config().wall_loops.value;
// Then calculate the infill offset.
@@ -2280,9 +2310,6 @@ void PrintObject::discover_vertical_shells()
}
polygons_append(cache.holes, to_polygons(layerm.fill_expolygons));
}
// Save some computing time by reducing the number of polygons.
cache.top_surfaces = union_(cache.top_surfaces);
cache.bottom_surfaces = union_(cache.bottom_surfaces);
// For a multi-material print, simulate perimeter / infill split as if only a single extruder has been used for the whole print.
if (perimeter_offset > 0.) {
// The layer.lslices are forced to merge by expanding them first.
@@ -2298,106 +2325,32 @@ void PrintObject::discover_vertical_shells()
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
}
cache.holes = union_(cache.holes);
});
}
});
m_print->throw_if_canceled();
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - end : cache top / bottom";
}
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) {
const PrintRegion &region = this->printing_region(region_id);
if (region.config().ensure_vertical_shell_thickness.value != evstAll )
// This region will be handled by discover_horizontal_shells().
continue;
//FIXME Improve the heuristics for a grain size.
size_t grain_size = std::max(num_layers / 16, size_t(1));
if (! top_bottom_surfaces_all_regions) {
// This is either a single material print, or a multi-material print and interface_shells are enabled, meaning that the vertical shell thickness
// is calculated over a single material.
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - start : cache top / bottom";
tbb::parallel_for(
tbb::blocked_range<size_t>(0, num_layers, grain_size),
[this, region_id, &cache_top_botom_regions](const tbb::blocked_range<size_t>& range) {
const std::initializer_list<SurfaceType> surfaces_bottom { stBottom, stBottomBridge };
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
m_print->throw_if_canceled();
Layer &layer = *m_layers[idx_layer];
LayerRegion &layerm = *layer.m_regions[region_id];
float top_bottom_expansion = float(layerm.flow(frSolidInfill).scaled_spacing()) * top_bottom_expansion_coeff;
// Top surfaces.
auto &cache = cache_top_botom_regions[idx_layer];
cache.top_surfaces = offset(layerm.slices.filter_by_type(stTop), top_bottom_expansion);
// append(cache.top_surfaces, offset(layerm.fill_surfaces.filter_by_type(stTop), top_bottom_expansion));
// Bottom surfaces.
cache.bottom_surfaces = offset(layerm.slices.filter_by_types(surfaces_bottom), top_bottom_expansion);
// append(cache.bottom_surfaces, offset(layerm.fill_surfaces.filter_by_types(surfaces_bottom), top_bottom_expansion));
// Holes over all regions. Only collect them once, they are valid for all region_id iterations.
if (cache.holes.empty()) {
for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id)
polygons_append(cache.holes, to_polygons(layer.regions()[region_id]->fill_expolygons));
}
}
});
m_print->throw_if_canceled();
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - end : cache top / bottom";
}
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - start : ensure vertical wall thickness";
grain_size = 1;
tbb::parallel_for(
tbb::blocked_range<size_t>(0, num_layers, grain_size),
[this, region_id, &cache_top_botom_regions]
(const tbb::blocked_range<size_t>& range) {
// printf("discover_vertical_shells from %d to %d\n", range.begin(), range.end());
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
m_print->throw_if_canceled();
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
static size_t debug_idx = 0;
++ debug_idx;
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
Layer *layer = m_layers[idx_layer];
LayerRegion *layerm = layer->m_regions[region_id];
const PrintRegionConfig &region_config = layerm->region().config();
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
layerm->export_region_slices_to_svg_debug("3_discover_vertical_shells-initial");
layerm->export_region_fill_surfaces_to_svg_debug("3_discover_vertical_shells-initial");
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
Flow solid_infill_flow = layerm->flow(frSolidInfill);
coord_t infill_line_spacing = solid_infill_flow.scaled_spacing();
// Find a union of perimeters below / above this surface to guarantee a minimum shell thickness.
// With one top/bottom cache for all regions, the shell and hole accumulation in the loop below depends on nothing
// region-specific but the shell settings and the external perimeter spacing, so a region sharing them with an earlier
// one reuses its result instead of repeating it: that accumulation is a union over several layers of top/bottom
// surfaces, and a multi-material print has a region per filament.
using AccumulationKey = std::array<double, 5>;
struct ShellAccumulation
{
AccumulationKey key;
Polygons shell;
Polygons holes;
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
ExPolygons shell_ex;
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
float min_perimeter_infill_spacing = float(infill_line_spacing) * 1.05f;
#if 0
// #ifdef SLIC3R_DEBUG_SLICE_PROCESSING
{
Slic3r::SVG svg_cummulative(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d.svg", debug_idx), this->bounding_box());
for (int n = (int)idx_layer - n_extra_bottom_layers; n <= (int)idx_layer + n_extra_top_layers; ++ n) {
if (n < 0 || n >= (int)m_layers.size())
continue;
ExPolygons &expolys = m_layers[n]->perimeter_expolygons;
for (size_t i = 0; i < expolys.size(); ++ i) {
Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d-layer%d-expoly%d.svg", debug_idx, n, i), get_extents(expolys[i]));
svg.draw(expolys[i]);
svg.draw_outline(expolys[i].contour, "black", scale_(0.05));
svg.draw_outline(expolys[i].holes, "blue", scale_(0.05));
svg.Close();
svg_cummulative.draw(expolys[i]);
svg_cummulative.draw_outline(expolys[i].contour, "black", scale_(0.05));
svg_cummulative.draw_outline(expolys[i].holes, "blue", scale_(0.05));
}
}
}
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
};
const auto accumulation_key = [](const PrintRegionConfig &region_config, const LayerRegion *layerm) {
return AccumulationKey{ double(region_config.top_shell_layers.value), region_config.top_shell_thickness.value,
double(region_config.bottom_shell_layers.value), region_config.bottom_shell_thickness.value,
double(layerm->flow(frExternalPerimeter).scaled_spacing()) };
};
const auto accumulate_shell = [this, &cache_top_botom_regions](size_t idx_layer, const PrintRegionConfig &region_config,
const LayerRegion *layerm, Polygons &shell, Polygons &holes) {
const Layer *layer = m_layers[idx_layer];
polygons_append(holes, cache_top_botom_regions[idx_layer].holes);
auto combine_holes = [&holes](const Polygons &holes2) {
if (holes.empty() || holes2.empty())
@@ -2472,6 +2425,141 @@ void PrintObject::discover_vertical_shells()
(i > ibottom || bottom_z - m_layers[i]->print_z < region_config.bottom_shell_thickness - EPSILON))
combine_holes(cache_top_botom_regions[i].holes);
}
};
std::vector<std::vector<ShellAccumulation>> shell_accumulations(top_bottom_surfaces_all_regions ? num_layers : 0);
if (! shell_accumulations.empty()) {
// Every (layer, key) pair is accumulated once, before the regions, so that nothing in the loop below is shared
// between them and they can run next to each other.
std::vector<std::array<size_t, 3>> todo; // layer, its slot, a region holding the key
for (size_t idx_layer = 0; idx_layer < num_layers; ++ idx_layer) {
std::vector<ShellAccumulation> &accumulations = shell_accumulations[idx_layer];
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) {
if (this->printing_region(region_id).config().ensure_vertical_shell_thickness.value != evstAll)
continue;
const LayerRegion *layerm = m_layers[idx_layer]->m_regions[region_id];
const AccumulationKey key = accumulation_key(layerm->region().config(), layerm);
if (std::none_of(accumulations.begin(), accumulations.end(), [&key](const ShellAccumulation &a) { return a.key == key; })) {
todo.push_back({ idx_layer, accumulations.size(), region_id });
accumulations.push_back({ key, {}, {} });
}
}
}
tbb::parallel_for(size_t(0), todo.size(), [this, &todo, &shell_accumulations, &accumulate_shell](size_t i) {
m_print->throw_if_canceled();
const LayerRegion *layerm = m_layers[todo[i][0]]->m_regions[todo[i][2]];
ShellAccumulation &out = shell_accumulations[todo[i][0]][todo[i][1]];
accumulate_shell(todo[i][0], layerm->region().config(), layerm, out.shell, out.holes);
});
m_print->throw_if_canceled();
}
const auto process_region = [&](size_t region_id) {
const PrintRegion &region = this->printing_region(region_id);
if (region.config().ensure_vertical_shell_thickness.value != evstAll )
// This region will be handled by discover_horizontal_shells().
return;
//FIXME Improve the heuristics for a grain size.
size_t grain_size = std::max(num_layers / 16, size_t(1));
if (! top_bottom_surfaces_all_regions) {
// This is either a single material print, or a multi-material print and interface_shells are enabled, meaning that the vertical shell thickness
// is calculated over a single material.
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - start : cache top / bottom";
tbb::parallel_for(
tbb::blocked_range<size_t>(0, num_layers, grain_size),
[this, region_id, &cache_top_botom_regions](const tbb::blocked_range<size_t>& range) {
const std::initializer_list<SurfaceType> surfaces_bottom { stBottom, stBottomBridge };
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
m_print->throw_if_canceled();
Layer &layer = *m_layers[idx_layer];
LayerRegion &layerm = *layer.m_regions[region_id];
float top_bottom_expansion = float(layerm.flow(frSolidInfill).scaled_spacing()) * top_bottom_expansion_coeff;
// Top surfaces.
auto &cache = cache_top_botom_regions[idx_layer];
cache.top_surfaces = offset(layerm.slices.filter_by_type(stTop), top_bottom_expansion);
// append(cache.top_surfaces, offset(layerm.fill_surfaces.filter_by_type(stTop), top_bottom_expansion));
// Bottom surfaces.
cache.bottom_surfaces = offset(layerm.slices.filter_by_types(surfaces_bottom), top_bottom_expansion);
// append(cache.bottom_surfaces, offset(layerm.fill_surfaces.filter_by_types(surfaces_bottom), top_bottom_expansion));
// Holes over all regions. Only collect them once, they are valid for all region_id iterations.
if (cache.holes.empty()) {
for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id)
polygons_append(cache.holes, to_polygons(layer.regions()[region_id]->fill_expolygons));
}
}
});
m_print->throw_if_canceled();
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - end : cache top / bottom";
}
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - start : ensure vertical wall thickness";
grain_size = 1;
tbb::parallel_for(
tbb::blocked_range<size_t>(0, num_layers, grain_size),
[this, region_id, &shell_accumulations, &accumulation_key, &accumulate_shell]
(const tbb::blocked_range<size_t>& range) {
// printf("discover_vertical_shells from %d to %d\n", range.begin(), range.end());
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
m_print->throw_if_canceled();
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
static size_t debug_idx = 0;
++ debug_idx;
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
Layer *layer = m_layers[idx_layer];
LayerRegion *layerm = layer->m_regions[region_id];
const PrintRegionConfig &region_config = layerm->region().config();
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
layerm->export_region_slices_to_svg_debug("3_discover_vertical_shells-initial");
layerm->export_region_fill_surfaces_to_svg_debug("3_discover_vertical_shells-initial");
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
Flow solid_infill_flow = layerm->flow(frSolidInfill);
coord_t infill_line_spacing = solid_infill_flow.scaled_spacing();
// Find a union of perimeters below / above this surface to guarantee a minimum shell thickness.
Polygons shell;
Polygons holes;
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
ExPolygons shell_ex;
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
float min_perimeter_infill_spacing = float(infill_line_spacing) * 1.05f;
#if 0
// #ifdef SLIC3R_DEBUG_SLICE_PROCESSING
{
Slic3r::SVG svg_cummulative(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d.svg", debug_idx), this->bounding_box());
for (int n = (int)idx_layer - n_extra_bottom_layers; n <= (int)idx_layer + n_extra_top_layers; ++ n) {
if (n < 0 || n >= (int)m_layers.size())
continue;
ExPolygons &expolys = m_layers[n]->perimeter_expolygons;
for (size_t i = 0; i < expolys.size(); ++ i) {
Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d-layer%d-expoly%d.svg", debug_idx, n, i), get_extents(expolys[i]));
svg.draw(expolys[i]);
svg.draw_outline(expolys[i].contour, "black", scale_(0.05));
svg.draw_outline(expolys[i].holes, "blue", scale_(0.05));
svg.Close();
svg_cummulative.draw(expolys[i]);
svg_cummulative.draw_outline(expolys[i].contour, "black", scale_(0.05));
svg_cummulative.draw_outline(expolys[i].holes, "blue", scale_(0.05));
}
}
}
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
const AccumulationKey key = accumulation_key(region_config, layerm);
const ShellAccumulation *reused = shell_accumulations.empty() ? nullptr :
[&]() -> const ShellAccumulation * {
for (const ShellAccumulation &a : shell_accumulations[idx_layer])
if (a.key == key)
return &a;
return nullptr;
}();
if (reused != nullptr) {
shell = reused->shell;
holes = reused->holes;
} else
accumulate_shell(idx_layer, region_config, layerm, shell, holes);
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
{
Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-%d.svg", debug_idx), get_extents(shell));
@@ -2565,11 +2653,8 @@ void PrintObject::discover_vertical_shells()
Polygons object_volume;
Polygons internal_volume;
{
Polygons shrinked_bottom_slice = idx_layer > 0 ? to_polygons(m_layers[idx_layer - 1]->lslices) : Polygons{};
Polygons shrinked_upper_slice = (idx_layer + 1) < m_layers.size() ?
to_polygons(m_layers[idx_layer + 1]->lslices) :
Polygons{};
object_volume = intersection(shrinked_bottom_slice, shrinked_upper_slice);
if (idx_layer > 0 && idx_layer + 1 < m_layers.size())
object_volume = to_polygons(intersection_ex_by_piece(m_layers[idx_layer - 1]->lslices, to_polygons(m_layers[idx_layer + 1]->lslices)));
internal_volume = closing(polygonsInternal, SCALED_EPSILON);
}
@@ -2580,15 +2665,34 @@ void PrintObject::discover_vertical_shells()
// the in-model condition is there due to small sloping surfaces, e.g. top of the hull of the benchy
// 2. the area does not fully cover an internal polygon
// This is there mainly for a very thin parts, where the solid layers would be missing if the part area is quite small
// Both tests below compare a small piece against the whole layer. Done literally, that is
// quadratic in the number of pieces, which is what a layer split up by colour painting has,
// so each is restricted to the part of the layer near the piece with an identical result:
// object_volume is clipped to the piece's box, and only the internal polygons whose box meets
// the expanded piece take part in the count, since the others pass through the difference
// unchanged and add the same number to both sides of it.
std::vector<BoundingBox> internal_bboxes;
internal_bboxes.reserve(internal_volume.size());
for (const Polygon &poly : internal_volume)
internal_bboxes.emplace_back(get_extents(poly));
regularized_shell.erase(std::remove_if(regularized_shell.begin(), regularized_shell.end(),
[&internal_volume, &min_perimeter_infill_spacing,
[&internal_volume, &internal_bboxes, &min_perimeter_infill_spacing,
&object_volume](const ExPolygon &p) {
return (p.area() < min_perimeter_infill_spacing * scaled(1.5) ||
const bool small = p.area() < min_perimeter_infill_spacing * scaled(1.5) ||
(p.area() < min_perimeter_infill_spacing * scaled(8.0) &&
diff(to_polygons(p), object_volume).empty())) &&
diff(internal_volume,
expand(to_polygons(p), min_perimeter_infill_spacing))
.size() >= internal_volume.size();
diff(to_polygons(p),
ClipperUtils::clip_clipper_polygons_with_subject_bbox(
object_volume, get_extents(p).inflated(SCALED_EPSILON)))
.empty());
if (!small)
return false;
const Polygons expanded = expand(to_polygons(p), min_perimeter_infill_spacing);
const BoundingBox bbox = get_extents(expanded);
Polygons nearby;
for (size_t i = 0; i < internal_volume.size(); ++i)
if (internal_bboxes[i].overlap(bbox))
nearby.emplace_back(internal_volume[i]);
return diff(nearby, expanded).size() >= nearby.size();
}),
regularized_shell.end());
}
@@ -2610,8 +2714,9 @@ void PrintObject::discover_vertical_shells()
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
// Trim the internal & internalvoid by the shell.
Slic3r::ExPolygons new_internal = diff_ex(layerm->fill_surfaces.filter_by_type(stInternal), regularized_shell);
Slic3r::ExPolygons new_internal_void = diff_ex(layerm->fill_surfaces.filter_by_type(stInternalVoid), regularized_shell);
const Polygons regularized_shell_polygons = to_polygons(regularized_shell);
Slic3r::ExPolygons new_internal = diff_ex_by_piece(to_expolygons(layerm->fill_surfaces.filter_by_type(stInternal)), regularized_shell_polygons);
Slic3r::ExPolygons new_internal_void = diff_ex_by_piece(to_expolygons(layerm->fill_surfaces.filter_by_type(stInternalVoid)), regularized_shell_polygons);
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
{
@@ -2638,7 +2743,15 @@ void PrintObject::discover_vertical_shells()
layerm->export_region_fill_surfaces_to_svg_debug("3_discover_vertical_shells-final");
}
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
} // for each region
}; // for each region
if (top_bottom_surfaces_all_regions)
// Nothing is shared between the regions, and a layer cut through a fine relief takes far longer than the others,
// so they run next to each other instead of one after another.
tbb::parallel_for(size_t(0), this->num_printing_regions(), process_region);
else
// Here every region fills the one top/bottom cache with its own surfaces first.
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id)
process_region(region_id);
} // void PrintObject::discover_vertical_shells()
// #define DEBUG_BRIDGE_OVER_INFILL
@@ -3159,6 +3272,16 @@ void PrintObject::bridge_over_infill()
vertical_lines[i].b = Point{x, y_max};
}
// The vertical lines only span the bridged area's x range, so anchors entirely outside it can never be
// hit. Leaving them out gives the same intersections without building a tree over the whole layer's
// boundary for every bridge.
const coord_t scan_x_min = bb_x.min.x();
const coord_t scan_x_max = bb_x.min.x() + coord_t(n_vlines) * scan_spacing;
anchors.erase(std::remove_if(anchors.begin(), anchors.end(),
[scan_x_min, scan_x_max](const Line &l) {
return std::max(l.a.x(), l.b.x()) < scan_x_min || std::min(l.a.x(), l.b.x()) > scan_x_max;
}),
anchors.end());
auto anchors_and_walls_tree = AABBTreeLines::LinesDistancer<Line>{std::move(anchors)};
auto bridged_area_tree = AABBTreeLines::LinesDistancer<Line>{to_lines(bridged_area)};
@@ -3403,28 +3526,63 @@ void PrintObject::bridge_over_infill()
std::vector<CandidateSurface> expanded_surfaces;
expanded_surfaces.reserve(surfaces_by_layer[lidx].size());
// The expanded fill boundary depends only on the bridging flow, and total_fill_area is not
// modified below, so build it once per spacing rather than once per candidate. A layer split
// into many candidates (e.g. by colour painting) otherwise repeats a layer-wide offset for each.
std::map<coord_t, Polylines> boundary_by_spacing;
// expansion_area is a clean, non-overlapping set, so uniting it with a bridge or cutting a bridge
// out of it only changes the polygons near that bridge. The rest are passed through untouched
// instead of being fed to ClipperLib with the whole layer again for every candidate.
// Not `near`/`far`: the Windows headers still define those as macros, and they expand to
// nothing, which turns the declaration below into an empty one.
const auto split_near = [](const Polygons &polys, const BoundingBox &bbox, Polygons &rest) {
Polygons nearby;
for (const Polygon &p : polys)
(get_extents(p).overlap(bbox) ? nearby : rest).emplace_back(p);
return nearby;
};
for (const CandidateSurface &candidate : surfaces_by_layer[lidx]) {
const auto &region_config = candidate.region->region().config();
const bool turning_pattern = region_config.sparse_infill_pattern == ipHilbertCurve ||
region_config.sparse_infill_pattern == ipOctagramSpiral;
const Flow &flow = candidate.region->bridging_flow(frSolidInfill, true);
Polygons area_to_be_bridge = expand(candidate.new_polys, flow.scaled_spacing());
area_to_be_bridge = intersection(area_to_be_bridge, deep_infill_area);
// deep_infill_area and internal_unsupported_area cover the whole layer; only their part under
// this candidate can change the results, so they are clipped to its box first.
if (!area_to_be_bridge.empty())
area_to_be_bridge = intersection(area_to_be_bridge,
ClipperUtils::clip_clipper_polygons_with_subject_bbox(
deep_infill_area, get_extents(area_to_be_bridge).inflated(SCALED_EPSILON)));
area_to_be_bridge.erase(std::remove_if(area_to_be_bridge.begin(), area_to_be_bridge.end(),
[internal_unsupported_area](const Polygon &p) {
return intersection({p}, internal_unsupported_area).empty();
[&internal_unsupported_area](const Polygon &p) {
return intersection({p}, ClipperUtils::clip_clipper_polygons_with_subject_bbox(
internal_unsupported_area,
get_extents(p).inflated(SCALED_EPSILON)))
.empty();
}),
area_to_be_bridge.end());
Polygons limiting_area = union_(area_to_be_bridge, expansion_area);
if (area_to_be_bridge.empty())
continue;
Polylines boundary_plines = to_polylines(expand(total_fill_area, 1.3 * flow.scaled_spacing()));
Polygons limiting_area;
const Polygons near_expansion = split_near(expansion_area, get_extents(area_to_be_bridge).inflated(SCALED_EPSILON),
limiting_area);
append(limiting_area, union_(area_to_be_bridge, near_expansion));
auto boundary_it = boundary_by_spacing.find(flow.scaled_spacing());
if (boundary_it == boundary_by_spacing.end())
boundary_it = boundary_by_spacing
.emplace(flow.scaled_spacing(), to_polylines(expand(total_fill_area, 1.3 * flow.scaled_spacing())))
.first;
Polylines boundary_plines = boundary_it->second;
{
Polylines limiting_plines = to_polylines(expand(limiting_area, 0.3*flow.spacing()));
// No offset here: flow.spacing() is in mm, so the expand(limiting_area, 0.3 * flow.spacing())
// this used to be moved the outline by 0.135 scaled units - nothing beyond rounding - while
// costing a whole-layer ClipperLib pass for every candidate. limiting_area is already a clean
// union, so its own outline is the same boundary.
Polylines limiting_plines = to_polylines(limiting_area);
boundary_plines.insert(boundary_plines.end(), limiting_plines.begin(), limiting_plines.end());
}
@@ -3499,8 +3657,11 @@ void PrintObject::bridge_over_infill()
{
bool reconstruct = false;
Polygons tmp_expanded_area = expand(bridging_area, 3.0 * flow.scaled_spacing());
const BoundingBox tmp_expanded_bbox = get_extents(tmp_expanded_area);
for (const CandidateSurface &s : expanded_surfaces) {
if (!intersection(s.new_polys, tmp_expanded_area).empty()) {
// Surfaces whose boxes miss each other cannot intersect, which is most pairs on a busy layer.
if (get_extents(s.new_polys).overlap(tmp_expanded_bbox) &&
!intersection(s.new_polys, tmp_expanded_area).empty()) {
bridging_angle = s.bridge_angle;
reconstruct = true;
break;
@@ -3524,10 +3685,20 @@ void PrintObject::bridge_over_infill()
bridging_area = union_(bridging_area, construct_anchored_polygon(bridging_area, to_lines(boundary_plines), flow,
bridging_angle, scan_spacing, true));
}
bridging_area = intersection(bridging_area, limiting_area);
bridging_area = intersection(bridging_area, total_fill_area);
bridging_area = diff(bridging_area, total_top_area);
expansion_area = diff(expansion_area, bridging_area);
// Each of these meets one bridge with the whole layer, so the layer side is first cut down to the
// bridge's box (and expansion_area split as above); the result is the same.
if (!bridging_area.empty()) {
const BoundingBox bridging_bbox = get_extents(bridging_area).inflated(SCALED_EPSILON);
bridging_area = intersection(bridging_area, ClipperUtils::clip_clipper_polygons_with_subject_bbox(limiting_area, bridging_bbox));
bridging_area = intersection(bridging_area, ClipperUtils::clip_clipper_polygons_with_subject_bbox(total_fill_area, bridging_bbox));
bridging_area = diff(bridging_area, ClipperUtils::clip_clipper_polygons_with_subject_bbox(total_top_area, bridging_bbox));
}
if (!bridging_area.empty()) {
Polygons kept;
const Polygons cut = split_near(expansion_area, get_extents(bridging_area).inflated(SCALED_EPSILON), kept);
append(kept, diff(cut, bridging_area));
expansion_area = std::move(kept);
}
#ifdef DEBUG_BRIDGE_OVER_INFILL
debug_draw(std::to_string(lidx) + "_" + std::to_string(cluster_idx) + "_" + std::to_string(job_idx) + "_" + "_expanded_bridging" + std::to_string(r),
+1 -1
View File
@@ -929,9 +929,9 @@ public:
::fread(&y, sizeof(coord_t), 1, file);
poly.points.emplace_back(Point(x * scale, y * scale));
}
printf("Polygon %d, area: %lf\n", i, area(poly.points));
if (which == -1 || which == i)
m_support_polygons_deserialized.emplace_back(std::move(poly));
printf("Polygon %d, area: %lf\n", i, area(poly.points));
}
::fread(&n_polygons, 4, 1, file);
m_trimming_polygons_deserialized.reserve(n_polygons);
+31 -1
View File
@@ -854,11 +854,41 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/)
if (is_auto(stype) && config_detect_sharp_tails)
{
// BBS detect sharp tail
// Each island is tested only against the lower islands whose box meets its own: overlaps() tries every
// pair, which on a layer cut through a fine relief (thousands of islands above thousands) never ends.
std::vector<BoundingBox> lower_bboxes;
lower_bboxes.reserve(lower_polys.size());
for (const ExPolygon &lower : lower_polys)
lower_bboxes.emplace_back(get_extents(lower));
for (const ExPolygon& expoly : curr_polys) {
bool is_sharp_tail = false;
// 1. nothing below
// this is a sharp tail region if it's floating and non-ignorable
if (!overlaps(offset_ex(expoly, 0.1 * extrusion_width_scaled), lower_polys)) {
const ExPolygons expanded = offset_ex(expoly, 0.1 * extrusion_width_scaled);
const BoundingBox bbox = get_extents(expanded);
ExPolygons lower_nearby;
for (size_t i = 0; i < lower_polys.size(); ++i)
if (lower_bboxes[i].overlap(bbox))
lower_nearby.emplace_back(lower_polys[i]);
// As overlaps(expanded, lower_nearby), with each lower island cut to the island's box first: below
// a fine relief the lower layer is a few islands with thousands of holes, whose whole boundary
// was otherwise intersected again for every island above.
const auto overlaps_nearby = [&]() {
for (const ExPolygon &a : expanded) {
if (a.empty())
continue;
const BoundingBox a_bbox = get_extents(a);
for (const ExPolygon &b : lower_nearby) {
if (b.empty() || !get_extents(b).overlap(a_bbox))
continue;
const Polygons b_near = ClipperUtils::clip_clipper_polygons_with_subject_bbox(b, a_bbox.inflated(SCALED_EPSILON));
if (!intersection_pl(to_polylines(b_near), a).empty() || b.contains(a.contour.points.front()))
return true;
}
}
return false;
};
if (!overlaps_nearby()) {
is_sharp_tail = !offset_ex(expoly, -0.1 * extrusion_width_scaled).empty();
}
+2 -2
View File
@@ -61,7 +61,7 @@ public:
thickness(other.thickness), thickness_layers(other.thickness_layers),
bridge_angle(other.bridge_angle), extra_perimeters(other.extra_perimeters)
{};
Surface(Surface &&rhs)
Surface(Surface &&rhs) noexcept
: surface_type(rhs.surface_type), expolygon(std::move(rhs.expolygon)),
thickness(rhs.thickness), thickness_layers(rhs.thickness_layers),
bridge_angle(rhs.bridge_angle), extra_perimeters(rhs.extra_perimeters)
@@ -87,7 +87,7 @@ public:
return *this;
}
Surface& operator=(Surface &&rhs)
Surface& operator=(Surface &&rhs) noexcept
{
surface_type = rhs.surface_type;
expolygon = std::move(rhs.expolygon);
+1 -10
View File
@@ -256,19 +256,10 @@ extern bool is_gallery_file(const std::string& path, char const* type);
extern bool is_shapes_dir(const std::string& dir);
//BBS: add json support
extern bool is_json_file(const std::string& path);
// True if rel_path is relative, has no ".." component or embedded NUL and, joined to root, still resolves inside it.
// True if rel_path is relative, has no ".." component and, joined to root, still resolves inside it.
// Both '/' and '\\' are treated as separators on every platform, so an archive rejected on one OS
// is rejected on all of them.
extern bool is_path_within_root(const std::string &rel_path, const boost::filesystem::path &root);
// True if a symlink stored at link_rel_path (relative to root) with this target stays inside root: the target
// must be relative, and joined to the link's directory it must pass is_path_within_root.
extern bool is_symlink_target_within_root(const std::string &link_rel_path, const std::string &target, const boost::filesystem::path &root);
// True if path names an entry strictly inside root: it must be spelled with root as its prefix,
// and must still resolve inside root once symlinks are followed.
extern bool is_absolute_path_within_root(const boost::filesystem::path &path, const boost::filesystem::path &root);
// True if a file with this name is of a type that the desktop opens as plain content, so it cannot run code.
// Anything unknown is not safe.
extern bool is_safe_to_open_file_name(const std::string &file_name);
// Orca: custom protocal support utils
inline bool is_orca_open(const std::string& url) { return boost::starts_with(url, "orcaslicer://open"); }
+4 -4
View File
@@ -162,10 +162,10 @@ inline void append(std::vector<T, Alloc> &dest, std::vector<T, Alloc> &&src)
{
if (dest.empty())
dest = std::move(src);
else {
dest.reserve(dest.size() + src.size());
std::move(std::begin(src), std::end(src), std::back_inserter(dest));
}
else
// insert() grows the capacity geometrically; reserving exactly the new size reallocated on every call, which
// made appending piece by piece quadratic.
dest.insert(dest.end(), std::make_move_iterator(src.begin()), std::make_move_iterator(src.end()));
src.clear();
src.shrink_to_fit();
}
-63
View File
@@ -4,9 +4,6 @@
#include "miniz_extension.hpp"
#include "Utils.hpp"
#include <boost/filesystem.hpp>
#include <boost/log/trivial.hpp>
#if defined(_MSC_VER) || defined(__MINGW64__)
#include "boost/nowide/cstdio.hpp"
#endif
@@ -118,66 +115,6 @@ std::string decode_archive_entry_path(mz_zip_archive *zip, const mz_zip_archive_
return decode_zip_unicode_path_extra_field(extra.substr(0, extra_size > 0 ? extra_size - 1 : 0), stat.m_filename);
}
bool extract_archive_confined(const std::string &zip_path_utf8, const std::string &dest_dir)
{
mz_zip_archive archive;
mz_zip_zero_struct(&archive);
if (!open_zip_reader(&archive, zip_path_utf8)) {
BOOST_LOG_TRIVIAL(error) << "Unable to open zip reader for " << zip_path_utf8;
return false;
}
const mz_uint num_entries = mz_zip_reader_get_num_files(&archive);
mz_zip_archive_file_stat stat;
// Validate every entry first so an archive with a single escaping entry leaves no partial output behind.
const boost::filesystem::path root(dest_dir);
for (mz_uint i = 0; i < num_entries; ++i) {
if (mz_zip_reader_file_stat(&archive, i, &stat) && !is_path_within_root(stat.m_filename, root)) {
BOOST_LOG_TRIVIAL(error) << "Unzip: rejecting " << zip_path_utf8 << ", entry " << stat.m_filename << " resolves outside " << dest_dir;
close_zip_reader(&archive);
return false;
}
}
for (mz_uint i = 0; i < num_entries; ++i) {
if (!mz_zip_reader_file_stat(&archive, i, &stat)) {
BOOST_LOG_TRIVIAL(warning) << "Unzip: read file stat failed";
continue;
}
const std::string dest_file = dest_dir + "/" + stat.m_filename;
try {
if (stat.m_is_directory) {
const boost::filesystem::path dest_path(dest_file);
if (!boost::filesystem::exists(dest_path))
boost::filesystem::create_directories(dest_path);
continue;
}
if (stat.m_uncomp_size == 0) {
BOOST_LOG_TRIVIAL(warning) << "Unzip: invalid size for file " << stat.m_filename;
continue;
}
// Replace a symlink at the destination rather than writing through it.
const boost::filesystem::path dest_path(dest_file);
if (boost::filesystem::is_symlink(boost::filesystem::symlink_status(dest_path)))
boost::filesystem::remove(dest_path);
if (!mz_zip_reader_extract_to_file(&archive, stat.m_file_index, dest_file.c_str(), 0)) {
BOOST_LOG_TRIVIAL(error) << "Unzip: extract file " << stat.m_filename << " to dest " << dest_file << " failed";
close_zip_reader(&archive);
return false;
}
BOOST_LOG_TRIVIAL(info) << "Unzip: successfully extract file " << stat.m_file_index << " to " << dest_file;
} catch (const std::exception &e) {
close_zip_reader(&archive);
BOOST_LOG_TRIVIAL(error) << "Unzip: archive read exception: " << e.what();
return false;
}
}
close_zip_reader(&archive);
return true;
}
MZ_Archive::MZ_Archive()
{
mz_zip_zero_struct(&arch);
-2
View File
@@ -11,8 +11,6 @@ bool open_zip_writer(mz_zip_archive *zip, const std::string &fname_utf8);
bool close_zip_reader(mz_zip_archive *zip);
bool close_zip_writer(mz_zip_archive *zip);
std::string decode_archive_entry_path(mz_zip_archive *zip, const mz_zip_archive_file_stat &stat);
// Extracts every entry of the archive under dest_dir. Nothing is written if any entry would resolve outside dest_dir.
bool extract_archive_confined(const std::string &zip_path_utf8, const std::string &dest_dir);
class MZ_Archive {
public:
+1 -39
View File
@@ -70,7 +70,6 @@
#include <boost/shared_ptr.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/nowide/fstream.hpp>
@@ -1094,9 +1093,6 @@ bool is_path_within_root(const std::string &rel_path, const boost::filesystem::p
auto is_separator = [](char c) { return c == '/' || c == '\\'; };
if (rel_path.empty() || is_separator(rel_path.front()) || (rel_path.size() > 1 && rel_path[1] == ':'))
return false;
// The filesystem calls stop at a NUL, so they would act on a shorter path than the one checked here.
if (rel_path.find('\0') != std::string::npos)
return false;
for (size_t start = 0; start <= rel_path.size();) {
size_t end = start;
while (end < rel_path.size() && !is_separator(rel_path[end]))
@@ -1107,10 +1103,7 @@ bool is_path_within_root(const std::string &rel_path, const boost::filesystem::p
}
// Resolve against the canonical root so a symlink inside it cannot lead back out.
try {
std::string root_str = boost::filesystem::weakly_canonical(root).string();
// A trailing separator on root would otherwise fail the prefix match below for every path.
while (!root_str.empty() && (root_str.back() == '/' || root_str.back() == boost::filesystem::path::preferred_separator))
root_str.pop_back();
const std::string root_str = boost::filesystem::weakly_canonical(root).string();
const std::string full_str = boost::filesystem::weakly_canonical(root / rel_path).string();
return full_str.compare(0, root_str.size(), root_str) == 0 &&
(full_str.size() == root_str.size() || full_str[root_str.size()] == boost::filesystem::path::preferred_separator);
@@ -1119,37 +1112,6 @@ bool is_path_within_root(const std::string &rel_path, const boost::filesystem::p
}
}
bool is_symlink_target_within_root(const std::string &link_rel_path, const std::string &target, const boost::filesystem::path &root)
{
if (target.empty() || target.front() == '/' || target.front() == '\\' || (target.size() > 1 && target[1] == ':'))
return false;
// A relative target without ".." only descends from the link's directory, so no chain of such links can leave root.
const size_t sep = link_rel_path.find_last_of("/\\");
return is_path_within_root((sep == std::string::npos ? std::string() : link_rel_path.substr(0, sep + 1)) + target, root);
}
bool is_absolute_path_within_root(const boost::filesystem::path &path, const boost::filesystem::path &root)
{
const boost::filesystem::path rel = path.lexically_relative(root);
return !rel.empty() && rel != "." && is_path_within_root(rel.string(), root);
}
bool is_safe_to_open_file_name(const std::string &file_name)
{
// Formats that cannot carry macros or scripts. Legacy and OpenDocument office files, HTML and SVG are left out on purpose.
static const std::vector<std::string> safe_extensions = {
"jpg", "jpeg", "jfif", "pjpeg", "pjp", "png", "gif", "bmp", "webp", "tif", "tiff",
"pdf", "txt", "md", "csv", "docx", "xlsx", "pptx",
"stl", "obj", "3mf", "amf", "ply", "step", "stp", "iges", "igs", "dxf",
"mp4", "mov", "webm"};
// The name must end in the extension itself: Windows drops trailing dots and spaces and reads ':' as a stream separator.
const size_t dot = file_name.find_last_of('.');
if (dot == std::string::npos || file_name.find_first_of("/\\:") != std::string::npos)
return false;
const std::string extension = boost::algorithm::to_lower_copy(file_name.substr(dot + 1));
return std::find(safe_extensions.begin(), safe_extensions.end(), extension) != safe_extensions.end();
}
bool is_img_file(const std::string &path)
{
return boost::iends_with(path, ".png") || boost::iends_with(path, ".svg");
+6 -1
View File
@@ -1019,9 +1019,14 @@ if (UNIX AND NOT APPLE)
find_package(GTK${SLIC3R_GTK} REQUIRED)
pkg_check_modules(LIBSECRET REQUIRED libsecret-1)
pkg_check_modules(webkit2gtk REQUIRED webkit2gtk-4.1)
if (FLATPAK)
# I don't know why this is needed, but for whatever reason slic3r isn't
# linking to X11 and webkit2gtk. force it.
find_package(X11 REQUIRED)
target_link_libraries(libslic3r_gui ${X11_LIBRARIES} ${webkit2gtk_LIBRARIES})
endif()
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${GTK${SLIC3R_GTK}_INCLUDE_DIRS} ${LIBSECRET_INCLUDE_DIRS} ${webkit2gtk_INCLUDE_DIRS})
target_link_libraries(libslic3r_gui ${GTK${SLIC3R_GTK}_LIBRARIES} fontconfig ${LIBSECRET_LIBRARIES} ${webkit2gtk_LIBRARIES} ${X11_LIBRARIES})
target_link_libraries(libslic3r_gui ${GTK${SLIC3R_GTK}_LIBRARIES} fontconfig ${LIBSECRET_LIBRARIES})
# Propagate GDK backend detection results as compile definitions so that
# LinuxDisplayBackend.cpp can include the right GDK headers.
+1 -1
View File
@@ -428,7 +428,7 @@ void AuFile::on_dclick(wxMouseEvent &evt)
if (m_type == AddFileButton)
return;
else
desktop_open_project_attachment(this, m_file_path);
wxLaunchDefaultApplication(m_file_path.wstring(), 0);
}
void AuFile::on_mouse_left_up(wxMouseEvent &evt)
+6 -8
View File
@@ -171,16 +171,14 @@ void ConfigManipulation::check_adaptive_pressure_advance_model(DynamicPrintConfi
return;
const auto* model = config->option<ConfigOptionStrings>("adaptive_pressure_advance_model");
if (model == nullptr)
if (model == nullptr || model->values.empty())
return;
// Each extruder variant holds its own model.
std::string error;
for (const std::string& variant_model : model->values) {
error = AdaptivePAProcessor::validate_adaptive_pa_model(variant_model);
if (!error.empty())
break;
}
std::string raw_model;
for (const std::string& chunk : model->values)
raw_model += chunk;
std::string error = AdaptivePAProcessor::validate_adaptive_pa_model(raw_model);
if (!error.empty()) {
wxString msg_text = _L("Adaptive Pressure Advance model validation failed:\n");
msg_text += from_u8(error);
-32
View File
@@ -14,9 +14,6 @@
#include <boost/algorithm/string/predicate.hpp>
#include <boost/any.hpp>
#include <wx/filename.h>
#include <wx/filesys.h>
#if __APPLE__
#import <IOKit/pwr_mgt/IOPMLib.h>
#elif _WIN32
@@ -30,13 +27,11 @@
#include "AboutDialog.hpp"
#include "MsgDialog.hpp"
#include "Plater.hpp"
#include "format.hpp"
#include "WebUserLoginDialog.hpp"
#include "libslic3r/Print.hpp"
#include "libslic3r/Utils.hpp"
namespace Slic3r {
@@ -536,11 +531,6 @@ boost::filesystem::path into_path(const wxString &str)
return boost::filesystem::path(str.wx_str());
}
wxString file_url_from_path(const boost::filesystem::path &path)
{
return wxFileSystem::FileNameToURL(wxFileName(from_path(path)));
}
void about()
{
AboutDialog dlg;
@@ -645,26 +635,4 @@ void desktop_open_any_folder( const std::string& path )
}
bool desktop_open_project_attachment(wxWindow *parent, const boost::filesystem::path &path)
{
// The auxiliary path is UTF-8, which is what boost::filesystem reads a narrow string as.
const boost::filesystem::path aux_root(wxGetApp().plater()->model().get_auxiliary_file_temp_path());
boost::system::error_code ec;
if (!is_absolute_path_within_root(path, aux_root) || !boost::filesystem::is_regular_file(path, ec))
return false;
// Attachments come with the project and carry no download mark, so the desktop would open them without a warning.
if (!is_safe_to_open_file_name(path.filename().string())) {
MessageDialog dlg(parent,
wxString::Format(_L("\"%s\" is not a plain document, image or model file. Opening it may run it as a "
"program or script on this computer.\n\n"
"Only open attachments from projects you trust. Open it anyway?"),
from_path(path.filename())),
_L("Open attachment"), wxICON_WARNING | wxYES_NO);
if (dlg.ShowModal() != wxID_YES)
return false;
}
return wxLaunchDefaultApplication(from_path(path), 0);
}
} }
-6
View File
@@ -76,9 +76,6 @@ std::string into_u8(const wxString &str);
wxString from_path(const boost::filesystem::path &path);
// boost path from wxString
boost::filesystem::path into_path(const wxString &str);
// file:// URL of a local path, percent-encoded so characters such as '#', '%' and '?' stay part of the path.
// Append any query or fragment to the result.
wxString file_url_from_path(const boost::filesystem::path &path);
// Display an About dialog
extern void about();
@@ -86,9 +83,6 @@ extern void about();
extern void desktop_open_datadir_folder();
// Ask the destop to open one folder
extern void desktop_open_any_folder(const std::string& path);
// Ask the desktop to open a file from the project's auxiliary directory, after a confirmation
// unless its type is known to be plain content. Returns false if the file was not opened.
extern bool desktop_open_project_attachment(wxWindow *parent, const boost::filesystem::path &path);
} // namespace GUI
} // namespace Slic3r
+6 -27
View File
@@ -1512,33 +1512,11 @@ int GUI_App::install_plugin(std::string name, std::string package_name, InstallP
size_t n = mz_zip_reader_get_extra(&archive, stat.m_file_index, extra.data(), extra.size());
dest_file = decode(extra.substr(0, n), stat.m_filename);
}
if (!is_path_within_root(dest_file, plugin_folder)) {
BOOST_LOG_TRIVIAL(error) << "[install_plugin] entry " << dest_file << " resolves outside " << plugin_folder.string();
close_zip_reader(&archive);
if (pro_fn) { pro_fn(InstallStatusUnzipFailed, 0, cancel); }
return InstallStatusUnzipFailed;
}
auto dest_path = plugin_folder / dest_file;
std::string dest_zip_file = encode_path(dest_path.string().c_str());
#ifndef WIN32
// Validate a symlink's target before anything at the destination is replaced.
const bool is_link = S_ISLNK(stat.m_external_attr >> 16);
std::string link;
if (is_link) {
link.assign(stat.m_uncomp_size, 0);
if (!mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, link.data(), stat.m_uncomp_size, 0) ||
!is_symlink_target_within_root(dest_file, link, plugin_folder)) {
BOOST_LOG_TRIVIAL(error) << "[install_plugin] link " << dest_file << " -> " << link << " is unreadable or resolves outside " << plugin_folder.string();
close_zip_reader(&archive);
if (pro_fn) { pro_fn(InstallStatusUnzipFailed, 0, cancel); }
return InstallStatusUnzipFailed;
}
}
#endif
try {
boost::filesystem::create_directories(dest_path.parent_path());
// symlink_status so that an existing symlink, dangling or not, is replaced rather than written through.
if (fs::exists(fs::symlink_status(dest_path))) {
std::string dest_zip_file = encode_path(dest_path.string().c_str());
try {
if (fs::exists(dest_path)) {
boost::system::error_code ec;
fs::remove(dest_path, ec);
if (ec) {
@@ -1566,8 +1544,9 @@ int GUI_App::install_plugin(std::string name, std::string package_name, InstallP
}
mz_bool res = 0;
#ifndef WIN32
if (is_link) {
res = 1;
if (S_ISLNK(stat.m_external_attr >> 16)) {
std::string link(stat.m_uncomp_size + 1, 0);
res = mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, link.data(), stat.m_uncomp_size, 0);
try {
boost::filesystem::create_symlink(link, dest_path);
} catch (const std::exception &e) {
+4 -1
View File
@@ -98,7 +98,10 @@ void MarkdownTip::LoadStyle()
ph /= "tooltip/styled.html";
_data_dir = false;
}
_tipView->LoadURL(file_url_from_path(ph));
auto url = ph.string();
std::replace(url.begin(), url.end(), '\\', '/');
url = "file:///" + url;
_tipView->LoadURL(from_u8(url));
_lastTip.clear();
}
+16 -28
View File
@@ -286,30 +286,6 @@ static void set_config_values(DynamicPrintConfig *config, const std::string &key
}
}
// Orca: a calibration print sets pressure advance explicitly to 0 on each extruder variant that
// has it disabled, so the value stored in the printer does not skew the result. Variants with
// pressure advance enabled keep their own value.
static void zero_pressure_advance_where_disabled(DynamicPrintConfig *filament_config)
{
auto enable_pa = filament_config->option<ConfigOptionBools>("enable_pressure_advance");
auto pa = filament_config->option<ConfigOptionFloats>("pressure_advance");
auto adaptive_pa = filament_config->option<ConfigOptionBools>("adaptive_pressure_advance");
if (!enable_pa || !pa || !adaptive_pa || pa->empty() || adaptive_pa->empty())
return;
// All variant keys share the variant count; widen a short one rather than index past it.
if (pa->size() < enable_pa->size())
pa->resize(enable_pa->size());
if (adaptive_pa->size() < enable_pa->size())
adaptive_pa->resize(enable_pa->size());
for (size_t variant = 0; variant < enable_pa->size(); ++variant) {
if (enable_pa->get_at(variant))
continue;
enable_pa->values[variant] = true;
pa->values[variant] = 0.0;
adaptive_pa->values[variant] = false;
}
}
bool Plater::has_illegal_filename_characters(const wxString& wxs_name)
{
std::string name = into_u8(wxs_name);
@@ -3696,7 +3672,7 @@ void Sidebar::update_all_preset_comboboxes()
wxString url = from_u8(PrintHost::get_print_host_webui(&cfg));
wxString apikey;
if(url.empty())
url = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/orca/missing_connection.html");
url = wxString::Format("file://%s/web/orca/missing_connection.html", from_u8(resources_dir()));
else {
const auto host_type = cfg.option<ConfigOptionEnum<PrintHostType>>("host_type")->value;
if (cfg.has("printhost_apikey") && (host_type != htSimplyPrint))
@@ -17088,7 +17064,11 @@ void Plater::calib_input_shaping_freq(const Calib_Params& params)
set_config_values<double, ConfigOptionFloatsNullable>(print_config, "default_jerk", 0);
}
zero_pressure_advance_where_disabled(filament_config);
if (!filament_config->option<ConfigOptionBools>("enable_pressure_advance")->get_at(0)) {
set_config_values<bool, ConfigOptionBools>(filament_config, "enable_pressure_advance", true);
set_config_values<double, ConfigOptionFloatsNullable>(filament_config, "pressure_advance", 0.0);
set_config_values<bool, ConfigOptionBools>(filament_config, "adaptive_pressure_advance", false);
}
printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false});
printer_config->set_key_value("input_shaping_emit", new ConfigOptionBool{false});
@@ -17150,7 +17130,11 @@ void Plater::calib_input_shaping_damp(const Calib_Params& params)
set_config_values<double, ConfigOptionFloatsNullable>(print_config, "default_jerk", 0);
}
zero_pressure_advance_where_disabled(filament_config);
if (!filament_config->option<ConfigOptionBools>("enable_pressure_advance")->get_at(0)) {
set_config_values<bool, ConfigOptionBools>(filament_config, "enable_pressure_advance", true);
set_config_values<double, ConfigOptionFloatsNullable>(filament_config, "pressure_advance", 0.0);
set_config_values<bool, ConfigOptionBools>(filament_config, "adaptive_pressure_advance", false);
}
printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false});
printer_config->set_key_value("input_shaping_emit", new ConfigOptionBool{false});
@@ -17212,7 +17196,11 @@ void Plater::Calib_Cornering(const Calib_Params& params)
set_config_values<double, ConfigOptionFloatsNullable>(print_config, "default_jerk", 0);
}
zero_pressure_advance_where_disabled(filament_config);
if (!filament_config->option<ConfigOptionBools>("enable_pressure_advance")->get_at(0)) {
set_config_values<bool, ConfigOptionBools>(filament_config, "enable_pressure_advance", true);
set_config_values<double, ConfigOptionFloatsNullable>(filament_config, "pressure_advance", 0.0);
set_config_values<bool, ConfigOptionBools>(filament_config, "adaptive_pressure_advance", false);
}
printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false});
printer_config->set_key_value("input_shaping_emit", new ConfigOptionBool{true});
+3 -1
View File
@@ -57,7 +57,9 @@ PrivacyUpdateDialog::PrivacyUpdateDialog(wxWindow* parent, wxWindowID id, const
fs::path ph(resources_dir());
ph /= "tooltip/privacyupdate.html";
m_host_url = into_u8(file_url_from_path(ph));
m_host_url = ph.string();
std::replace(m_host_url.begin(), m_host_url.end(), '\\', '/');
m_host_url = "file:///" + m_host_url;
m_vebview_release_note->LoadURL(from_u8(m_host_url));
m_sizer_right->Add(m_vebview_release_note, 0, wxEXPAND | wxRIGHT | wxLEFT, FromDIP(15));
+6 -4
View File
@@ -45,10 +45,10 @@ const std::vector<std::string> license_list = {
ProjectPanel::ProjectPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style) : wxPanel(parent, id, pos, size, style)
{
SetBackgroundColour(*wxWHITE);
m_project_home_url = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/model/index.html");
m_project_home_url = wxString::Format("file://%s/web/model/index.html", from_u8(resources_dir()));
wxString strlang = wxGetApp().current_language_code_safe();
if (strlang != "")
m_project_home_url += "?lang=" + strlang;
m_project_home_url = wxString::Format("file://%s/web/model/index.html?lang=%s", from_u8(resources_dir()), strlang);
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
@@ -293,8 +293,10 @@ void ProjectPanel::OnScriptMessage(wxWebViewEvent& evt)
if (!accessory_path.empty()) {
std::string decode_path = wxGetApp().url_decode(accessory_path.ToStdString());
fs::path path(decode_path);
if (!desktop_open_project_attachment(this, path))
BOOST_LOG_TRIVIAL(warning) << "open_3mf_accessory: not opening " << decode_path;
if (fs::exists(path)) {
wxLaunchDefaultApplication(path.wstring(), 0);
}
}
}
else if (strCmd == "request_3mf_info") {
+1 -1
View File
@@ -510,7 +510,7 @@ void UpdateVersionDialog::update_version_info(wxString release_note, wxString ve
out_buf->append(text, size);
}, (void*) &html_source, MD_DIALECT_GITHUB | MD_FLAG_STRIKETHROUGH | MD_FLAG_WIKILINKS, 0);
html_source.append("</body></html>");
m_vebview_release_note->LoadURL(file_url_from_path(boost::filesystem::path(resources_dir()) / "web/guide/0/index.html"));
m_vebview_release_note->LoadURL("file://" + (boost::filesystem::path (resources_dir()) / "web/guide/0/index.html").string());
SetMinSize(GetSize());
SetMaxSize(GetSize());
+19 -20
View File
@@ -4415,15 +4415,15 @@ void TabFilament::build()
optgroup->append_single_option_line("pellet_flow_coefficient", "printer_basic_information_advanced#pellet-modded-printer");
optgroup->append_single_option_line("filament_flow_ratio", "material_flow_ratio_and_pressure_advance#flow-ratio", 0);
optgroup->append_single_option_line("enable_pressure_advance", "material_flow_ratio_and_pressure_advance#pressure-advance", 0);
optgroup->append_single_option_line("pressure_advance", "material_flow_ratio_and_pressure_advance#pressure-advance", 0);
optgroup->append_single_option_line("enable_pressure_advance", "material_flow_ratio_and_pressure_advance#pressure-advance");
optgroup->append_single_option_line("pressure_advance", "material_flow_ratio_and_pressure_advance#pressure-advance");
// Orca: adaptive pressure advance and calibration model
optgroup->append_single_option_line("adaptive_pressure_advance", "material_flow_ratio_and_pressure_advance#enable-adaptive-pressure-advance-beta", 0);
optgroup->append_single_option_line("adaptive_pressure_advance_overhangs", "material_flow_ratio_and_pressure_advance#enable-adaptive-pressure-advance-for-overhangs-beta", 0);
optgroup->append_single_option_line("adaptive_pressure_advance_bridges", "material_flow_ratio_and_pressure_advance#pressure-advance-for-bridges", 0);
optgroup->append_single_option_line("adaptive_pressure_advance", "material_flow_ratio_and_pressure_advance#enable-adaptive-pressure-advance-beta");
optgroup->append_single_option_line("adaptive_pressure_advance_overhangs", "material_flow_ratio_and_pressure_advance#enable-adaptive-pressure-advance-for-overhangs-beta");
optgroup->append_single_option_line("adaptive_pressure_advance_bridges", "material_flow_ratio_and_pressure_advance#pressure-advance-for-bridges");
Option option = optgroup->get_option("adaptive_pressure_advance_model", 0);
Option option = optgroup->get_option("adaptive_pressure_advance_model");
option.opt.full_width = true;
option.opt.is_code = true;
option.opt.height = 15;
@@ -4432,7 +4432,7 @@ void TabFilament::build()
DynamicPrintConfig& filament_config = m_preset_bundle->filaments.get_edited_preset().config;
update_dirty();
if (opt_key.substr(0, opt_key.find('#')) == "adaptive_pressure_advance_model")
if (opt_key == "adaptive_pressure_advance_model")
m_config_manipulation.check_adaptive_pressure_advance_model(&filament_config);
on_value_change(opt_key, value);
@@ -4829,11 +4829,8 @@ void TabFilament::toggle_options()
}
if (m_active_page->title() == L("Filament"))
{
const int selection = m_variant_combo ? m_variant_combo->GetSelection() : 0;
const unsigned int variant_idx = (unsigned int) std::max(selection, 0);
bool pa = m_config->opt_bool("enable_pressure_advance", variant_idx);
toggle_option("pressure_advance", pa, 0);
bool pa = m_config->opt_bool("enable_pressure_advance", 0);
toggle_option("pressure_advance", pa);
//Orca: Enable the plates that should be visible when multi bed support is enabled or a BBL printer is selected; otherwise, enable only the plate visible for the selected bed type.
DynamicConfig& proj_cfg = m_preset_bundle->project_config;
@@ -4861,12 +4858,12 @@ void TabFilament::toggle_options()
// Orca: adaptive pressure advance and calibration model
// If PA is not enabled, disable adaptive pressure advance and hide the model section
// If adaptive PA is not enabled, hide the adaptive PA model section
toggle_option("adaptive_pressure_advance", pa, 0);
toggle_option("adaptive_pressure_advance_overhangs", pa, 0);
bool has_adaptive_pa = m_config->opt_bool("adaptive_pressure_advance", variant_idx);
toggle_line("adaptive_pressure_advance_overhangs", has_adaptive_pa && pa, 0);
toggle_line("adaptive_pressure_advance_model", has_adaptive_pa && pa, 0);
toggle_line("adaptive_pressure_advance_bridges", has_adaptive_pa && pa, 0);
toggle_option("adaptive_pressure_advance", pa);
toggle_option("adaptive_pressure_advance_overhangs", pa);
bool has_adaptive_pa = m_config->opt_bool("adaptive_pressure_advance", 0);
toggle_line("adaptive_pressure_advance_overhangs", has_adaptive_pa && pa);
toggle_line("adaptive_pressure_advance_model", has_adaptive_pa && pa);
toggle_line("adaptive_pressure_advance_bridges", has_adaptive_pa && pa);
bool is_pellet_printer = printer_cfg.opt_bool("pellet_modded_printer");
toggle_line("pellet_flow_coefficient", is_pellet_printer);
@@ -4874,6 +4871,8 @@ void TabFilament::toggle_options()
toggle_line("activate_chamber_temp_control", printer_cfg.opt_bool("support_chamber_temp_control"));
const int selection = m_variant_combo ? m_variant_combo->GetSelection() : 0;
const unsigned int variant_idx = (unsigned int) std::max(selection, 0);
std::string volumetric_speed_cos = m_config->opt_string("volumetric_speed_coefficients", variant_idx);
bool enable_fit = volumetric_speed_cos != "0 0 0 0 0 0";
toggle_option("filament_adaptive_volumetric_speed", enable_fit, 256 + variant_idx);
@@ -5206,6 +5205,7 @@ void TabPrinter::build_fff()
optgroup->append_single_option_line("adaptive_bed_mesh_margin", "printer_basic_information_adaptive_bed_mesh#mesh-margin");
optgroup = page->new_optgroup(L("Accessory"), "param_accessory");
optgroup->append_single_option_line("nozzle_type", "printer_basic_information_accessory#nozzle-type", 0);
optgroup->append_single_option_line("nozzle_hrc", "printer_basic_information_accessory#nozzle-hrc");
optgroup->append_single_option_line("auxiliary_fan", "printer_basic_information_accessory#auxiliary-part-cooling-fan");
optgroup->append_single_option_line("fan_direction");
@@ -5720,7 +5720,6 @@ if (is_marlin_flavor)
auto optgroup = page->new_optgroup(L("Basic information"), L"param_information", -1, true);
optgroup->append_single_option_line("nozzle_diameter", "printer_extruder_basic_information#nozzle-diameter", extruder_idx);
optgroup->append_single_option_line("nozzle_type", "printer_basic_information_accessory#nozzle-type", extruder_idx);
//optgroup->append_single_option_line("nozzle_volume_type", "", extruder_idx);
optgroup->append_single_option_line("nozzle_volume", "printer_extruder_basic_information#nozzle-volume", extruder_idx);
@@ -5732,7 +5731,7 @@ if (is_marlin_flavor)
optgroup->m_on_change = [this, extruder_idx](const t_config_option_key& opt_key, boost::any value)
{
bool is_SEMM = m_config->opt_bool("single_extruder_multi_material");
if (is_SEMM && m_extruders_count > 1 && boost::starts_with(opt_key, "nozzle_diameter"))
if (is_SEMM && m_extruders_count > 1 && opt_key.find_first_of("nozzle_diameter") != std::string::npos)
{
SuppressBackgroundProcessingUpdate sbpu;
const double new_nd = boost::any_cast<double>(value);
+3 -4
View File
@@ -31,15 +31,14 @@ int UserManager::parse_json(std::string payload)
{
bool restored_json = false;
json j;
//bind/unbind
try {
json j_pre = json::parse(payload);
if (j_pre.empty()) {
return -1;
}
//bind/unbind
try {
if (j_pre.contains("bind")) {
if (j_pre["bind"].contains("command")) {
+3 -1
View File
@@ -38,7 +38,9 @@ DownPluginFrame::DownPluginFrame(GUI_App *pGUI) : wxDialog((wxWindow *) (pGUI->m
// set the frame icon
wxBoxSizer *topsizer = new wxBoxSizer(wxVERTICAL);
wxString TargetUrl = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/guide/6/index.html");
wxString TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/6/index.html").make_preferred().string());
TargetUrl = "file://" + TargetUrl;
// Create the webview
m_browser = WebView::CreateWebView(this, TargetUrl);
+12 -12
View File
@@ -218,38 +218,37 @@ wxString GuideFrame::SetStartPage(GuidePage startpage, bool load)
m_page = startpage;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(" enter, load=%1%, start_page=%2%")%load%int(startpage);
//wxLogMessage("GUIDE: webpage_1 %s", (boost::filesystem::path(resources_dir()) / "web\\guide\\1\\index.html").make_preferred().string().c_str() );
const wxString guide_url = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/guide/0/index.html");
wxString TargetUrl = guide_url + "?target=1";
wxString TargetUrl = from_u8( (boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=1").make_preferred().string() );
//wxLogMessage("GUIDE: webpage_2 %s", TargetUrl.mb_str());
if (startpage == BBL_WELCOME){
SetTitle(_L("Setup Wizard"));
TargetUrl = guide_url + "?target=1";
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=1").make_preferred().string());
} else if (startpage == BBL_REGION) {
SetTitle(_L("Setup Wizard"));
TargetUrl = guide_url + "?target=11";
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=11").make_preferred().string());
} else if (startpage == BBL_MODELS) {
SetTitle(_L("Setup Wizard"));
TargetUrl = guide_url + "?target=21";
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=21").make_preferred().string());
} else if (startpage == BBL_FILAMENTS) {
SetTitle(_L("Setup Wizard"));
int nSize = m_ProfileJson["model"].size();
if (nSize>0)
TargetUrl = guide_url + "?target=22";
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=22").make_preferred().string());
else
TargetUrl = guide_url + "?target=21";
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=21").make_preferred().string());
} else if (startpage == BBL_FILAMENT_ONLY) {
SetTitle("");
TargetUrl = guide_url + "?target=23";
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=23").make_preferred().string());
} else if (startpage == BBL_MODELS_ONLY) {
SetTitle("");
TargetUrl = guide_url + "?target=24";
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=24").make_preferred().string());
}
else {
SetTitle(_L("Setup Wizard"));
TargetUrl = guide_url + "?target=21";
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=21").make_preferred().string());
}
wxString strlang = wxGetApp().current_language_code_safe();
@@ -257,6 +256,7 @@ wxString GuideFrame::SetStartPage(GuidePage startpage, bool load)
if (strlang != "")
TargetUrl = wxString::Format("%s&lang=%s", w2s(TargetUrl), strlang);
TargetUrl = "file://" + TargetUrl;
if (load)
load_url(TargetUrl);
@@ -1284,7 +1284,7 @@ bool GuideFrame::BuildProfileJson(const PresetBundle& bundle, bool require_all_r
entry["vendor"] = vp.id;
entry["nozzle_diameter"] = nozzle_str;
entry["materials"] = materials_str;
entry["cover"] = into_u8(file_url_from_path(cover_path));
entry["cover"] = cover_path.string();
entry["nozzle_selected"] = "";
entry["sub_path"] = "";
m_ProfileJson["model"].push_back(entry);
@@ -1732,7 +1732,7 @@ int GuideFrame::LoadProfileFamily(std::string strVendor, std::string strFilePath
cover_path = (boost::filesystem::absolute(boost::filesystem::path(resources_dir()) / "/web/image/printer/") / cover_file)
.make_preferred();
}
OneModel["cover"] = into_u8(file_url_from_path(cover_path));
OneModel["cover"] = cover_path.string();
OneModel["nozzle_selected"] = "";
+1 -1
View File
@@ -599,7 +599,7 @@ void ZUserLogin::OnScriptResponseMessage(wxCommandEvent &WXUNUSED(evt))
bool ZUserLogin::ShowErrorPage()
{
wxString ErrortUrl = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/login/error.html");
wxString ErrortUrl = from_u8((boost::filesystem::path(resources_dir()) / "web\\login\\error.html").make_preferred().string());
load_url(ErrortUrl);
return true;
+2 -2
View File
@@ -36,10 +36,10 @@ namespace GUI {
WebViewPanel::WebViewPanel(wxWindow *parent)
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
{
wxString url = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/homepage/index.html");
wxString url = wxString::Format("file://%s/web/homepage/index.html", from_u8(resources_dir()));
wxString strlang = wxGetApp().current_language_code_safe();
if (strlang != "")
url += "?lang=" + strlang;
url = wxString::Format("file://%s/web/homepage/index.html?lang=%s", from_u8(resources_dir()), strlang);
wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL);
+4 -2
View File
@@ -1,6 +1,5 @@
#include "MultiNozzleSync.hpp"
#include "../GUI.hpp"
#include "../GUI_App.hpp"
#include "../I18N.hpp"
#include "../Plater.hpp"
@@ -22,6 +21,8 @@
#include <set>
#include <wx/choice.h>
#include <wx/filename.h>
#include <wx/filesys.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
@@ -640,7 +641,8 @@ NozzleListTable::NozzleListTable(wxWindow* parent) : wxPanel(parent,wxID_ANY,wxD
m_web_view->AddScriptMessageHandler("nozzleListTable");
m_web_view->EnableContextMenu(false);
fs::path filepath = fs::path(resources_dir()) / "web/flush/NozzleListTable.html";
wxString url = file_url_from_path(filepath);
wxFileName fn(wxString::FromUTF8(filepath.string()));
wxString url = wxFileSystem::FileNameToURL(fn);
m_web_view->LoadURL(url);
auto sizer = new wxBoxSizer(wxVERTICAL);
+6 -5
View File
@@ -48,19 +48,20 @@ constexpr char ORCA_BRIDGE_JS[] = R"JS(
wxString bootstrap_url()
{
return file_url_from_path(boost::filesystem::path(resources_dir()) / BOOTSTRAP_PAGE);
return wxString("file://") + from_u8((boost::filesystem::path(resources_dir()) / BOOTSTRAP_PAGE).make_preferred().string());
}
wxString content_base_url()
{
return file_url_from_path(boost::filesystem::path(resources_dir()) / "web") + "/";
const std::string dir = (boost::filesystem::path(resources_dir()) / "web").make_preferred().string();
return wxString("file://") + from_u8(dir) + "/";
}
bool is_content_url(const wxString& url)
{
// The web view reports the URL it parsed, which may escape the resources path differently
// from content_base_url().
return wxURI::Unescape(url.BeforeFirst('#')) == wxURI::Unescape(content_base_url());
// The web view reports the URL it parsed, which escapes anything the resources path holds
// (a space, a non-ASCII character), while content_base_url() is the raw path.
return wxURI::Unescape(url.BeforeFirst('#')) == content_base_url();
}
const char* orca_bridge_script() { return ORCA_BRIDGE_JS; }
+2 -2
View File
@@ -192,7 +192,7 @@ bool WebViewHostDialog::create_webview(const std::string& resource_path,
wxString WebViewHostDialog::build_resource_url(const std::string& resource_path) const
{
wxString target_url = file_url_from_path(boost::filesystem::path(resources_dir()) / resource_path);
wxString target_url = from_u8((boost::filesystem::path(resources_dir()) / resource_path).make_preferred().string());
if (append_language_to_url()) {
const wxString lang = wxGetApp().current_language_code_safe();
@@ -202,7 +202,7 @@ wxString WebViewHostDialog::build_resource_url(const std::string& resource_path)
}
}
return target_url;
return wxString("file://") + target_url;
}
void WebViewHostDialog::load_url(const wxString& url)
+1 -1
View File
@@ -464,7 +464,7 @@ WipingDialog::WipingDialog(wxWindow* parent, const int max_flush_volume) :
wxString filepath_str = from_path(filepath);
wxFileName fn(filepath_str);
if(fn.FileExists()) {
wxString url = file_url_from_path(filepath);
wxString url = wxFileSystem::FileNameToURL(fn);
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__<< "File exists and load url " << url.ToStdString();
m_webview->LoadURL(url);
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__<< "Successfully loaded url: " << url.ToStdString();
+9
View File
@@ -274,4 +274,13 @@ bool Duet::start_print(wxString &msg, const std::string &filename, ConnectionTyp
return res;
}
int Duet::get_err_code_from_body(const std::string &body) const
{
pt::ptree root;
std::istringstream iss (body); // wrap returned json to istringstream
pt::read_json(iss, root);
return root.get<int>("err", 0);
}
}
+1
View File
@@ -40,6 +40,7 @@ private:
ConnectionType connect(wxString &msg) const;
void disconnect(ConnectionType connectionType) const;
bool start_print(wxString &msg, const std::string &filename, ConnectionType connectionType, bool simulationMode) const;
int get_err_code_from_body(const std::string &body) const;
};
}
+9
View File
@@ -146,6 +146,15 @@ bool ESP3D::start_print(wxString& msg, const std::string& filename) const
return ret;
}
int ESP3D::get_err_code_from_body(const std::string& body) const
{
pt::ptree root;
std::istringstream iss(body); // wrap returned json to istringstream
pt::read_json(iss, root);
return root.get<int>("err", 0);
}
// ESP3D only accepts 8.3 filenames else it crashes marlin and other undefined behaviour
std::string ESP3D::get_short_name(const std::string& filename) const
{
+1
View File
@@ -33,6 +33,7 @@ private:
std::string m_console_port;
bool start_print(wxString& msg, const std::string& filename) const;
int get_err_code_from_body(const std::string& body) const;
std::string get_short_name(const std::string& filename) const;
std::string format_command(const std::string& path, const std::string& arg, const std::string& val) const;
};
+3 -1
View File
@@ -340,7 +340,9 @@ namespace Slic3r {
if (classify_printer_model(config->opt_string("printer_model")) != ElegooPrinterType::CC2)
return fallback_webui;
std::string web_path = GUI::into_u8(GUI::file_url_from_path(boost::filesystem::path(resources_dir()) / "web/elegoolink/lan_service_web/index.html"));
std::string web_path = resources_dir() + "/web/elegoolink/lan_service_web/index.html";
std::replace(web_path.begin(), web_path.end(), '\\', '/');
web_path = "file://" + web_path;
const std::string token = get_cc2_token(config->opt_string("printhost_apikey"));
const std::string host_ip = Http::get_host_header_value(host);
+15 -25
View File
@@ -510,22 +510,12 @@ bool Flashforge::fetch_material_slots(std::vector<FlashforgeMaterialSlot>& slots
if (!request_local_api_json("detail", json{{"serialNumber", m_serial_number}, {"checkCode", m_check_code}}.dump(), body, msg))
return false;
if (!parse_material_slots(body, slots, supports_material_station)) {
const auto parsed = json::parse(body, nullptr, false, true);
if (parsed.is_discarded()) {
msg = _(L("Flashforge returned an invalid JSON response."));
return false;
}
return true;
}
bool Flashforge::parse_material_slots(const std::string& body, std::vector<FlashforgeMaterialSlot>& slots, bool* supports_material_station)
{
slots.clear();
const auto parsed = json::parse(body, nullptr, false, true);
if (parsed.is_discarded())
return false;
const auto& detail = parsed.contains("detail") ? parsed["detail"] : parsed;
const auto& station = detail.contains("matlStationInfo") ? detail["matlStationInfo"] :
detail.contains("MatlStationInfo") ? detail["MatlStationInfo"] : json();
@@ -552,21 +542,12 @@ bool Flashforge::parse_material_slots(const std::string& body, std::vector<Flash
if (supports_material_station != nullptr)
*supports_material_station = reports_material_station;
// Fields are read leniently: firmware may send numbers as strings or flags as numbers.
for (const auto& slot : slot_infos) {
if (!slot.is_object())
continue;
FlashforgeMaterialSlot info;
info.slot_id = static_cast<int>(slots.size()) + 1;
if (const auto it = slot.find("slotId"); it != slot.end())
try_parse_json_int(*it, info.slot_id);
int has_filament = 0;
if (const auto it = slot.find("hasFilament"); it != slot.end() && try_parse_json_int(*it, has_filament))
info.has_filament = has_filament != 0;
if (const auto it = slot.find("materialName"); it != slot.end() && it->is_string())
info.material_name = it->get<std::string>();
if (const auto it = slot.find("materialColor"); it != slot.end() && it->is_string())
info.material_color = it->get<std::string>();
info.slot_id = slot.value("slotId", static_cast<int>(slots.size()) + 1);
info.has_filament = slot.value("hasFilament", false);
info.material_name = slot.value("materialName", std::string());
info.material_color = slot.value("materialColor", std::string());
slots.emplace_back(std::move(info));
}
@@ -689,4 +670,13 @@ std::string Flashforge::extract_host_name() const
return out;
}
int Flashforge::get_err_code_from_body(const std::string& body) const
{
pt::ptree root;
std::istringstream iss(body); // wrap returned json to istringstream
pt::read_json(iss, root);
return root.get<int>("err", 0);
}
} // namespace Slic3r
+1 -2
View File
@@ -45,8 +45,6 @@ public:
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
std::string get_host() const override { return m_host; }
bool fetch_material_slots(std::vector<FlashforgeMaterialSlot>& slots, bool* supports_material_station, wxString& msg) const;
// Parses a local API "detail" reply. Returns false when the body is not valid JSON.
static bool parse_material_slots(const std::string& body, std::vector<FlashforgeMaterialSlot>& slots, bool* supports_material_station);
static bool discover_printers(std::vector<FlashforgeDiscoveredPrinter>& printers, wxString& msg, int timeout_ms = 10000, int idle_timeout_ms = 1500, int max_retries = 3);
private:
@@ -70,6 +68,7 @@ private:
bool request_local_api_json(const std::string& path, const std::string& body, std::string& response_body, wxString& error_msg) const;
std::string make_http_url(const std::string& path) const;
std::string extract_host_name() const;
int get_err_code_from_body(const std::string &body) const;
bool connect(wxString& msg) const;
bool start_print(wxString& msg, const std::string& filename) const;
};
+9
View File
@@ -141,4 +141,13 @@ bool MKS::start_print(wxString& msg, const std::string& filename) const
return ret;
}
int MKS::get_err_code_from_body(const std::string& body) const
{
pt::ptree root;
std::istringstream iss(body); // wrap returned json to istringstream
pt::read_json(iss, root);
return root.get<int>("err", 0);
}
} // Slic3r
+1
View File
@@ -34,6 +34,7 @@ private:
std::string get_upload_url(const std::string& filename) const;
bool start_print(wxString& msg, const std::string& filename) const;
int get_err_code_from_body(const std::string& body) const;
};
}
+56 -2
View File
@@ -339,8 +339,62 @@ bool PresetUpdater::priv::get_file(const std::string &url, const fs::path &targe
//BBS: refine preset update logic
bool PresetUpdater::priv::extract_file(const fs::path &source_path, const fs::path &dest_path)
{
const std::string parent_path = (!dest_path.empty() ? dest_path : source_path.parent_path()).string();
return extract_archive_confined(source_path.string(), parent_path);
bool res = true;
std::string file_path = source_path.string();
std::string parent_path = (!dest_path.empty() ? dest_path : source_path.parent_path()).string();
mz_zip_archive archive;
mz_zip_zero_struct(&archive);
if (!open_zip_reader(&archive, file_path))
{
BOOST_LOG_TRIVIAL(error) << "Unable to open zip reader for "<<file_path;
return false;
}
mz_uint num_entries = mz_zip_reader_get_num_files(&archive);
mz_zip_archive_file_stat stat;
// we first loop the entries to read from the archive the .amf file only, in order to extract the version from it
for (mz_uint i = 0; i < num_entries; ++i)
{
if (mz_zip_reader_file_stat(&archive, i, &stat))
{
std::string dest_file = parent_path+"/"+stat.m_filename;
if (stat.m_is_directory) {
fs::path dest_path(dest_file);
if (!fs::exists(dest_path))
fs::create_directories(dest_path);
continue;
}
else if (stat.m_uncomp_size == 0) {
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater]Unzip: invalid size for file "<<stat.m_filename;
continue;
}
try
{
res = mz_zip_reader_extract_to_file(&archive, stat.m_file_index, dest_file.c_str(), 0);
if (!res) {
BOOST_LOG_TRIVIAL(error) << "[Orca Updater]extract file "<<stat.m_filename<<" to dest "<<dest_file<<" failed";
close_zip_reader(&archive);
return res;
}
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]successfully extract file " << stat.m_file_index << " to "<<dest_file;
}
catch (const std::exception& e)
{
// ensure the zip archive is closed and rethrow the exception
close_zip_reader(&archive);
BOOST_LOG_TRIVIAL(error) << "[Orca Updater]Archive read exception:"<<e.what();
return false;
}
}
else {
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater]Unzip: read file stat failed";
}
}
close_zip_reader(&archive);
return true;
}
// Remove a leftover partial archive for the vendor about to be synchronized.
+9 -42
View File
@@ -3,13 +3,10 @@
#include <vector>
#include <thread>
#include <exception>
#include <sstream>
#include <boost/optional.hpp>
#include <boost/log/trivial.hpp>
#include <boost/filesystem.hpp>
#include <nlohmann/json.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <wx/string.h>
#include <wx/app.h>
@@ -175,20 +172,6 @@ std::string moonraker_error_reason(const std::string &body)
} // namespace
int PrintHost::get_err_code_from_body(const std::string &body)
{
boost::property_tree::ptree root;
std::istringstream iss(body);
try {
boost::property_tree::read_json(iss, root);
} catch (const std::exception &ex) {
BOOST_LOG_TRIVIAL(error) << "PrintHost: response is not valid JSON: " << ex.what();
return -1;
}
return root.get<int>("err", 0);
}
wxString PrintHost::format_error(const std::string &body, const std::string &error, unsigned status) const
{
if (status != 0) {
@@ -432,10 +415,12 @@ void PrintHostJobQueue::priv::remove_source()
source_to_remove.clear();
}
bool PrintHostJobQueue::upload_job(PrintHostJob &job, PrintHost::ProgressFn progress_fn, PrintHost::ErrorFn error_fn, PrintHost::InfoFn info_fn)
void PrintHostJobQueue::priv::perform_job(PrintHostJob the_job)
{
emit_progress(0); // Indicate the upload is starting
// Captured before upload_data is moved into upload() below.
const std::string upload_filename = job.upload_data.source_path.filename().string();
const std::string upload_filename = the_job.upload_data.source_path.filename().string();
{
LifecycleEventContext ctx;
@@ -444,37 +429,19 @@ bool PrintHostJobQueue::upload_job(PrintHostJob &job, PrintHost::ProgressFn prog
fire_lifecycle_event(LifecycleEvent::UploadStarted, ctx);
}
bool success = false;
std::string error;
// A throwing upload must not stop the worker, or later jobs would stay queued forever.
try {
success = job.printhost->upload(std::move(job.upload_data), std::move(progress_fn), error_fn, std::move(info_fn));
} catch (const std::exception &e) {
error = e.what();
error_fn(error);
}
bool success = the_job.printhost->upload(std::move(the_job.upload_data),
[this](Http::Progress progress, bool &cancel) { this->progress_fn(std::move(progress), cancel); },
[this](wxString error) { this->error_fn(std::move(error)); },
[this](wxString tag, wxString host) { this->info_fn(std::move(tag), std::move(host)); }
);
{
LifecycleEventContext ctx;
ctx.name = upload_filename;
ctx.code = success ? LifecycleEvtCode::Ok : LifecycleEvtCode::Error;
ctx.msg = error;
fire_lifecycle_event(LifecycleEvent::UploadFinished, ctx);
}
return success;
}
void PrintHostJobQueue::priv::perform_job(PrintHostJob the_job)
{
emit_progress(0); // Indicate the upload is starting
bool success = PrintHostJobQueue::upload_job(the_job,
[this](Http::Progress progress, bool &cancel) { this->progress_fn(std::move(progress), cancel); },
[this](wxString error) { this->error_fn(std::move(error)); },
[this](wxString tag, wxString host) { this->info_fn(std::move(tag), std::move(host)); }
);
if (success) {
emit_progress(100);
if (the_job.switch_to_device_tab) {
-6
View File
@@ -87,8 +87,6 @@ public:
static PrintHost* get_print_host(DynamicPrintConfig *config);
static std::string get_print_host_webui(DynamicPrintConfig *config);
// Reads the "err" field of a JSON reply, 0 when absent. Returns -1 when the body is not valid JSON.
static int get_err_code_from_body(const std::string &body);
//Support for cloud webui login
virtual bool is_cloud() const { return false; }
@@ -152,10 +150,6 @@ public:
void enqueue(PrintHostJob job);
void cancel(size_t id);
// Uploads the job, firing UploadStarted and a matching UploadFinished. An exception thrown by
// the upload is reported through error_fn and makes the upload fail.
static bool upload_job(PrintHostJob &job, PrintHost::ProgressFn progress_fn, PrintHost::ErrorFn error_fn, PrintHost::InfoFn info_fn);
private:
struct priv;
std::shared_ptr<priv> p;
+9
View File
@@ -654,4 +654,13 @@ bool UltiMaker::start_print(wxString &msg, const std::string &filename, Connecti
return res;
}
int UltiMaker::get_err_code_from_body(const std::string &body) const
{
pt::ptree root;
std::istringstream iss (body); // wrap returned json to istringstream
pt::read_json(iss, root);
return root.get<int>("err", 0);
}
}
+1
View File
@@ -64,6 +64,7 @@ private:
void set_auth(Http& http) const;
void disconnect(ConnectionType connectionType) const;
bool start_print(wxString &msg, const std::string &filename, ConnectionType connectionType) const;
int get_err_code_from_body(const std::string &body) const;
};
}
-125
View File
@@ -768,128 +768,3 @@ TEST_CASE("Each filament prints with its variant of the extruder's variant strin
CHECK(gcode.find("; nozzle_temperature = " + resolved + "\n") != std::string::npos);
}
}
// 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);
}
}
+1 -1
View File
@@ -28,6 +28,7 @@ add_executable(${_TEST_NAME}_tests
test_filament_mixer.cpp
test_fill_plane_path.cpp
test_geometry.cpp
test_kdtree.cpp
test_multimaterial_segmentation.cpp
test_placeholder_parser.cpp
test_polygon.cpp
@@ -44,7 +45,6 @@ add_executable(${_TEST_NAME}_tests
test_lay_on_face.cpp
test_model.cpp
test_utils.cpp
test_miniz_extension.cpp
test_timeutils.cpp
test_voronoi.cpp
test_wipe_tower_estimate.cpp
+44
View File
@@ -299,3 +299,47 @@ TEST_CASE("Traversing Clipper PolyTree", "[ClipperUtils]") {
REQUIRE(count_polys(output) == reference.size());
}
}
TEST_CASE("Tiled diff and intersection cover the same area as the plain calls", "[ClipperUtils]") {
// A grid of disjoint framed squares, enough of them to be split into several tiles.
const int n = 40;
const coord_t cell = scaled<coord_t>(2.), side = scaled<coord_t>(1.5), frame = scaled<coord_t>(0.3);
ExPolygons subject;
for (int y = 0; y < n; ++ y)
for (int x = 0; x < n; ++ x) {
const Point o(x * cell, y * cell);
ExPolygon square(Polygon({ o, o + Point(side, 0), o + Point(side, side), o + Point(0, side) }));
Polygon hole({ o + Point(frame, frame), o + Point(frame, side - frame), o + Point(side - frame, side - frame), o + Point(side - frame, frame) });
square.holes.emplace_back(std::move(hole));
subject.emplace_back(std::move(square));
}
// Clip polygons crossing many squares, one of them large with holes of its own.
Polygons clip;
const coord_t span = n * cell;
for (int i = 0; i < 8; ++ i) {
const coord_t y0 = coord_t(i) * span / 8, y1 = y0 + scaled<coord_t>(0.9);
clip.emplace_back(Polygon({ Point(- cell, y0), Point(span, y0 + cell * 3), Point(span, y1 + cell * 3), Point(- cell, y1) }));
}
ExPolygon big(Polygon({ Point(span / 4, span / 4), Point(3 * span / 4, span / 4), Point(3 * span / 4, 3 * span / 4), Point(span / 4, 3 * span / 4) }));
for (int i = 0; i < 4; ++ i) {
const Point o(span / 4 + scaled<coord_t>(3.1) + i * scaled<coord_t>(9.7), span / 4 + scaled<coord_t>(5.3));
big.holes.emplace_back(Polygon({ o, o + Point(0, scaled<coord_t>(20.)), o + Point(scaled<coord_t>(5.), scaled<coord_t>(20.)), o + Point(scaled<coord_t>(5.), 0) }));
}
polygons_append(clip, to_polygons(big));
const auto xor_area = [](const ExPolygons &a, const ExPolygons &b) { return area(diff_ex(a, b)) + area(diff_ex(b, a)); };
const ApplySafetyOffset safety = GENERATE(ApplySafetyOffset::No, ApplySafetyOffset::Yes);
const double tolerance = double(scaled<coord_t>(0.001)) * double(span);
const ExPolygons diff_plain = diff_ex(subject, clip, safety);
const ExPolygons diff_tiled = diff_ex_by_piece(subject, clip, safety);
REQUIRE(area(diff_plain) > 0.);
CHECK_THAT(area(diff_tiled), Catch::Matchers::WithinRel(area(diff_plain), 1e-9));
CHECK(xor_area(diff_tiled, diff_plain) < tolerance);
const ExPolygons intersection_plain = intersection_ex(subject, clip, safety);
const ExPolygons intersection_tiled = intersection_ex_by_piece(subject, clip, safety);
REQUIRE(area(intersection_plain) > 0.);
CHECK_THAT(area(intersection_tiled), Catch::Matchers::WithinRel(area(intersection_plain), 1e-9));
CHECK(xor_area(intersection_tiled, intersection_plain) < tolerance);
}
@@ -717,15 +717,3 @@ TEST_CASE("get_index_for_extruder scales the variant column by the requested str
REQUIRE(col0_stride2 == 0);
REQUIRE(col1_stride2 == 2);
}
// A per-variant filament option read with a single value gives it to every filament variant. A project
// exported by an older CLI holds a single value for an option no loaded preset defined, such as
// filament_ironing_flow.
TEST_CASE("A per-variant filament option read with a single value gives it to every filament variant", "[Config]")
{
// filament 1 defines Standard and High Flow, filament 2 Standard
DynamicPrintConfig config;
config.option<ConfigOptionInts>("filament_self_index", true)->values = {1, 1, 2};
config.load_from_ini_string("pressure_advance = 0.021", ForwardCompatibilitySubstitutionRule::Disable);
REQUIRE(config.option<ConfigOptionFloats>("pressure_advance")->values == std::vector<double>({0.021, 0.021, 0.021}));
}
+67
View File
@@ -0,0 +1,67 @@
#include <catch2/catch_all.hpp>
#include <numeric>
#include <random>
#include <vector>
#include "libslic3r/KDTreeIndirect.hpp"
#include "libslic3r/Point.hpp"
using namespace Slic3r;
TEST_CASE("Visiting the nearby points gives what collecting them gives", "[KDTree]") {
std::mt19937 rng(19937);
std::uniform_real_distribution<float> coord(-50.f, 50.f);
// Points in a box, so that a radius search returns anything from none of them to all of them.
std::vector<Vec3f> points(2000);
for (Vec3f &p : points)
p = Vec3f(coord(rng), coord(rng), coord(rng));
auto coordinate = [&points](size_t idx, size_t dimension) { return points[idx](int(dimension)); };
KDTreeIndirect<3, float, decltype(coordinate)> tree(coordinate);
std::vector<size_t> indices(points.size());
std::iota(indices.begin(), indices.end(), 0);
tree.build(indices);
const float radius = GENERATE(0.5f, 5.f, 25.f, 200.f);
for (int i = 0; i < 20; ++ i) {
const Vec3f center(coord(rng), coord(rng), coord(rng));
const std::vector<size_t> collected = find_nearby_points(tree, center, radius);
std::vector<size_t> visited;
visit_nearby_points(tree, center, radius, [&visited](size_t idx) { visited.emplace_back(idx); });
// Same points, and in the same order: a caller that keeps the first of several equally good ones
// must get the same answer either way.
REQUIRE(visited == collected);
}
}
TEST_CASE("A radius search returns every point within the radius and no other", "[KDTree]") {
std::mt19937 rng(2024);
std::uniform_real_distribution<float> coord(-20.f, 20.f);
std::vector<Vec3f> points(500);
for (Vec3f &p : points)
p = Vec3f(coord(rng), coord(rng), coord(rng));
auto coordinate = [&points](size_t idx, size_t dimension) { return points[idx](int(dimension)); };
KDTreeIndirect<3, float, decltype(coordinate)> tree(coordinate);
std::vector<size_t> indices(points.size());
std::iota(indices.begin(), indices.end(), 0);
tree.build(indices);
const Vec3f center(1.f, -2.f, 3.f);
const float radius = 7.f;
std::vector<size_t> expected;
for (size_t i = 0; i < points.size(); ++ i)
if ((points[i] - center).squaredNorm() < radius * radius)
expected.emplace_back(i);
std::vector<size_t> visited;
visit_nearby_points(tree, center, radius, [&visited](size_t idx) { visited.emplace_back(idx); });
std::sort(visited.begin(), visited.end());
REQUIRE(! expected.empty());
REQUIRE(visited == expected);
}

Some files were not shown because too many files have changed in this diff Show More