From 7dc269f99473421162ad89c555bfac7ace0e9a6b Mon Sep 17 00:00:00 2001 From: "xun.zhang" Date: Tue, 25 Mar 2025 14:51:45 +0800 Subject: [PATCH] ENH: add timelapse pos picker 1. refine code structure 2. prevent moving tool head between camera and object 3. consider raft layer jira: NONE Signed-off-by: xun.zhang Change-Id: Ic0004791bfd4036d4323045a041709d861e5c8d0 --- .../BBL/machine/Bambu Lab H2D 0.4 nozzle.json | 2 +- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/GCode.cpp | 29 +- src/libslic3r/GCode.hpp | 4 + src/libslic3r/GCode/TimelapsePosPicker.cpp | 396 ++++++++++++++++++ src/libslic3r/GCode/TimelapsePosPicker.hpp | 71 ++++ src/libslic3r/Print.cpp | 2 +- src/libslic3r/Print.hpp | 2 +- 8 files changed, 501 insertions(+), 7 deletions(-) create mode 100644 src/libslic3r/GCode/TimelapsePosPicker.cpp create mode 100644 src/libslic3r/GCode/TimelapsePosPicker.hpp diff --git a/resources/profiles/BBL/machine/Bambu Lab H2D 0.4 nozzle.json b/resources/profiles/BBL/machine/Bambu Lab H2D 0.4 nozzle.json index 0f113df2a..2213bcc68 100644 --- a/resources/profiles/BBL/machine/Bambu Lab H2D 0.4 nozzle.json +++ b/resources/profiles/BBL/machine/Bambu Lab H2D 0.4 nozzle.json @@ -123,6 +123,6 @@ "machine_start_gcode": ";===== machine: H2D =========================\n;===== date: 20250418 =====================\n\n;M1002 set_flag extrude_cali_flag=1\n;M1002 set_flag g29_before_print_flag=1\n;M1002 set_flag auto_cali_toolhead_offset_flag=1\n;M1002 set_flag build_plate_detect_flag=1\n\nM993 A0 B0 C0 ; nozzle cam detection not allowed.\n\nM400\n;M73 P99\n\n;=====printer start sound ===================\nM17\nM400 S1\nM1006 S1\nM1006 A53 B9 L99 C53 D9 M99 E53 F9 N99 \nM1006 A56 B9 L99 C56 D9 M99 E56 F9 N99 \nM1006 A61 B9 L99 C61 D9 M99 E61 F9 N99 \nM1006 A53 B9 L99 C53 D9 M99 E53 F9 N99 \nM1006 A56 B9 L99 C56 D9 M99 E56 F9 N99 \nM1006 A61 B18 L99 C61 D18 M99 E61 F18 N99 \nM1006 W\n;=====printer start sound ===================\n\n;===== reset machine status =================\nM204 S10000\nM630 S0 P0\n\nG90\nM17 D ; reset motor current to default\nM960 S5 P1 ; turn on logo lamp\nG90\nM220 S100 ;Reset Feedrate\nM221 S100 ;Reset Flowrate\nM73.2 R1.0 ;Reset left time magnitude\nG29.1 Z{+0.0} ; clear z-trim value first\nM983.1 M1 \nM901 D4\nM481 S0 ; turn off cutter pos comp\n;===== reset machine status =================\n\nM620 M ;enable remap\n\n;===== avoid end stop =================\nG91\nG380 S2 Z22 F1200\nG380 S2 Z-12 F1200\nG90\n;===== avoid end stop =================\n\n;==== set airduct mode ==== \n\n{if (overall_chamber_temperature >= 40)}\n\n M145 P1 ; set airduct mode to heating mode for heating\n M106 P2 S0 ; turn off auxiliary fan\n M106 P3 S0 ; turn off chamber fan\n\n{else}\n M145 P0 ; set airduct mode to cooling mode for cooling\n M106 P2 S178 ; turn on auxiliary fan for cooling\n M106 P3 S127 ; turn on chamber fan for cooling\n M140 S0 ; stop heatbed from heating\n\n M1002 gcode_claim_action : 29\n M191 S0 ; wait for chamber temp\n M142 P1 R35 S40 U0.3 V0.5 ; set chamber autocooling\n M106 P2 S0 ; turn off auxiliary fan\n\n{endif}\n;==== set airduct mode ==== \n\n;===== start to heat heatbed & hotend==========\n\n M1002 set_filament_type:{filament_type[initial_no_support_extruder]}\n\n M104 S140 A\n M140 S[bed_temperature_initial_layer_single]\n\n ;===== set chamber temperature ==========\n {if (overall_chamber_temperature >= 40)}\n M145 P1 ; set airduct mode to heating mode\n M141 S[overall_chamber_temperature] ; Let Chamber begin to heat\n {endif}\n ;===== set chamber temperature ==========\n\n;===== start to heat heatbead & hotend==========\n\n;====== cog noise reduction=================\nM982.2 S1 ; turn on cog noise reduction\n\n;===== first homing start =====\nM1002 gcode_claim_action : 13\n\nG28 X T300\n\nG150.1 F18000 ; wipe mouth to avoid filament stick to heatbed\nG150.3 F18000\nM400 P200\nM972 S24 P0 T2000\n{if curr_bed_type==\"Textured PEI Plate\"}\nM972 S26 P0 C0\n{elsif curr_bed_type==\"High Temp Plate\"}\nM972 S36 P0 C0 X1\n{endif}\nM972 S35 P0 C0\n\nG90\nG1 X175 Y160 F30000\n\nG28 Z P0 T250\n\n;===== first homign end =====\n\nM400\n;M73 P99\n\n;===== detection start =====\n\n T1001\n G383.4 ; left-extruder load status detection\n \n M104 S{nozzle_temperature_initial_layer[initial_no_support_extruder]-80} A ; rise temp in advance\n\nM1002 judge_flag build_plate_detect_flag\nM622 S1\n M972 S19 P0 C0 ; heatbed presence detection\nM623\n\n M972 S14 P0 ; nozzle type detection\n\n;===== detection end =====\n\nM400\n;M73 P99\n\n;===== prepare print temperature and material ==========\nM400\nM211 X0 Y0 Z0 ;turn off soft endstop\nM975 S1 ; turn on input shaping\n\nG29.2 S0 ; avoid invalid abl data\n\nM620.10 A0 F{filament_max_volumetric_speed[initial_no_support_extruder]/2.4053*60} H{nozzle_diameter[initial_no_support_extruder]} T{nozzle_temperature_range_high[initial_no_support_extruder]} P{nozzle_temperature_initial_layer[initial_no_support_extruder]} S1\nM620.10 A1 F{filament_max_volumetric_speed[initial_no_support_extruder]/2.4053*60} H{nozzle_diameter[initial_no_support_extruder]} T{nozzle_temperature_range_high[initial_no_support_extruder]} P{nozzle_temperature_initial_layer[initial_no_support_extruder]} S1\n\n{if filament_type[initial_no_support_extruder] == \"TPU\"}\n M620.11 S0 L0 I[initial_no_support_extruder] E-{retraction_distances_when_cut[initial_no_support_extruder]} F{filament_max_volumetric_speed[initial_no_support_extruder]/2.4053*60}\n{else}\n{if (filament_type[initial_no_support_extruder] == \"PA\") || (filament_type[initial_no_support_extruder] == \"PA-GF\")}\n M620.11 S1 L0 I[initial_no_support_extruder] R4 D2 E-{retraction_distances_when_cut[initial_no_support_extruder]} F{filament_max_volumetric_speed[initial_no_support_extruder]/2.4053*60}\n{else}\n M620.11 S1 L0 I[initial_no_support_extruder] R10 D8 E-{retraction_distances_when_cut[initial_no_support_extruder]} F{filament_max_volumetric_speed[initial_no_support_extruder]/2.4053*60}\n{endif}\n{endif}\n\nM620 S[initial_no_support_extruder]A ; switch material if AMS exist\nM1002 gcode_claim_action : 4\nM1002 set_filament_type:UNKNOWN\nM400\nT[initial_no_support_extruder]\nM400\nM628 S0\nM629\nM400\nM1002 set_filament_type:{filament_type[initial_no_support_extruder]}\nM621 S[initial_no_support_extruder]A\n\nM104 S{nozzle_temperature_initial_layer[initial_no_support_extruder]}\nM400\nM106 P1 S0\n\nG29.2 S1\n;===== prepare print temperature and material ==========\n\nM400\n;M73 P99\n\n;===== auto extrude cali start =========================\nM975 S1\nM1002 judge_flag extrude_cali_flag\n\nM622 J0\n M983.3 F{filament_max_volumetric_speed[initial_no_support_extruder]/2.4} A0.4 ; cali dynamic extrusion compensation\nM623\n\nM622 J1\n M1002 set_filament_type:{filament_type[initial_no_support_extruder]}\n M1002 gcode_claim_action : 8\n\n M109 S{nozzle_temperature[initial_no_support_extruder]}\n\n G90\n M83\n M983.3 F{filament_max_volumetric_speed[initial_no_support_extruder]/2.4} A0.4 ; cali dynamic extrusion compensation\n\n M400\n M106 P1 S255\n M400 S5\n M106 P1 S0\n G150.3\nM623\n\nM622 J2\n M1002 set_filament_type:{filament_type[initial_no_support_extruder]}\n M1002 gcode_claim_action : 8\n\n M109 S{nozzle_temperature[initial_no_support_extruder]}\n\n G90\n M83\n M983.3 F{filament_max_volumetric_speed[initial_no_support_extruder]/2.4} A0.4 ; cali dynamic extrusion compensation\n\n M400\n M106 P1 S255\n M400 S5\n M106 P1 S0\n G150.3\nM623\n\n;===== auto extrude cali end =========================\n\n{if filament_type[initial_no_support_extruder] == \"TPU\"}\n G150.2\n G150.1\n G150.2\n G150.1\n G150.2\n G150.1\n{else}\n M106 P1 S0\n M400 S2\n M83\n G1 E45 F{filament_max_volumetric_speed[initial_no_support_extruder]/2.4053*60}\n G1 E-3 F1800\n M400 P500\n G150.2\n G150.1\n{endif}\n\nG91\nG1 Y-16 F12000 ; move away from the trash bin\nG90\n\nM400\n;M73 P99\n\n;===== wipe right nozzle start =====\n\nM1002 gcode_claim_action : 14\n G150 T{nozzle_temperature_initial_layer[initial_no_support_extruder]}\n {if (overall_chamber_temperature >= 40)}\n G150 T{nozzle_temperature_initial_layer[initial_no_support_extruder] - 80}\n {endif}\nM106 S255 ; turn on fan to cool the nozzle\n\n;===== wipe left nozzle end =====\n\nM400\n;M73 P99\n\n{if (overall_chamber_temperature >= 40)}\n M1002 gcode_claim_action : 49\n M191 S[overall_chamber_temperature] ; wait for chamber temp\n{endif}\n\nM400\n;M73 P99\n\n;===== bed leveling ==================================\n\nM1002 judge_flag g29_before_print_flag\n\nM190 S[bed_temperature_initial_layer_single]; ensure bed temp\nM109 S140 A\nM106 S0 ; turn off fan , too noisy\n\nG91\nG1 Z5 F1200\nG90\nG1 X275 Y300 F30000\n\nM622 J1\n M1002 gcode_claim_action : 1\n G29.20 A3\n G29 A1 O X{first_layer_print_min[0]} Y{first_layer_print_min[1]} I{first_layer_print_size[0]} J{first_layer_print_size[1]}\n M400\n M500 ; save cali data\nM623\n \nM622 J2\n M1002 gcode_claim_action : 1\n {if has_tpu_in_first_layer}\n G29.20 A3\n G29 A1 O X{first_layer_print_min[0]} Y{first_layer_print_min[1]} I{first_layer_print_size[0]} J{first_layer_print_size[1]}\n {else}\n G29.20 A4\n G29 A2 O X{first_layer_print_min[0]} Y{first_layer_print_min[1]} I{first_layer_print_size[0]} J{first_layer_print_size[1]}\n {endif}\n M400\n M500 ; save cali data\nM623\n\nM622 J0\n G28\nM623\n\n;===== bed leveling end ================================\n\nG39.1 ; cali nozzle wrapped detection pos\nM500\n\n;===== z ofst cali start =====\n\n M190 S[bed_temperature_initial_layer_single]; ensure bed temp\n\n G383 O0 M2 T140\n M500\n\n;===== z ofst cali end =====\n\nM400\n;M73 P99\n\nM141 S[overall_chamber_temperature]\nM104 S{nozzle_temperature_initial_layer[initial_no_support_extruder]} A\n\n;===== mech mode sweep start =====\n M1002 gcode_claim_action : 3\n\n G90\n G1 Z5 F1200\n G1 X187 Y160 F20000\n T1000\n M400 P200\n\n M970.3 Q1 A5 K0 O1\n M974 Q1 S2 P0\n\n M970.3 Q0 A5 K0 O1\n M974 Q0 S2 P0\n\n M970.2 Q2 K0 W38 Z0.01\n M974 Q2 S2 P0\n M500\n\n M975 S1\n;===== mech mode sweep end =====\n\nM400\n;M73 P99\n\nG150.3 ; move to garbage can to wait for temp\nM1026\n\n;===== xy ofst cali start =====\n\nM1002 judge_flag auto_cali_toolhead_offset_flag\n\nM622 J0\n M1012.5 N1 R1\nM623\n\nM622 J1\n M1002 gcode_claim_action : 39\n M141 S0\n M620.17 T0 S{nozzle_temperature_initial_layer[(first_non_support_filaments[0] != -1 ? first_non_support_filaments[0] : first_filaments[0])]} L{(first_non_support_filaments[0] != -1 ? first_non_support_filaments[0] : first_filaments[0])}\n M620.17 T1 S{nozzle_temperature_initial_layer[(first_non_support_filaments[1] != -1 ? first_non_support_filaments[1] : first_filaments[1])]} L{(first_non_support_filaments[1] != -1 ? first_non_support_filaments[1] : first_filaments[1])}\n G383 O1 T{nozzle_temperature_initial_layer[initial_no_support_extruder]} L{initial_no_support_extruder}\n M500\n M141 S[overall_chamber_temperature]\nM623\n\nM622 J2\n M1002 gcode_claim_action : 39\n M141 S0\n M620.17 T0 S{nozzle_temperature_initial_layer[(first_non_support_filaments[0] != -1 ? first_non_support_filaments[0] : first_filaments[0])]} L{(first_non_support_filaments[0] != -1 ? first_non_support_filaments[0] : first_filaments[0])}\n M620.17 T1 S{nozzle_temperature_initial_layer[(first_non_support_filaments[1] != -1 ? first_non_support_filaments[1] : first_filaments[1])]} L{(first_non_support_filaments[1] != -1 ? first_non_support_filaments[1] : first_filaments[1])}\n G383.3 T{nozzle_temperature_initial_layer[initial_no_support_extruder]} L{initial_no_support_extruder}\n M500\n M141 S[overall_chamber_temperature]\nM623\n;===== xy ofst cali end =====\n\nM400\n;M73 P99\n\nM1002 gcode_claim_action : 0\nM400\n\n;============switch again==================\n\nM211 X0 Y0 Z0 ;turn off soft endstop\nG91\nG1 Z6 F1200\nG90\nM1002 set_filament_type:{filament_type[initial_no_support_extruder]}\nM620 S[initial_no_support_extruder]A\nM400\nT[initial_no_support_extruder]\nM400\nM628 S0\nM629\nM400\nM621 S[initial_no_support_extruder]A\n\n;============switch again==================\n\nM400\n;M73 P99\n\n;===== wait temperature reaching the reference value =======\n\nM104 S{nozzle_temperature_initial_layer[initial_no_support_extruder]} ; rise to print tmpr\n\nM140 S[bed_temperature_initial_layer_single] \nM190 S[bed_temperature_initial_layer_single] \n\n ;========turn off light and fans =============\n M960 S1 P0 ; turn off laser\n M960 S2 P0 ; turn off laser\n M106 S0 ; turn off fan\n M106 P2 S0 ; turn off big fan\n\n {if (overall_chamber_temperature >= 40)}\n M106 P3 S0 ; turn off chamber fan\n {else}\n M142 P1 R35 S40 U0.3 V0.5 ; set chamber autocooling\n {endif}\n\n ;============set motor current==================\n M400 S1\n\n;===== wait temperature reaching the reference value =======\n\nM400\n;M73 P99\n\n;===== for Textured PEI Plate , lower the nozzle as the nozzle was touching topmost of the texture when homing ==\n {if curr_bed_type==\"Textured PEI Plate\"}\n G29.1 Z{-0.02} ; for Textured PEI Plate\n {endif}\n \nG150.1\n\nM975 S1 ; turn on mech mode supression\nM983.4 S1 ; turn on deformation compensation \nG29.2 S1 ; turn on pos comp\nG29.7 S1\n\nG90\nG1 Z5 F1200\nG1 Y295 F30000\nG1 Y265 F18000\n\n;===== nozzle load line ===============================\nG29.2 S1 ; ensure z comp turn on\nG90\nM83\nM109 S{nozzle_temperature_initial_layer[initial_no_support_extruder]}\n{if filament_type[initial_no_support_extruder] == \"TPU\"}\n ;G130 O0 F{filament_max_volumetric_speed[initial_no_support_extruder]/2/2.4053} L80 E5 D12 A{first_layer_print_min[0]} B{first_layer_print_min[1]} I{first_layer_print_size[0]} J{first_layer_print_size[1]}\n G130 O0 X250 Y-0.5 Z0.8 F{filament_max_volumetric_speed[initial_no_support_extruder]/2/2.4053} L40 E20 D5\n{else}\n ;G130 O0 F{filament_max_volumetric_speed[initial_no_support_extruder]/2/2.4053} L80 E5 D12 A{first_layer_print_min[0]} B{first_layer_print_min[1]} I{first_layer_print_size[0]} J{first_layer_print_size[1]}\n G130 O0 X250 Y-0.5 Z0.8 F{filament_max_volumetric_speed[initial_no_support_extruder]/2/2.4053} L40 E20 D5\n{endif}\nG90\nM83\nG1 Z0.2\n\n;===== noozle load line end ===========================\n\nM400\n;M73 P99\n\nM993 A1 B1 C1 ; nozzle cam detection allowed.\n\n{if (filament_type[initial_no_support_extruder] == \"TPU\")}\nM1015.3 S1;enable tpu clog detect\n{else}\nM1015.3 S0;disable tpu clog detect\n{endif}\n\n{if (filament_type[initial_no_support_extruder] == \"PLA\") || (filament_type[initial_no_support_extruder] == \"PETG\")\n || (filament_type[initial_no_support_extruder] == \"PLA-CF\") || (filament_type[initial_no_support_extruder] == \"PETG-CF\")}\nM1015.4 S1 K1 H[nozzle_diameter] ;enable E air printing detect\n{else}\nM1015.4 S0 K0 H[nozzle_diameter] ;disable E air printing detect\n{endif}\n\nM620.6 I[initial_no_support_extruder] W1 ;enable ams air printing detect\n\nM211 Z1\nG29.99\n\n\n", "machine_end_gcode": ";===== date: 2024/12/19 =====================\n;===== H2D =====================\nG392 S0 ;turn off nozzle clog detect\nM993 A0 B0 C0 ; nozzle cam detection not allowed.\n\n{if timelapse_type == 2}\nM991 S0 P-1 ;end timelapse immediately\n{endif}\nM400 ; wait for buffer to clear\nG92 E0 ; zero the extruder\nG1 E-0.8 F1800 ; retract\nG1 Z{max_layer_z + 0.5} F900 ; lower z a little\n\nG90\nG150.3\n\n{if timelapse_type == 1}\nM991 S0 P-1 ;end timelapse at safe pos\n{endif}\n\nM141 S0 ; turn off chamber heating\nM140 S0 ; turn off bed\nM106 S0 ; turn off fan\nM106 P2 S0 ; turn off remote part cooling fan\nM106 P3 S0 ; turn off chamber cooling fan\n\n; pull back filament to AMS\nM620 S65535\nT65535\nG150.2\nM621 S65535\n\nM620 S65279\nT65279\nG150.2\nM621 S65279\n\nG150.3\n\nM104 S0 T0; turn off hotend\nM104 S0 T1; turn off hotend\n\nM400 ; wait all motion done\nM17 S\nM17 Z0.4 ; lower z motor current to reduce impact if there is something in the bottom\n{if (max_layer_z + 100.0) < 320}\n G1 Z{max_layer_z + 100.0} F600\n G1 Z{max_layer_z +98.0}\n{else}\n G1 Z320 F600\n G1 Z320\n{endif}\nM400 P100\nM17 R ; restore z current\n\nM220 S100 ; Reset feedrate magnitude\nM201.2 K1.0 ; Reset acc magnitude\nM73.2 R1.0 ;Reset left time magnitude\nM1002 set_gcode_claim_speed_level : 0\n\nM1015.4 S0 K0 ;disable air printing detect\n;=====printer finish sound=========\nM17\nM400 S1\nM1006 S1\nM1006 A53 B10 L99 C53 D10 M99 E53 F10 N99 \nM1006 A57 B10 L99 C57 D10 M99 E57 F10 N99 \nM1006 A0 B15 L0 C0 D15 M0 E0 F15 N0 \nM1006 A53 B10 L99 C53 D10 M99 E53 F10 N99 \nM1006 A57 B10 L99 C57 D10 M99 E57 F10 N99 \nM1006 A0 B15 L0 C0 D15 M0 E0 F15 N0 \nM1006 A48 B10 L99 C48 D10 M99 E48 F10 N99 \nM1006 A0 B15 L0 C0 D15 M0 E0 F15 N0 \nM1006 A60 B10 L99 C60 D10 M99 E60 F10 N99 \nM1006 W\n;=====printer finish sound=========\nM400\nM18\n\n", "layer_change_gcode": ";======== H2D 20250414========\n; layer num/total_layer_count: {layer_num+1}/[total_layer_count]\n; update layer progress\nM73 L{layer_num+1}\nM991 S0 P{layer_num} ;notify layer change\n\n{if !spiral_mode && print_sequence != \"by object\" && filament_type[initial_no_support_extruder] != \"TPU\"}\n M622.1 S0 ; for previous firmware, default turn off\n M1002 set_flag g39_forced_detection_flag=1\n M1002 judge_flag g39_forced_detection_flag\n M622 J1\n {if layer_num == 3}\n M993 A2 B2 C2 ; nozzle cam detection allow status save.\n M993 A0 B0 C0 ; nozzle cam detection not allowed.\n \n G39 R{-retraction_length} E{retraction_length} M30 N{filament_max_volumetric_speed[initial_no_support_extruder]/2.4053}\n G130 O0 X285 Y-0.5 Z0.8 F{filament_max_volumetric_speed[initial_no_support_extruder]/2/2.4053} L10 E2 D0\n M83\n G1 E{-retraction_length} F1800\n G3 Z{layer_z + 0.4} I1.217 J0 P1 F60000\n \n M993 A3 B3 C3 ; nozzle cam detection allow status restore.\n {elsif layer_z == 2}\n M993 A2 B2 C2 ; nozzle cam detection allow status save.\n M993 A0 B0 C0 ; nozzle cam detection not allowed.\n \n G39 R{-retraction_length} E{retraction_length} M30 N{filament_max_volumetric_speed[initial_no_support_extruder]/2.4053}\n G130 O0 X290 Y-0.5 Z0.8 F{filament_max_volumetric_speed[initial_no_support_extruder]/2/2.4053} L10 E2 D0\n M83\n G1 E{-retraction_length} F1800\n G3 Z{layer_z + 0.4} I1.217 J0 P1 F60000\n \n M993 A3 B3 C3 ; nozzle cam detection allow status restore.\n {elsif layer_z == 4}\n M993 A2 B2 C2 ; nozzle cam detection allow status save.\n M993 A0 B0 C0 ; nozzle cam detection not allowed.\n \n G39 R{-retraction_length} E{retraction_length} M30 N{filament_max_volumetric_speed[initial_no_support_extruder]/2.4053}\n G130 O0 X295 Y-0.5 Z0.8 F{filament_max_volumetric_speed[initial_no_support_extruder]/2/2.4053} L10 E2 D0\n M83\n G1 E{-retraction_length} F1800\n G3 Z{layer_z + 0.4} I1.217 J0 P1 F60000\n \n M993 A3 B3 C3 ; nozzle cam detection allow status restore.\n {endif}\n M623\n{endif}\n", - "time_lapse_gcode": ";======== H2D 20250213========\n; SKIPPABLE_START\n; SKIPTYPE: timelapse\nM622.1 S1 ; for prev firware, default turned on\n\nM1002 judge_flag timelapse_record_flag\n\nM622 J1\nM993 A2 B2 C2\nM993 A0 B0 C0\n\n{if !spiral_mode }\nM9712 E{most_used_physical_extruder_id} M{timelapse_type}\nM83\nG1 Z{max_layer_z + 0.4} F1200\nM400\nM9713\n{endif}\n\nM9711 M{timelapse_type} E{most_used_physical_extruder_id} Z{layer_z} S11 C10 O0 T3000\n\n{if !spiral_mode }\nM9712 E{most_used_physical_extruder_id} M{timelapse_type}\nG90\nG1 Z{max_layer_z + 3.0} F1200\nG1 Y295 F30000\nG1 Y265 F18000\nM83\nM9713\n{endif}\nM993 A3 B3 C3\n\nM623\n; SKIPPABLE_END\n", + "time_lapse_gcode": ";======== H2D 20250213========\n; SKIPPABLE_START\n; SKIPTYPE: timelapse\nM622.1 S1 ; for prev firware, default turned on\n\nM1002 judge_flag timelapse_record_flag\n\nM622 J1\nM993 A2 B2 C2\nM993 A0 B0 C0\n\n{if !spiral_mode && !has_timelapse_safe_pos }\nM9712 E{most_used_physical_extruder_id} M{timelapse_type}\nM83\nG1 Z{max_layer_z + 0.4} F1200\nM400\nM9713\n{endif}\n\n{if has_timelapse_safe_pos}\nM9711 M{timelapse_type} E{most_used_physical_extruder_id} X{timelapse_pos_x} Y{timelapse_pos_y} Z{layer_z} S11 C10 O0 T3000\n{else}\nM9711 M{timelapse_type} E{most_used_physical_extruder_id} Z{layer_z} S11 C10 O0 T3000\n{endif}\n\n{if !spiral_mode && !has_timelapse_safe_pos }\nM9712 E{most_used_physical_extruder_id} M{timelapse_type}\nG90\nG1 Z{max_layer_z + 3.0} F1200\nG1 Y295 F30000\nG1 Y265 F18000\nM83\nM9713\n{endif}\nM993 A3 B3 C3\n\nM623\n; SKIPPABLE_END\n", "change_filament_gcode": ";======== H2D ========\n;===== 20250417 =====\nM993 A2 B2 C2 ; nozzle cam detection allow status save.\nM993 A0 B0 C0 ; nozzle cam detection not allowed.\n\n{if (filament_type[next_extruder] == \"PLA\") || (filament_type[next_extruder] == \"PETG\")\n || (filament_type[next_extruder] == \"PLA-CF\") || (filament_type[next_extruder] == \"PETG-CF\")}\nM1015.4 S1 K0 ;disable E air printing detect\n{else}\nM1015.4 S0 ; disable E air printing detect\n{endif}\n\nM620 S[next_extruder]A\nM1002 gcode_claim_action : 4\nM204 S9000\n\nG1 Z{max_layer_z + 3.0} F1200\n\nM400\nM106 P1 S0\nM106 P2 S0\n\n{if toolchange_count == 2}\n; get travel path for change filament\n;M620.1 X[travel_point_1_x] Y[travel_point_1_y] F21000 P0\n;M620.1 X[travel_point_2_x] Y[travel_point_2_y] F21000 P1\n;M620.1 X[travel_point_3_x] Y[travel_point_3_y] F21000 P2\n{endif}\n\nM620.10 A0 F{filament_max_volumetric_speed[current_extruder]/2.4053*60} L[flush_length] H{nozzle_diameter[current_extruder]} T{nozzle_temperature_range_high[current_extruder]} P{nozzle_temperature[current_extruder]} S1\nM620.10 A1 F{filament_max_volumetric_speed[next_extruder]/2.4053*60} L[flush_length] H{nozzle_diameter[next_extruder]} T{nozzle_temperature_range_high[next_extruder]} P{nozzle_temperature[next_extruder]} S1\n\n{if filament_type[current_extruder] == \"TPU\"}\nM620.11 S0 L0 I[current_extruder] E-{retraction_distances_when_cut[current_extruder]} F{max((filament_max_volumetric_speed[current_extruder]/2.4053*60), 200)}\n{else}\n{if (filament_type[current_extruder] == \"PA\") || (filament_type[current_extruder] == \"PA-GF\")}\nM620.11 S1 L0 I[current_extruder] R4 D2 E-{retraction_distances_when_cut[current_extruder]} F{max((filament_max_volumetric_speed[current_extruder]/2.4053*60), 200)}\n{else}\nM620.11 S1 L0 I[current_extruder] R10 D8 E-{retraction_distances_when_cut[current_extruder]} F{max((filament_max_volumetric_speed[current_extruder]/2.4053*60), 200)}\n{endif}\n{endif}\n\n{if filament_type[current_extruder] == \"TPU\" || filament_type[next_extruder] == \"TPU\"}\nM620.11 H2 C331\n{else}\nM620.11 H0\n{endif}\n\nT[next_extruder]\n\n;deretract\n{if filament_type[next_extruder] == \"TPU\"}\n{else}\n{if (filament_type[next_extruder] == \"PA\") || (filament_type[next_extruder] == \"PA-GF\")}\n;VG1 E1 F{max(new_filament_e_feedrate, 200)}\n;VG1 E1 F{max(new_filament_e_feedrate/2, 100)}\n{else}\n;VG1 E4 F{max(new_filament_e_feedrate, 200)}\n;VG1 E4 F{max(new_filament_e_feedrate/2, 100)}\n{endif}\n{endif}\n\n; VFLUSH_START\n\n{if flush_length>41.5}\n;VG1 E41.5 F{min(old_filament_e_feedrate,new_filament_e_feedrate)}\n;VG1 E{flush_length-41.5} F{new_filament_e_feedrate}\n{else}\n;VG1 E{flush_length} F{min(old_filament_e_feedrate,new_filament_e_feedrate)}\n{endif}\n\nSYNC T{ceil(flush_length / 125) * 5}\n\n; VFLUSH_END\n\nM1002 set_filament_type:{filament_type[next_extruder]}\n\nM400\nM83\n{if next_extruder < 255}\n\nM628 S0\n;VM109 S[new_filament_temp]\nM629\nM400\n\nM983.3 F{filament_max_volumetric_speed[next_extruder]/2.4} A0.4\n\nM400\n{if wipe_avoid_perimeter}\nG1 Y320 F30000\nG1 X{wipe_avoid_pos_x} F30000\n{endif}\nG1 Y295 F30000\nG1 Y265 F18000\nG1 Z{max_layer_z + 3.0} F3000\n{if layer_z <= (initial_layer_print_height + 0.001)}\nM204 S[initial_layer_acceleration]\n{else}\nM204 S[default_acceleration]\n{endif}\n{else}\nG1 X[x_after_toolchange] Y[y_after_toolchange] Z[z_after_toolchange] F12000\n{endif}\nM621 S[next_extruder]A\n\nM993 A3 B3 C3 ; nozzle cam detection allow status restore.\n\n{if (filament_type[next_extruder] == \"TPU\")}\nM1015.3 S1;enable tpu clog detect\n{else}\nM1015.3 S0;disable tpu clog detect\n{endif}\n\n{if (filament_type[next_extruder] == \"PLA\") || (filament_type[next_extruder] == \"PETG\")\n || (filament_type[next_extruder] == \"PLA-CF\") || (filament_type[next_extruder] == \"PETG-CF\")}\nM1015.4 S1 K1 H[nozzle_diameter] ;enable E air printing detect\n{else}\nM1015.4 S0 ; disable E air printing detect\n{endif}\n\nM620.6 I[next_extruder] W1 ;enable ams air printing detect\nM1002 gcode_claim_action : 0\n" } \ No newline at end of file diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 71cf247f7..ff2f3a8d1 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -169,6 +169,8 @@ set(lisbslic3r_sources GCode/Smoothing.hpp GCode/CoolingBuffer.cpp GCode/CoolingBuffer.hpp + GCode/TimelapsePosPicker.cpp + GCode/TimelapsePosPicker.hpp GCode.cpp GCode.hpp GCodeReader.cpp diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 4a9c73bde..ecc4da446 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1860,6 +1860,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato PROFILE_FUNC(); m_print = &print; + m_timelapse_pos_picker.init(&print,m_writer.get_xy_offset().cast()); // modifies m_silent_time_estimator_enabled DoExport::init_gcode_processor(print.config(), m_processor, m_silent_time_estimator_enabled, m_writer.extruders()); @@ -2535,6 +2536,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser // and export G-code into file. tool_ordering.cal_most_used_extruder(print.config()); + m_printed_objects.insert(&object); this->process_layers(print, tool_ordering, collect_layers_to_print(object), *print_object_instance_sequential_active - object.instances().data(), file, prime_extruder); { @@ -3676,7 +3678,6 @@ GCode::LayerResult GCode::process_layer( bool has_insert_timelapse_gcode = false; bool has_wipe_tower = (layer_tools.has_wipe_tower && m_wipe_tower); - int physical_extruder_id = print.config().physical_extruder_map.get_at(most_used_extruder); ZHopType z_hope_type = ZHopType(FILAMENT_CONFIG(z_hop_types)); LiftType auto_lift_type = LiftType::NormalLift; @@ -3689,7 +3690,7 @@ GCode::LayerResult GCode::process_layer( m_object_layer_over_raft = false; if (! print.config().layer_change_gcode.value.empty()) { DynamicConfig config; - config.set_key_value("most_used_physical_extruder_id", new ConfigOptionInt(physical_extruder_id)); + config.set_key_value("most_used_physical_extruder_id", new ConfigOptionInt(m_config.physical_extruder_map.get_at(most_used_extruder))); config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); gcode += this->placeholder_parser_process("layer_change_gcode", @@ -4072,14 +4073,34 @@ GCode::LayerResult GCode::process_layer( } } - auto insert_timelapse_gcode = [this, print_z, &print, &physical_extruder_id, &layer_object_label_ids]() -> std::string { + auto insert_timelapse_gcode = [this, print_z, &print, &most_used_extruder, &layer_object_label_ids,&printed_objects = std::as_const(m_printed_objects)]() -> std::string { + PosPickCtx ctx; + ctx.curr_pos = { (coord_t)(scale_(m_writer.get_position().x())),(coord_t)(scale_(m_writer.get_position().y())) }; + ctx.curr_layer = this->layer(); + ctx.curr_extruder_id = m_writer.filament()->extruder_id(); + ctx.picture_extruder_id = most_used_extruder; + if (m_config.nozzle_diameter.size() > 1) { + ctx.extruder_height_gap = m_config.extruder_printable_height.values[0] - m_config.extruder_printable_height.values[1]; + ctx.liftable_extruder_id = m_config.extruder_printable_height.values[0] < m_config.extruder_printable_height.values[0] ? 0 : 1; + } + + ctx.print_sequence = m_config.print_sequence; + if (m_config.print_sequence == PrintSequence::ByObject) + ctx.printed_objects = printed_objects; + ctx.based_on_all_layer = m_config.timelapse_type == TimelapseType::tlSmooth; + + auto timelapse_pos=m_timelapse_pos_picker.pick_pos(ctx); + std::string timepals_gcode; if (!print.config().time_lapse_gcode.value.empty()) { DynamicConfig config; config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); - config.set_key_value("most_used_physical_extruder_id", new ConfigOptionInt(physical_extruder_id)); + config.set_key_value("most_used_physical_extruder_id", new ConfigOptionInt(m_config.physical_extruder_map.get_at(most_used_extruder))); + config.set_key_value("timelapse_pos_x", new ConfigOptionInt(timelapse_pos.x())); + config.set_key_value("timelapse_pos_y", new ConfigOptionInt(timelapse_pos.y())); + config.set_key_value("has_timelapse_safe_pos", new ConfigOptionBool(timelapse_pos != DefaultTimelapsePos)); timepals_gcode = this->placeholder_parser_process("timelapse_gcode", print.config().time_lapse_gcode.value, m_writer.filament()->id(), &config) + "\n"; } m_writer.set_current_position_clear(false); diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 93714dd75..80c3480ac 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -19,6 +19,7 @@ #include "EdgeGrid.hpp" #include "GCode/ThumbnailData.hpp" #include "libslic3r/ObjectID.hpp" +#include "GCode/TimelapsePosPicker.hpp" #include #include @@ -491,6 +492,7 @@ private: Wipe m_wipe; AvoidCrossingPerimeters m_avoid_crossing_perimeters; RetractWhenCrossingPerimeters m_retract_when_crossing_perimeters; + TimelapsePosPicker m_timelapse_pos_picker; bool m_enable_loop_clipping; // If enabled, the G-code generator will put following comments at the ends // of the G-code lines: _EXTRUDE_SET_SPEED, _WIPE, _OVERHANG_FAN_START, _OVERHANG_FAN_END @@ -556,6 +558,8 @@ private: Print *m_print{nullptr}; + std::set m_printed_objects; + // Processor GCodeProcessor m_processor; diff --git a/src/libslic3r/GCode/TimelapsePosPicker.cpp b/src/libslic3r/GCode/TimelapsePosPicker.cpp new file mode 100644 index 000000000..8888ce2e4 --- /dev/null +++ b/src/libslic3r/GCode/TimelapsePosPicker.cpp @@ -0,0 +1,396 @@ +#include "TimelapsePosPicker.hpp" +#include "Layer.hpp" + +namespace Slic3r { + void TimelapsePosPicker::init(const Print* print_, const Point& plate_offset) + { + reset(); + m_plate_offset = plate_offset; + print = print_; + construct_printable_area_by_printer(); + } + + void TimelapsePosPicker::reset() + { + print = nullptr; + m_bed_polygon.clear(); + m_extruder_printable_area.clear(); + m_all_layer_pos = std::nullopt; + } + + /** + * @brief Retrieves a list of print objects based on the provided optional set of printed objects. + * + * If the optional set of printed objects is provided, it converts the set into a vector. + * Otherwise, it retrieves all objects from the print instance. + */ + std::vector TimelapsePosPicker::get_object_list(const std::optional>& printed_objects) + { + std::vector object_list; + if (printed_objects.has_value()) { + object_list = std::vector(printed_objects->begin(), printed_objects->end()); + } + else { + object_list = std::vector(print->objects().begin(), print->objects().end()); + } + return object_list; + } + + /** + * @brief Constructs the printable area based on printer configuration. + * + * This function initializes the bed polygon, excludes specific areas, accounts for wipe towers, + * and calculates the printable area for each extruder. + */ + void TimelapsePosPicker::construct_printable_area_by_printer() + { + auto config = print->config(); + size_t extruder_count = config.nozzle_diameter.size(); + m_extruder_printable_area.clear(); + m_extruder_printable_area.resize(extruder_count); + + for (size_t idx = 0; idx < config.printable_area.values.size(); ++idx) + m_bed_polygon.points.emplace_back(coord_t(scale_(config.printable_area.values[idx].x())), coord_t(scale_(config.printable_area.values[idx].y()))); + + Polygon bed_exclude_area; + for (size_t idx = 0; idx < config.bed_exclude_area.values.size(); ++idx) + bed_exclude_area.points.emplace_back(coord_t(scale_(config.bed_exclude_area.values[idx].x())), coord_t(scale_(config.bed_exclude_area.values[idx].y()))); + + Point base_wp_pt = print->get_fake_wipe_tower().pos.cast(); + base_wp_pt = Point{ scale_(base_wp_pt.x()),scale_(base_wp_pt.y()) }; + + auto transform_wt_pt = [base_wp_pt](const Point& pt) -> Point { + Point out =pt; + out += base_wp_pt; + return out; + }; + auto wt_box = print->wipe_tower_data().bbx; + Polygon wipe_tower_area{ + {transform_wt_pt({scale_(wt_box.min.x()),scale_(wt_box.min.y())})}, + {transform_wt_pt({scale_(wt_box.max.x()),scale_(wt_box.min.y())})}, + {transform_wt_pt({scale_(wt_box.max.x()),scale_(wt_box.max.y())})}, + {transform_wt_pt({scale_(wt_box.min.x()),scale_(wt_box.max.y())})} + }; + + for (size_t idx = 0; idx < extruder_count; ++idx) { + ExPolygons printable_area = diff_ex(diff(m_bed_polygon, bed_exclude_area), { wipe_tower_area }); + if (idx < config.extruder_printable_area.size()) { + Polygon extruder_printable_area; + for (size_t j = 0; j < config.extruder_printable_area.values[idx].size(); ++j) + extruder_printable_area.points.emplace_back(coord_t(scale_(config.extruder_printable_area.values[idx][j].x())), coord_t(scale_(config.extruder_printable_area.values[idx][j].y()))); + printable_area = intersection_ex(printable_area, Polygons{ extruder_printable_area }); + } + m_extruder_printable_area[idx] = printable_area; + } + } + + /** + * @brief Collects object slice data within a specified height range for a given layer. + * + * @param layer The layer for which slices are being collected. + * @param height_range The height range to consider for collecting slices. + * @param object_list List of print objects to process. + * @return ExPolygons representing the collected slice data. + */ + ExPolygons TimelapsePosPicker::collect_object_slices_data(const Layer* layer, float height_range, const std::vector& object_list) + { + auto range_intersect = [](int left1, int right1, int left2, int right2) { + if (left1 <= left2 && left2 <= right1) + return true; + if (left2 <= left1 && left1 <= right2) + return true; + return false; + }; + ExPolygons ret; + float z_target = layer->print_z; + float z_low = height_range < 0 ? layer->print_z + height_range : layer->print_z; + float z_high = height_range < 0 ? layer->print_z : layer->print_z + height_range; + if (z_low <= 0) + return to_expolygons({ m_bed_polygon }); + + for (auto& obj : object_list) { + for (auto& instance : obj->instances()) { + auto instance_bbox = get_real_instance_bbox(instance); + if(range_intersect(instance_bbox.min.z(), instance_bbox.max.z(), layer->print_z + height_range, layer->print_z)){ + ExPolygon expoly; + expoly.contour = { + {scale_(instance_bbox.min.x()), scale_(instance_bbox.min.y())}, + {scale_(instance_bbox.max.x()), scale_(instance_bbox.min.y())}, + {scale_(instance_bbox.max.x()), scale_(instance_bbox.max.y())}, + {scale_(instance_bbox.min.x()), scale_(instance_bbox.max.y())} + }; + expoly.contour = expand_object_projection(expoly.contour); + ret.emplace_back(std::move(expoly)); + } + } + } + ret = union_ex(ret); + return ret; + } + + + Polygons TimelapsePosPicker::collect_limit_areas_for_camera(const std::vector& object_list) + { + Polygons ret; + for (auto& obj : object_list) + ret.emplace_back(get_limit_area_for_camera(obj)); + ret = union_(ret); + return ret; + } + + // expand the object expolygon by safe distance + Polygon TimelapsePosPicker::expand_object_projection(const Polygon& poly) + { + // the input poly is bounding box, so we get the first offseted polygon is ok + float radius = scale_(print->config().extruder_clearance_max_radius.value / 2); + return offset(poly, radius)[0]; + } + + double TimelapsePosPicker::get_raft_height(const PrintObject* obj) + { + if (!obj || !obj->has_raft()) + return 0; + auto slice_params = obj->slicing_parameters(); + int base_raft_layers = slice_params.base_raft_layers; + double base_raft_height = slice_params.base_raft_layer_height; + int interface_raft_layers = slice_params.interface_raft_layers; + double interface_raft_height = slice_params.interface_raft_layer_height; + double contact_raft_layer_height = slice_params.contact_raft_layer_height; + + double ret = print->config().initial_layer_print_height; + if (base_raft_layers - 1 > 0) + ret += (base_raft_layers - 1) * base_raft_height; + if (interface_raft_layers - 1 > 0) + ret += (interface_raft_layers - 1) * interface_raft_height; + if (obj->config().raft_layers > 1) + ret += contact_raft_layer_height; + + return ret + slice_params.gap_raft_object; + } + + // get the real instance bounding box, remove the plate offset and add raft height + BoundingBoxf3 TimelapsePosPicker::get_real_instance_bbox(const PrintInstance& instance) + { + auto bbox = instance.get_bounding_box(); + double raft_height =get_raft_height(instance.print_object); + bbox.max.z() += raft_height; + // remove plate offset + bbox.min.x() -= m_plate_offset.x(); + bbox.max.x() -= m_plate_offset.x(); + bbox.min.y() -= m_plate_offset.y(); + bbox.max.y() -= m_plate_offset.y(); + return bbox; + } + + Polygon TimelapsePosPicker::get_limit_area_for_camera(const PrintObject* obj) + { + if (!obj) + return {}; + auto bbox = get_real_instance_bbox(obj->instances().front()); + float radius = print->config().extruder_clearance_max_radius.value / 2; + + auto offset_bbox = bbox.inflated(sqrt(2) * radius); + Polygon ret = { + DefaultCameraPos, + {scale_(offset_bbox.max.x()),scale_(offset_bbox.min.y())}, + {scale_(offset_bbox.max.x()),scale_(offset_bbox.max.y())}, + {scale_(offset_bbox.min.x()),scale_(offset_bbox.max.y())} + }; + return ret; + } + + /** + * @brief Selects the nearest position within the given safe areas relative to the current position. + * + * This function determines the closest point in the safe areas to the provided current position. + * If the current position is already inside a safe area, it returns the current position. + * If no safe areas are defined, return default timelapse position. + * + * @param curr_pos The reference point representing the current position. + * @param safe_areas A collection of extended polygons defining the safe areas. + * @return Point The nearest point within the safe areas or the default timelapse position if no safe areas exist. + */ + Point pick_pos_internal(const Point& curr_pos, const ExPolygons& safe_areas) + { + if (std::any_of(safe_areas.begin(), safe_areas.end(), [&curr_pos](const ExPolygon& p) { return p.contains(curr_pos);})) + return curr_pos; + + if (safe_areas.empty()) + return DefaultTimelapsePos; + + double min_distance = std::numeric_limits::max(); + Point nearest_point =DefaultTimelapsePos; +#if 0 + for (const auto& expoly : safe_areas) { + Polygons polys = to_polygons(expoly); + for (auto& poly : polys) { + auto nearest_point_ptr = poly.closest_point(curr_pos); + if (nearest_point_ptr) { + double dist = (*nearest_point_ptr - curr_pos).cast().norm(); + if (min_distance > dist) { + min_distance = dist; + nearest_point = *nearest_point_ptr; + } + } + } + } +#else + for (const auto& expoly : safe_areas) { + Polygons polys = to_polygons(expoly); + for (auto& poly : polys) { + for (size_t idx = 0; idx < poly.points.size(); ++idx) { + Line line(poly.points[idx], poly.points[next_idx_modulo(idx, poly.points)]); + Point candidate; + double dist = line.distance_to_squared(curr_pos, &candidate); + if (min_distance > dist) { + min_distance = dist; + nearest_point = candidate; + } + } + } + } +#endif + return nearest_point; + } + + Point TimelapsePosPicker::pick_pos(const PosPickCtx& ctx) + { + Point res; + if (ctx.based_on_all_layer) + res = pick_pos_for_all_layer(ctx); + else + res = pick_pos_for_curr_layer(ctx); + + return { unscale_(res.x()), unscale_(res.y()) }; + } + + // get center point of curr object + Point TimelapsePosPicker::get_object_center(const PrintObject* obj) + { + if (!obj) + return {}; + // in bambu studio, each object only has one instance + auto instance = obj->instances().front(); + auto instance_bbox = get_real_instance_bbox(instance); + Point min_p{ instance_bbox.min.x(),instance_bbox.min.y() }; + Point max_p{ instance_bbox.max.x(),instance_bbox.max.y() }; + + return { scale_((min_p.x() + max_p.x()) / 2),scale_(min_p.y() + max_p.y() / 2) }; + } + + Point TimelapsePosPicker::pick_nearest_object_center(const Point& curr_pos, const std::vector& object_list) + { + if (object_list.empty()) + return {}; + const PrintObject* ptr = object_list.front(); + double distance = std::numeric_limits::max(); + for (auto& obj : object_list) { + Point obj_center = get_object_center(obj); + double dist = (obj_center - curr_pos).cast().norm(); + if (distance > dist) { + distance = dist; + ptr = obj; + } + } + return get_object_center(ptr); + } + + Point TimelapsePosPicker::pick_pos_for_curr_layer(const PosPickCtx& ctx) + { + float height_gap = 0; + if (ctx.curr_extruder_id != ctx.picture_extruder_id) { + if (ctx.liftable_extruder_id.has_value() && ctx.picture_extruder_id != ctx.liftable_extruder_id && ctx.extruder_height_gap.has_value()) + height_gap = -*ctx.extruder_height_gap; + } + + std::vector object_list = get_object_list(ctx.printed_objects); + + ExPolygons layer_slices = collect_object_slices_data(ctx.curr_layer,height_gap, object_list); + Polygons camera_limit_areas = collect_limit_areas_for_camera(object_list); + ExPolygons unplacable_area = union_ex(layer_slices, camera_limit_areas); + ExPolygons extruder_printable_area = m_extruder_printable_area[ctx.picture_extruder_id]; + + ExPolygons safe_area = diff_ex(extruder_printable_area, unplacable_area); + Point objs_center = get_objects_center(object_list); + return pick_pos_internal(objs_center, safe_area); + } + + /** + * @brief Calculates the center of multiple objects. + * + * This function computes the average center of all instances of the provided objects. + * + * @param object_list A vector of pointers to PrintObject instances. + * @return Point The average center of all objects. + */ + Point TimelapsePosPicker::get_objects_center(const std::vector& object_list) + { + if (object_list.empty()) + return Point(0,0); + double sum_x = 0.0; + double sum_y = 0.0; + size_t total_instances = 0; + for (auto& obj : object_list) { + for (auto& instance : obj->instances()) { + const auto& bbox = get_real_instance_bbox(instance); + Point min_p{ bbox.min.x(),bbox.min.y() }; + Point max_p{ bbox.max.x(),bbox.max.y() }; + double center_x = (min_p.x() + max_p.x()) / 2.f; + double center_y = (min_p.y() + max_p.y()) / 2.f; + sum_x += center_x; + sum_y += center_y; + total_instances += 1; + } + } + return Point{ coord_t(scale_(sum_x / total_instances)),coord_t(scale_(sum_y / total_instances)) }; + } + + Point TimelapsePosPicker::pick_pos_for_all_layer(const PosPickCtx& ctx) + { + float height_gap = 0; + if (ctx.curr_extruder_id != ctx.picture_extruder_id) { + if (ctx.liftable_extruder_id.has_value() && ctx.picture_extruder_id != ctx.liftable_extruder_id && ctx.extruder_height_gap.has_value()) + height_gap = *ctx.extruder_height_gap; + } + if (ctx.curr_layer->print_z < height_gap) + return DefaultTimelapsePos; + if (m_all_layer_pos) + return *m_all_layer_pos; + + Polygons object_projections; + + auto object_list = get_object_list(std::nullopt); + + for (auto& obj : object_list) { + for (auto& instance : obj->instances()) { + const auto& bbox = get_real_instance_bbox(instance); + Point min_p{ scale_(bbox.min.x()),scale_(bbox.min.y()) }; + Point max_p{ scale_(bbox.max.x()),scale_(bbox.max.y()) }; + Polygon obj_proj{ { min_p.x(),min_p.y() }, + { max_p.x(),min_p.y() }, + { max_p.x(),max_p.y() }, + { min_p.x(),max_p.y() } + }; + object_projections.emplace_back(expand_object_projection(obj_proj)); + } + }; + + object_projections = union_(object_projections); + Polygons camera_limit_areas = collect_limit_areas_for_camera(object_list); + Polygons unplacable_area = union_(object_projections, camera_limit_areas); + + ExPolygons extruder_printable_area; + if (m_extruder_printable_area.size() > 1) + extruder_printable_area = intersection_ex(m_extruder_printable_area[0], m_extruder_printable_area[1]); + else if (m_extruder_printable_area.size() == 1) + extruder_printable_area = m_extruder_printable_area.front(); + + ExPolygons safe_area = diff_ex(extruder_printable_area, unplacable_area); + + Point starting_pos = get_objects_center(object_list); + + m_all_layer_pos = pick_pos_internal(starting_pos, safe_area); + return *m_all_layer_pos; + } + +} \ No newline at end of file diff --git a/src/libslic3r/GCode/TimelapsePosPicker.hpp b/src/libslic3r/GCode/TimelapsePosPicker.hpp new file mode 100644 index 000000000..af5711eca --- /dev/null +++ b/src/libslic3r/GCode/TimelapsePosPicker.hpp @@ -0,0 +1,71 @@ +#ifndef TIMELAPSE_POS_PICKER_HPP +#define TIMELAPSE_POS_PICKER_HPP + +#include +#include "libslic3r/Point.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/PrintConfig.hpp" + +namespace Slic3r { + + const Point DefaultTimelapsePos = Point(0, 0); + const Point DefaultCameraPos = Point(0, 0); + + class Layer; + class Print; + + struct PosPickCtx + { + Point curr_pos; + const Layer* curr_layer; + int picture_extruder_id; // the extruder id to take picture + int curr_extruder_id; + bool based_on_all_layer; // whether to calculate the safe position based all layers + PrintSequence print_sequence; // print sequence: by layer or by object + std::optional> printed_objects; // printed objects, only have value in by object mode + std::optional liftable_extruder_id; // extruder id that can be lifted, cause bed height to change + std::optional extruder_height_gap; // the height gap caused by extruder lift + }; + + // data are stored without plate offset + class TimelapsePosPicker + { + public: + TimelapsePosPicker() = default; + ~TimelapsePosPicker() = default; + + Point pick_pos(const PosPickCtx& ctx); + void init(const Print* print, const Point& plate_offset); + void reset(); + private: + void construct_printable_area_by_printer(); + + Point pick_pos_for_curr_layer(const PosPickCtx& ctx); + Point pick_pos_for_all_layer(const PosPickCtx& ctx); + + ExPolygons collect_object_slices_data(const Layer* curr_layer, float height_range, const std::vector& object_list); + Polygons collect_limit_areas_for_camera(const std::vector& object_list); + + Polygon expand_object_projection(const Polygon& poly); + + Point pick_nearest_object_center(const Point& curr_pos, const std::vector& object_list); + Point get_objects_center(const std::vector& object_list); + + Polygon get_limit_area_for_camera(const PrintObject* obj); + std::vector get_object_list(const std::optional>& printed_objects); + + double get_raft_height(const PrintObject* obj); + BoundingBoxf3 get_real_instance_bbox(const PrintInstance& instance); + Point get_object_center(const PrintObject* obj); + private: + const Print* print{ nullptr }; + std::vector m_extruder_printable_area; + Polygon m_bed_polygon; + Point m_plate_offset; + + std::optional m_all_layer_pos; + }; +} + +#endif \ No newline at end of file diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index ccb311a5e..5f2c18666 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -4164,7 +4164,7 @@ int Print::load_cached_data(const std::string& directory) return ret; } -BoundingBoxf3 PrintInstance::get_bounding_box() { +BoundingBoxf3 PrintInstance::get_bounding_box() const { return print_object->model_object()->instance_bounding_box(*model_instance, false); } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 7a470d628..f8d367f3b 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -205,7 +205,7 @@ struct PrintInstance // Shift of this instance's center into the world coordinates. Point shift; - BoundingBoxf3 get_bounding_box(); + BoundingBoxf3 get_bounding_box() const; Polygon get_convex_hull_2d(); // OrcaSlicer //