mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-14 11:21:18 +00:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a336941f4 | ||
|
|
ea90f45127 | ||
|
|
c7b4e8ef84 | ||
|
|
8f1103f064 | ||
|
|
6e42a9b0ae | ||
|
|
e77ba5f672 |
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"name": "Goofoo",
|
||||
"version": "02.04.00.01",
|
||||
"force_update": "0",
|
||||
"description": "Goofoo configurations",
|
||||
"machine_model_list": [
|
||||
{
|
||||
"name": "Goofoo GP1",
|
||||
"sub_path": "machine/Goofoo GP1.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo TurboM",
|
||||
"sub_path": "machine/Goofoo TurboM.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo Velo+",
|
||||
"sub_path": "machine/Goofoo Velo+.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo Velo+ Tool",
|
||||
"sub_path": "machine/Goofoo Velo+ Tool.json"
|
||||
}
|
||||
],
|
||||
"process_list": [
|
||||
{
|
||||
"name": "fdm_process_common",
|
||||
"sub_path": "process/fdm_process_common.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_process_klipper_common",
|
||||
"sub_path": "process/fdm_process_klipper_common.json"
|
||||
},
|
||||
{
|
||||
"name": "0.20mm Standard @Goofoo GP1",
|
||||
"sub_path": "process/0.20mm Standard @Goofoo GP1.json"
|
||||
},
|
||||
{
|
||||
"name": "0.20mm Standard @Goofoo TurboM",
|
||||
"sub_path": "process/0.20mm Standard @Goofoo TurboM.json"
|
||||
},
|
||||
{
|
||||
"name": "0.20mm Standard @Goofoo Velo+",
|
||||
"sub_path": "process/0.20mm Standard @Goofoo Velo+.json"
|
||||
},
|
||||
{
|
||||
"name": "0.30mm Standard @Goofoo Velo+",
|
||||
"sub_path": "process/0.30mm Standard @Goofoo Velo+.json"
|
||||
},
|
||||
{
|
||||
"name": "0.10mm Standard @Goofoo Velo+",
|
||||
"sub_path": "process/0.10mm Standard @Goofoo Velo+.json"
|
||||
}
|
||||
],
|
||||
"filament_list": [],
|
||||
"machine_list": [
|
||||
{
|
||||
"name": "fdm_machine_common",
|
||||
"sub_path": "machine/fdm_machine_common.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_klipper_common",
|
||||
"sub_path": "machine/fdm_klipper_common.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo GP1 0.4 nozzle",
|
||||
"sub_path": "machine/Goofoo GP1 0.4 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo TurboM 0.4 nozzle",
|
||||
"sub_path": "machine/Goofoo TurboM 0.4 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo Velo+ 0.2 nozzle",
|
||||
"sub_path": "machine/Goofoo Velo+ 0.2 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo Velo+ 0.4 nozzle",
|
||||
"sub_path": "machine/Goofoo Velo+ 0.4 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo Velo+ 0.6 nozzle",
|
||||
"sub_path": "machine/Goofoo Velo+ 0.6 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo Velo+ Tool 0.4",
|
||||
"sub_path": "machine/Goofoo Velo+ Tool 0.4.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256">
|
||||
<g id="Layer_1">
|
||||
<title>Layer 1</title>
|
||||
<text fill="#0000ff" x="31.38885" y="400.22266" id="svg_1" font-size="24" font-family="Noto Sans JP" text-anchor="start" xml:space="preserve" stroke="#0000ff" transform="matrix(2.19884 0 0 1.66297 -63.896 -416.238)">GP1</text>
|
||||
</g>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 373 B |
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
<svg width="600" height="480" xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
<g>
|
||||
<title>Layer 1</title>
|
||||
<text transform="matrix(2.3401 0 0 1.462 -92.4668 -168.496)" stroke="#000" stroke-width="3" xml:space="preserve" text-anchor="start" font-family="Noto Sans JP" font-size="24" id="svg_1" y="421.25588" x="52.33404" fill="#000000">TurboM</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 353 B |
Binary file not shown.
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="480" viewBox="0 0 600 480">
|
||||
<g id="Layer_1">
|
||||
<title>Layer 1</title>
|
||||
<text transform="matrix(2.19884 0 0 1.66297 -203.896 -238.238)" stroke="#0000ff" xml:space="preserve" text-anchor="start" font-family="Noto Sans JP" font-size="24" id="svg_1" y="400.22266" x="120.07198" fill="#0000ff">VELO+</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 376 B |
Binary file not shown.
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"type": "machine",
|
||||
"name": "Goofoo GP1 0.4 nozzle",
|
||||
"inherits": "fdm_klipper_common",
|
||||
"from": "system",
|
||||
"setting_id": "x4GJ08PooNDQ8WJj",
|
||||
"instantiation": "true",
|
||||
"nozzle_diameter": [
|
||||
"0.4"
|
||||
],
|
||||
"default_filament_profile": [
|
||||
"Goofoo ABS @System"
|
||||
],
|
||||
"printer_model": "Goofoo GP1",
|
||||
"default_print_profile": "0.20mm Standard @Goofoo GP1",
|
||||
"printer_variant": "0.4",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\n;G92 E0",
|
||||
"change_filament_gcode": "G5",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\n;layer\n;{if layer_num == 2}\n;M106 P1 S255\n;{endif}\nSET_PRINT_STATS_INFO CURRENT_LAYER=[layer_num]\n",
|
||||
"machine_end_gcode": "; End G-code\nEND_PRINT",
|
||||
"machine_pause_gcode": "G5",
|
||||
"machine_start_gcode": "M117 Printing...\nM107 ; Turn off the fan\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nM104 S140\nM190 S[first_layer_bed_temperature] ; wait for bed temp\n;G32 ;Load bed mesh\nG28 ;home\nG29 bed leveling\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nG0 Z2.0 F600;\nG0 X50 Y10 F8000;\nG92 E0.0 ; reset extruder distance position\nG0 Z0.4 F600;\nG1 X100.0 E10 F3000.0 ; intro line\nG92 E0.0 ; reset extruder distance position\nG1 X200.0 E15 F3000.0 ; intro line\nG92 E0.0 ; reset extruder distance position\nG0 Z0.8 F600;\nG1 X100.0 E15 F3000.0 ; intro line\nG1 Z0.4 F600 ;Wipe\nG0 Y12 F6000 ;Wipe\nG1 X100 F6000 ;Wipe\nG0 Y8 F12000 ;Wipe\nG1 X200 F6000 ;Wipe\nG1 X190 Y12 F6000 ;Wipe\nG1 X180 Y8 F6000 ;Wipe\nG1 X170 Y12 F6000 ;Wipe\nG1 X160 Y8 F6000 ;Wipe\nG1 X150 Y12 F6000 ;Wipe\n;G0 Z2.0 F600;\nG92 E0.0 ; reset extruder distance position\n;START_PRINT",
|
||||
"purge_in_prime_tower": "0",
|
||||
"time_lapse_gcode": ";TIMELAPSE_TAKE_FRAME",
|
||||
"printable_height": "250",
|
||||
"printable_area": [
|
||||
"0x0",
|
||||
"250x0",
|
||||
"250x250",
|
||||
"0x250"
|
||||
],
|
||||
"deretraction_speed": [
|
||||
"0"
|
||||
],
|
||||
"extruder_clearance_height_to_lid": "140",
|
||||
"extruder_clearance_height_to_rod": "36",
|
||||
"extruder_clearance_radius": "65",
|
||||
"machine_max_acceleration_e": [
|
||||
"20000",
|
||||
"5000"
|
||||
],
|
||||
"machine_max_acceleration_retracting": [
|
||||
"15000",
|
||||
"5000"
|
||||
],
|
||||
"machine_max_acceleration_z": [
|
||||
"100",
|
||||
"200"
|
||||
],
|
||||
"machine_max_jerk_e": [
|
||||
"1",
|
||||
"2.5"
|
||||
],
|
||||
"machine_max_jerk_x": [
|
||||
"5",
|
||||
"9"
|
||||
],
|
||||
"machine_max_jerk_y": [
|
||||
"5",
|
||||
"9"
|
||||
],
|
||||
"machine_max_speed_e": [
|
||||
"25",
|
||||
"25"
|
||||
],
|
||||
"machine_max_speed_x": [
|
||||
"500",
|
||||
"200"
|
||||
],
|
||||
"machine_max_speed_y": [
|
||||
"500",
|
||||
"200"
|
||||
],
|
||||
"machine_max_speed_z": [
|
||||
"12",
|
||||
"12"
|
||||
],
|
||||
"retract_before_wipe": [
|
||||
"100%"
|
||||
],
|
||||
"retract_length_toolchange": [
|
||||
"10"
|
||||
],
|
||||
"retraction_length": [
|
||||
"0.5"
|
||||
],
|
||||
"retraction_minimum_travel": [
|
||||
"2"
|
||||
],
|
||||
"retraction_speed": [
|
||||
"50"
|
||||
],
|
||||
"thumbnails": "300x300/PNG",
|
||||
"z_hop": [
|
||||
"0"
|
||||
],
|
||||
"upward_compatible_machine": []
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"type": "machine_model",
|
||||
"name": "Goofoo GP1",
|
||||
"nozzle_diameter": "0.4",
|
||||
"bed_model": "Goofoo-GP1.stl",
|
||||
"bed_texture": "Goofoo-GP1-texture.svg",
|
||||
"family": "Goofoo",
|
||||
"machine_tech": "FFF",
|
||||
"model_id": "V003",
|
||||
"default_materials": "Goofoo ABS @System"
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"type": "machine",
|
||||
"name": "Goofoo TurboM 0.4 nozzle",
|
||||
"inherits": "fdm_klipper_common",
|
||||
"from": "system",
|
||||
"setting_id": "Wmdn5zTy1HH93Q5d",
|
||||
"instantiation": "true",
|
||||
"nozzle_diameter": [
|
||||
"0.4"
|
||||
],
|
||||
"nozzle_type": "brass",
|
||||
"printer_model": "Goofoo TurboM",
|
||||
"printer_variant": "0.4",
|
||||
"default_filament_profile": [
|
||||
"Goofoo ABS @System"
|
||||
],
|
||||
"default_print_profile": "0.20mm Standard @Goofoo TurboM",
|
||||
"printable_area": [
|
||||
"0x0",
|
||||
"230x0",
|
||||
"230x200",
|
||||
"0x200"
|
||||
],
|
||||
"extruder_clearance_height_to_lid": "350",
|
||||
"extruder_clearance_height_to_rod": "60",
|
||||
"extruder_clearance_radius": "50",
|
||||
"machine_end_gcode": "M104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG92 E0.0 ; reset extruder distance position;\nG1 E-1 F2100 ; retract\nG28 X Y; home X Y axis\n;M84 ; disable motors\nM107 ; turn off fan\n;SET_VELOCITY_LIMIT ACCEL_TO_DECEL=4000",
|
||||
"machine_max_jerk_z": [
|
||||
"3",
|
||||
"0.4"
|
||||
],
|
||||
"machine_start_gcode": "M107 ; Turn off the fan\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\n\n\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\n\nG28 ;home\n\nG1 X2 Y10 F3000;\n\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\n\nG1 Z0.28 F240\n\nG92 E0\n\nG1 Y140 E10 F1500 ; prime the nozzle\n\nG1 X2.3 F5000\nG92 E0\n\nG1 Y10 E10 F1200 ; prime the nozzle\n;G0 Z2.0 F600;\nG92 E0.0 ; reset extruder distance position;\nM82\n;SET_PRESSURE_ADVANCE ADVANCE=0.05\n;SET_VELOCITY_LIMIT ACCEL_TO_DECEL=10000\n",
|
||||
"printable_height": "200",
|
||||
"z_hop": [
|
||||
"0"
|
||||
],
|
||||
"machine_max_speed_e": [
|
||||
"25",
|
||||
"25"
|
||||
],
|
||||
"machine_max_speed_x": [
|
||||
"500",
|
||||
"200"
|
||||
],
|
||||
"machine_max_speed_y": [
|
||||
"500",
|
||||
"200"
|
||||
],
|
||||
"machine_max_speed_z": [
|
||||
"12",
|
||||
"12"
|
||||
],
|
||||
"upward_compatible_machine": []
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"type": "machine_model",
|
||||
"name": "Goofoo TurboM",
|
||||
"nozzle_diameter": "0.4",
|
||||
"bed_model": "Goofoo-TurboM.stl",
|
||||
"bed_texture": "Goofoo-TurboM-texture.svg",
|
||||
"family": "Goofoo",
|
||||
"machine_tech": "FFF",
|
||||
"model_id": "V002",
|
||||
"default_materials": "Goofoo ABS @System"
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
{
|
||||
"type": "machine",
|
||||
"name": "Goofoo Velo+ 0.2 nozzle",
|
||||
"inherits": "fdm_klipper_common",
|
||||
"from": "system",
|
||||
"setting_id": "wSr6VNECZHekeNRi",
|
||||
"instantiation": "true",
|
||||
"nozzle_diameter": [
|
||||
"0.2"
|
||||
],
|
||||
"nozzle_type": "hardened_steel",
|
||||
"printer_model": "Goofoo Velo+",
|
||||
"printer_variant": "0.2",
|
||||
"default_filament_profile": [
|
||||
"Goofoo ABS @System"
|
||||
],
|
||||
"default_print_profile": "0.10mm Standard @Goofoo Velo+",
|
||||
"printable_area": [
|
||||
"0x0",
|
||||
"600x0",
|
||||
"600x500",
|
||||
"0x500"
|
||||
],
|
||||
"machine_end_gcode": "{if max_layer_z < printable_height}G1 Z{z_offset+min(max_layer_z+2, printable_height)} F600 ; Move print head up{endif}\nG1 X5 Y{print_bed_max[1]*0.8} F{travel_speed*60} ; present print\n{if max_layer_z < printable_height-10}G1 Z{z_offset+min(max_layer_z+70, printable_height-10)} F600 ; Move print head further up{endif}\n{if max_layer_z < max_print_height*0.6}G1 Z{printable_height*0.6} F600 ; Move print head further up{endif}\nM140 S0 ; turn off heatbed\nM104 S0 ; turn off temperature\nM107 ; turn off fan\nM84 X Y E ; disable motors",
|
||||
"printable_height": "600",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0",
|
||||
"change_filament_gcode": "PAUSE",
|
||||
"deretraction_speed": [
|
||||
"0"
|
||||
],
|
||||
"extruder_clearance_height_to_lid": "350",
|
||||
"extruder_clearance_height_to_rod": "60",
|
||||
"extruder_clearance_radius": "50",
|
||||
"machine_max_acceleration_e": [
|
||||
"20000",
|
||||
"5000"
|
||||
],
|
||||
"machine_max_acceleration_retracting": [
|
||||
"15000",
|
||||
"5000"
|
||||
],
|
||||
"machine_max_acceleration_z": [
|
||||
"100",
|
||||
"200"
|
||||
],
|
||||
"machine_max_jerk_e": [
|
||||
"1",
|
||||
"2.5"
|
||||
],
|
||||
"machine_max_jerk_x": [
|
||||
"5",
|
||||
"9"
|
||||
],
|
||||
"machine_max_jerk_y": [
|
||||
"5",
|
||||
"9"
|
||||
],
|
||||
"machine_max_speed_e": [
|
||||
"50",
|
||||
"25"
|
||||
],
|
||||
"machine_max_speed_x": [
|
||||
"730",
|
||||
"200"
|
||||
],
|
||||
"machine_max_speed_y": [
|
||||
"730",
|
||||
"200"
|
||||
],
|
||||
"machine_max_speed_z": [
|
||||
"15",
|
||||
"12"
|
||||
],
|
||||
"machine_start_gcode": "G90 ; 绝对坐标模式\nM83 ; 挤出机相对模式\nM140 S[first_layer_bed_temperature] ; 设置热床温度\nM104 S140 ; 临时喷嘴温度=切片首层设定(防止归零渗料)\nG4 S10 ; 预热等待10秒\nG28 ; 归零所有轴\nG1 Z50 F240 ; Z轴抬升\nG1 X2 Y10 F3000 ; 移动至起始位置\nM104 S[nozzle_temperature_initial_layer] ; 正式设置喷嘴温度(与切片设置一致)\nM190 S[bed_temperature_initial_layer_single] ; 等待热床温度稳定\nM109 S[nozzle_temperature_initial_layer] ; 等待喷嘴温度稳定\nG1 Z0.28 F240 ; 喷嘴下降至打印高度\nG92 E0 ; 挤出机归零\nG1 Y140 E10 F1500 ; 画第一条 priming 线\nG1 X2.3 F5000 ; X轴微调\nG92 E0 ; 挤出机归零\nG1 Y10 E10 F1200 ; 画第二条 priming 线\nG92 E0 ; 最终挤出机归零",
|
||||
|
||||
"retract_before_wipe": [
|
||||
"100%"
|
||||
],
|
||||
"retract_length_toolchange": [
|
||||
"10"
|
||||
],
|
||||
"retraction_length": [
|
||||
"0.5"
|
||||
],
|
||||
"retraction_minimum_travel": [
|
||||
"2"
|
||||
],
|
||||
"retraction_speed": [
|
||||
"50"
|
||||
],
|
||||
"thumbnails": "300x300/PNG",
|
||||
"z_hop": [
|
||||
"0"
|
||||
],
|
||||
"machine_max_acceleration_travel": [
|
||||
"10000",
|
||||
"10000"
|
||||
],
|
||||
"max_layer_height": [
|
||||
"0.16"
|
||||
],
|
||||
"min_layer_height": [
|
||||
"0.04"
|
||||
],
|
||||
"upward_compatible_machine": []
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"type": "machine",
|
||||
"name": "Goofoo Velo+ 0.4 nozzle",
|
||||
"inherits": "fdm_klipper_common",
|
||||
"from": "system",
|
||||
"setting_id": "XzHz1vNOYg3Ofe8X",
|
||||
"instantiation": "true",
|
||||
"nozzle_diameter": [
|
||||
"0.4"
|
||||
],
|
||||
"nozzle_type": "hardened_steel",
|
||||
"printer_model": "Goofoo Velo+",
|
||||
"printer_variant": "0.4",
|
||||
"default_filament_profile": [
|
||||
"Goofoo ABS @System"
|
||||
],
|
||||
"default_print_profile": "0.20mm Standard @Goofoo Velo+",
|
||||
"printable_area": [
|
||||
"0x0",
|
||||
"600x0",
|
||||
"600x500",
|
||||
"0x500"
|
||||
],
|
||||
"machine_end_gcode": "{if max_layer_z < printable_height}G1 Z{z_offset+min(max_layer_z+2, printable_height)} F600 ; Move print head up{endif}\nG1 X5 Y{print_bed_max[1]*0.8} F{travel_speed*60} ; present print\n{if max_layer_z < printable_height-10}G1 Z{z_offset+min(max_layer_z+70, printable_height-10)} F600 ; Move print head further up{endif}\n{if max_layer_z < max_print_height*0.6}G1 Z{printable_height*0.6} F600 ; Move print head further up{endif}\nM140 S0 ; turn off heatbed\nM104 S0 ; turn off temperature\nM107 ; turn off fan\nM84 X Y E ; disable motors",
|
||||
"printable_height": "600",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0",
|
||||
"change_filament_gcode": "PAUSE",
|
||||
"deretraction_speed": [
|
||||
"0"
|
||||
],
|
||||
"extruder_clearance_height_to_lid": "350",
|
||||
"extruder_clearance_height_to_rod": "60",
|
||||
"extruder_clearance_radius": "50",
|
||||
"machine_max_acceleration_e": [
|
||||
"20000",
|
||||
"5000"
|
||||
],
|
||||
"machine_max_acceleration_retracting": [
|
||||
"15000",
|
||||
"5000"
|
||||
],
|
||||
"machine_max_acceleration_z": [
|
||||
"100",
|
||||
"200"
|
||||
],
|
||||
"machine_max_jerk_e": [
|
||||
"1",
|
||||
"2.5"
|
||||
],
|
||||
"machine_max_jerk_x": [
|
||||
"5",
|
||||
"9"
|
||||
],
|
||||
"machine_max_jerk_y": [
|
||||
"5",
|
||||
"9"
|
||||
],
|
||||
"machine_max_speed_e": [
|
||||
"50",
|
||||
"25"
|
||||
],
|
||||
"machine_max_speed_x": [
|
||||
"730",
|
||||
"200"
|
||||
],
|
||||
"machine_max_speed_y": [
|
||||
"730",
|
||||
"200"
|
||||
],
|
||||
"machine_max_speed_z": [
|
||||
"15",
|
||||
"12"
|
||||
],
|
||||
"machine_start_gcode": "G90 ; 绝对坐标模式\nM83 ; 挤出机相对模式\nM140 S[first_layer_bed_temperature] ; 设置热床温度\nM104 S140 ; 临时喷嘴温度=切片首层设定(防止归零渗料)\nG4 S10 ; 预热等待10秒\nG28 ; 归零所有轴\nG1 Z50 F240 ; Z轴抬升\nG1 X2 Y10 F3000 ; 移动至起始位置\nM104 S[nozzle_temperature_initial_layer] ; 正式设置喷嘴温度(与切片设置一致)\nM190 S[bed_temperature_initial_layer_single] ; 等待热床温度稳定\nM109 S[nozzle_temperature_initial_layer] ; 等待喷嘴温度稳定\nG1 Z0.28 F240 ; 喷嘴下降至打印高度\nG92 E0 ; 挤出机归零\nG1 Y140 E10 F1500 ; 画第一条 priming 线\nG1 X2.3 F5000 ; X轴微调\nG92 E0 ; 挤出机归零\nG1 Y10 E10 F1200 ; 画第二条 priming 线\nG92 E0 ; 最终挤出机归零",
|
||||
|
||||
"retract_before_wipe": [
|
||||
"100%"
|
||||
],
|
||||
"retract_length_toolchange": [
|
||||
"10"
|
||||
],
|
||||
"retraction_length": [
|
||||
"0.5"
|
||||
],
|
||||
"retraction_minimum_travel": [
|
||||
"2"
|
||||
],
|
||||
"retraction_speed": [
|
||||
"50"
|
||||
],
|
||||
"thumbnails": "300x300/PNG",
|
||||
"z_hop": [
|
||||
"0"
|
||||
],
|
||||
"machine_max_acceleration_travel": [
|
||||
"10000",
|
||||
"10000"
|
||||
],
|
||||
"upward_compatible_machine": []
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
{
|
||||
"type": "machine",
|
||||
"name": "Goofoo Velo+ 0.6 nozzle",
|
||||
"inherits": "fdm_klipper_common",
|
||||
"from": "system",
|
||||
"setting_id": "Pauu7JRq7u2OD6kv",
|
||||
"instantiation": "true",
|
||||
"nozzle_diameter": [
|
||||
"0.6"
|
||||
],
|
||||
"nozzle_type": "hardened_steel",
|
||||
"printer_model": "Goofoo Velo+",
|
||||
"printer_variant": "0.6",
|
||||
"default_filament_profile": [
|
||||
"Goofoo ABS @System"
|
||||
],
|
||||
"default_print_profile": "0.30mm Standard @Goofoo Velo+",
|
||||
"printable_area": [
|
||||
"0x0",
|
||||
"600x0",
|
||||
"600x500",
|
||||
"0x500"
|
||||
],
|
||||
"machine_end_gcode": "{if max_layer_z < printable_height}G1 Z{z_offset+min(max_layer_z+2, printable_height)} F600 ; Move print head up{endif}\nG1 X5 Y{print_bed_max[1]*0.8} F{travel_speed*60} ; present print\n{if max_layer_z < printable_height-10}G1 Z{z_offset+min(max_layer_z+70, printable_height-10)} F600 ; Move print head further up{endif}\n{if max_layer_z < max_print_height*0.6}G1 Z{printable_height*0.6} F600 ; Move print head further up{endif}\nM140 S0 ; turn off heatbed\nM104 S0 ; turn off temperature\nM107 ; turn off fan\nM84 X Y E ; disable motors",
|
||||
"printable_height": "600",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0",
|
||||
"change_filament_gcode": "PAUSE",
|
||||
"deretraction_speed": [
|
||||
"0"
|
||||
],
|
||||
"extruder_clearance_height_to_lid": "350",
|
||||
"extruder_clearance_height_to_rod": "60",
|
||||
"extruder_clearance_radius": "50",
|
||||
"machine_max_acceleration_e": [
|
||||
"20000",
|
||||
"5000"
|
||||
],
|
||||
"machine_max_acceleration_retracting": [
|
||||
"15000",
|
||||
"5000"
|
||||
],
|
||||
"machine_max_acceleration_z": [
|
||||
"100",
|
||||
"200"
|
||||
],
|
||||
"machine_max_jerk_e": [
|
||||
"1",
|
||||
"2.5"
|
||||
],
|
||||
"machine_max_jerk_x": [
|
||||
"5",
|
||||
"9"
|
||||
],
|
||||
"machine_max_jerk_y": [
|
||||
"5",
|
||||
"9"
|
||||
],
|
||||
"machine_max_speed_e": [
|
||||
"50",
|
||||
"25"
|
||||
],
|
||||
"machine_max_speed_x": [
|
||||
"730",
|
||||
"200"
|
||||
],
|
||||
"machine_max_speed_y": [
|
||||
"730",
|
||||
"200"
|
||||
],
|
||||
"machine_max_speed_z": [
|
||||
"15",
|
||||
"12"
|
||||
],
|
||||
"machine_start_gcode": "G90 ; 绝对坐标模式\nM83 ; 挤出机相对模式\nM140 S[first_layer_bed_temperature] ; 设置热床温度\nM104 S140 ; 临时喷嘴温度=切片首层设定(防止归零渗料)\nG4 S10 ; 预热等待10秒\nG28 ; 归零所有轴\nG1 Z50 F240 ; Z轴抬升\nG1 X2 Y10 F3000 ; 移动至起始位置\nM104 S[nozzle_temperature_initial_layer] ; 正式设置喷嘴温度(与切片设置一致)\nM190 S[bed_temperature_initial_layer_single] ; 等待热床温度稳定\nM109 S[nozzle_temperature_initial_layer] ; 等待喷嘴温度稳定\nG1 Z0.28 F240 ; 喷嘴下降至打印高度\nG92 E0 ; 挤出机归零\nG1 Y140 E10 F1500 ; 画第一条 priming 线\nG1 X2.3 F5000 ; X轴微调\nG92 E0 ; 挤出机归零\nG1 Y10 E10 F1200 ; 画第二条 priming 线\nG92 E0 ; 最终挤出机归零",
|
||||
|
||||
"retract_before_wipe": [
|
||||
"100%"
|
||||
],
|
||||
"retract_length_toolchange": [
|
||||
"10"
|
||||
],
|
||||
"retraction_length": [
|
||||
"0.5"
|
||||
],
|
||||
"retraction_minimum_travel": [
|
||||
"2"
|
||||
],
|
||||
"retraction_speed": [
|
||||
"50"
|
||||
],
|
||||
"thumbnails": "300x300/PNG",
|
||||
"z_hop": [
|
||||
"0"
|
||||
],
|
||||
"machine_max_acceleration_travel": [
|
||||
"10000",
|
||||
"10000"
|
||||
],
|
||||
"max_layer_height": [
|
||||
"0.4"
|
||||
],
|
||||
"min_layer_height": [
|
||||
"0.12"
|
||||
],
|
||||
"upward_compatible_machine": []
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
{
|
||||
"type": "machine",
|
||||
"name": "Goofoo Velo+ Tool 0.4",
|
||||
"inherits": "Goofoo Velo+ 0.4 nozzle",
|
||||
"from": "system",
|
||||
"setting_id": "ww39WeaF5nYks1em",
|
||||
"instantiation": "true",
|
||||
"nozzle_diameter": [
|
||||
"0.4",
|
||||
"0.4"
|
||||
],
|
||||
"printer_model": "Goofoo Velo+ Tool",
|
||||
"change_filament_gcode": "T[next_extruder]\n;M104 S[nozzle_temperature_initial_layer] T[next_extruder]; 正式设置喷嘴温度(与切片设置一致)\n;M109 S[nozzle_temperature_initial_layer] T[next_extruder] ; 等待喷嘴温度稳定\n",
|
||||
"default_nozzle_volume_type": [
|
||||
"Standard",
|
||||
"Standard"
|
||||
],
|
||||
"deretraction_speed": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"extruder_colour": [
|
||||
"#FCE94F",
|
||||
"#FCE94F"
|
||||
],
|
||||
"extruder_offset": [
|
||||
"0x0",
|
||||
"0x0"
|
||||
],
|
||||
"extruder_printable_height": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"extruder_type": [
|
||||
"Direct Drive",
|
||||
"Direct Drive"
|
||||
],
|
||||
"extruder_variant_list": [
|
||||
"Direct Drive Standard",
|
||||
"Direct Drive Standard"
|
||||
],
|
||||
"long_retractions_when_cut": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"machine_end_gcode": "{if max_layer_z < printable_height}G1 Z{z_offset+min(max_layer_z+2, printable_height)} F600 ; Move print head up{endif}\nT0\nG1 X5 Y{print_bed_max[1]*0.8} F{travel_speed*60} ; present print\n{if max_layer_z < printable_height-10}G1 Z{z_offset+min(max_layer_z+70, printable_height-10)} F600 ; Move print head further up{endif}\n{if max_layer_z < max_print_height*0.6}G1 Z{printable_height*0.6} F600 ; Move print head further up{endif}\nM140 S0 ; turn off heatbed\nM104 S0 ; turn off temperature\nM107 ; turn off fan\nM84 X Y E ; disable motors",
|
||||
"machine_start_gcode": "G90 ; 绝对坐标模式\nM83 ; 挤出机相对模式\nM140 S[first_layer_bed_temperature] ; 设置热床温度\nM104 S140 T0 ; 临时喷嘴温度=切片首层设定(防止归零渗料)\nM104 S140 T1\nG4 S10 ; 预热等待10秒\nG28 ; 归零所有轴\nG1 Z50 F240 ; Z轴抬升\nG1 X2 Y10 F3000 ; 移动至起始位置\nM104 S[nozzle_temperature_initial_layer] T0 ; 正式设置喷嘴温度(与切片设置一致)\nM104 S[nozzle_temperature_initial_layer] T1 ; 正式设置喷嘴温度(与切片设置一致)\nM190 S[bed_temperature_initial_layer_single] T0; 等待热床温度稳定\nM109 S[nozzle_temperature_initial_layer] T0 ; 等待喷嘴温度稳定\nG1 Z0.28 F240 ; 喷嘴下降至打印高度\nG92 E0 ; 挤出机归零\nG1 Y140 E10 F1500 ; 画第一条 priming 线\nG1 X2.3 F5000 ; X轴微调\nG92 E0 ; 挤出机归零\nG1 Y10 E10 F1200 ; 画第二条 priming 线\nG92 E0 ; 最终挤出机归零\nG1 Z20 F240 ; Z轴抬升\nG1 X280 Y10 F3000 ; 移动至起始位置\nT1\nM104 S[nozzle_temperature_initial_layer] T1 ; 正式设置喷嘴温度(与切片设置一致)\nM190 S[bed_temperature_initial_layer_single] T1; 等待热床温度稳定\nM109 S[nozzle_temperature_initial_layer] T1; 等待喷嘴温度稳定\nG4 S10 ; 预热等待10秒\nG1 Z0.28 F240 ; 喷嘴下降至打印高度\nG92 E0 ; 挤出机归零\nG1 Y140 E10 F1500 ; 画第一条 priming 线\nG1 X282.3 F5000 ; X轴微调\nG92 E0 ; 挤出机归零\nG1 Y10 E10 F1200 ; 画第二条 priming 线\nG92 E0 ; 最终挤出机归零",
|
||||
"max_layer_height": [
|
||||
"0.32",
|
||||
"0.32"
|
||||
],
|
||||
"min_layer_height": [
|
||||
"0.08",
|
||||
"0.08"
|
||||
],
|
||||
"nozzle_flush_dataset": [
|
||||
"nil",
|
||||
"0"
|
||||
],
|
||||
"nozzle_type": [
|
||||
"nil",
|
||||
"hardened_steel"
|
||||
],
|
||||
"nozzle_volume": [
|
||||
"nil",
|
||||
"0"
|
||||
],
|
||||
"printable_area": [
|
||||
"0x0",
|
||||
"300x0",
|
||||
"300x300",
|
||||
"0x300"
|
||||
],
|
||||
"printer_extruder_id": [
|
||||
"1",
|
||||
"2"
|
||||
],
|
||||
"printer_extruder_variant": [
|
||||
"Direct Drive Standard",
|
||||
"Direct Drive Standard"
|
||||
],
|
||||
"purge_in_prime_tower": "0",
|
||||
"retract_before_wipe": [
|
||||
"100%",
|
||||
"100%"
|
||||
],
|
||||
"retract_length_toolchange": [
|
||||
"10",
|
||||
"10"
|
||||
],
|
||||
"retract_lift_above": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"retract_lift_below": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"retract_lift_enforce": [
|
||||
"All Surfaces",
|
||||
"All Surfaces"
|
||||
],
|
||||
"retract_restart_extra": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"retract_restart_extra_toolchange": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"retract_when_changing_layer": [
|
||||
"1",
|
||||
"1"
|
||||
],
|
||||
"retraction_distances_when_cut": [
|
||||
"18",
|
||||
"18"
|
||||
],
|
||||
"retraction_length": [
|
||||
"0.5",
|
||||
"0.5"
|
||||
],
|
||||
"retraction_minimum_travel": [
|
||||
"2",
|
||||
"2"
|
||||
],
|
||||
"retraction_speed": [
|
||||
"50",
|
||||
"50"
|
||||
],
|
||||
"single_extruder_multi_material": "0",
|
||||
"travel_slope": [
|
||||
"3",
|
||||
"3"
|
||||
],
|
||||
"wipe": [
|
||||
"1",
|
||||
"1"
|
||||
],
|
||||
"wipe_distance": [
|
||||
"1",
|
||||
"1"
|
||||
],
|
||||
"z_hop": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"z_hop_types": [
|
||||
"Slope Lift",
|
||||
"Slope Lift"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"type": "machine_model",
|
||||
"name": "Goofoo Velo+ Tool",
|
||||
"nozzle_diameter": "0.4",
|
||||
"bed_model": "Goofoo-Velo+.stl",
|
||||
"bed_texture": "Goofoo-Velo+-texture.svg",
|
||||
"family": "Goofoo",
|
||||
"machine_tech": "FFF",
|
||||
"model_id": "V006",
|
||||
"default_materials": "Goofoo ABS @System"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"type": "machine_model",
|
||||
"name": "Goofoo Velo+",
|
||||
"nozzle_diameter": "0.2;0.4;0.6",
|
||||
"bed_model": "Goofoo-Velo+.stl",
|
||||
"bed_texture": "Goofoo-Velo+-texture.svg",
|
||||
"family": "Goofoo",
|
||||
"machine_tech": "FFF",
|
||||
"model_id": "V001",
|
||||
"default_materials": "Goofoo ABS @System"
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"type": "machine",
|
||||
"name": "fdm_klipper_common",
|
||||
"inherits": "fdm_machine_common",
|
||||
"from": "system",
|
||||
"instantiation": "false",
|
||||
"gcode_flavor": "klipper",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\nG92 E0.0\n;[layer_z]\n\n",
|
||||
"machine_start_gcode": "G90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nM140 S{first_layer_bed_temperature[0]} ; set bed temp\nM104 S140 ; set temporary nozzle temp to prevent oozing\nG4 S10 ; wait 10s for preheating\nG28 ; home all axes\nG1 Z50 F240 ; raise Z axis\nG1 X2 Y10 F3000 ; move to start position\nM104 S{first_layer_temperature[0]} ; set final nozzle temp\nM190 S{first_layer_bed_temperature[0]} ; wait for bed temp\nM109 S{first_layer_temperature[0]} ; wait for nozzle temp\nG1 Z0.28 F240 ; lower nozzle to print height\nG92 E0 ; reset extrusion distance\nG1 Y140 E10 F1500 ; prime the nozzle\nG1 X2.3 F5000 ; slight X adjustment\nG92 E0 ; reset extrusion distance\nG1 Y10 E10 F1200 ; prime again\nG92 E0 ; final extrusion reset",
|
||||
"machine_end_gcode": "{if max_layer_z < printable_height}G1 Z{z_offset+min(max_layer_z+2, printable_height)} F600 ; Move print head up{endif}\nG1 X5 Y{print_bed_max[1]*0.8} F{travel_speed*60} ; present print\n{if max_layer_z < printable_height-10}G1 Z{z_offset+min(max_layer_z+70, printable_height-10)} F600 ; Move print head further up{endif}\n{if max_layer_z < max_print_height*0.6}G1 Z{printable_height*0.6} F600 ; Move print head further up{endif}\nM140 S0 ; turn off heatbed\nM104 S0 ; turn off temperature\nM107 ; turn off fan\nM84 X Y E ; disable motors",
|
||||
"change_filament_gcode": "PAUSE",
|
||||
"machine_pause_gcode": "PAUSE"
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
{
|
||||
"type": "machine",
|
||||
"name": "fdm_machine_common",
|
||||
"from": "system",
|
||||
"instantiation": "false",
|
||||
"printer_technology": "FFF",
|
||||
"deretraction_speed": [
|
||||
"40"
|
||||
],
|
||||
"extruder_colour": [
|
||||
"#FCE94F"
|
||||
],
|
||||
"extruder_offset": [
|
||||
"0x0"
|
||||
],
|
||||
"gcode_flavor": "marlin",
|
||||
"machine_max_acceleration_e": [
|
||||
"5000"
|
||||
],
|
||||
"machine_max_acceleration_extruding": [
|
||||
"10000"
|
||||
],
|
||||
"machine_max_acceleration_retracting": [
|
||||
"1000"
|
||||
],
|
||||
"machine_max_acceleration_x": [
|
||||
"10000"
|
||||
],
|
||||
"machine_max_acceleration_y": [
|
||||
"10000"
|
||||
],
|
||||
"machine_max_acceleration_z": [
|
||||
"500"
|
||||
],
|
||||
"machine_max_speed_e": [
|
||||
"60"
|
||||
],
|
||||
"machine_max_speed_x": [
|
||||
"500"
|
||||
],
|
||||
"machine_max_speed_y": [
|
||||
"500"
|
||||
],
|
||||
"machine_max_speed_z": [
|
||||
"10"
|
||||
],
|
||||
"machine_max_jerk_e": [
|
||||
"5"
|
||||
],
|
||||
"machine_max_jerk_x": [
|
||||
"8"
|
||||
],
|
||||
"machine_max_jerk_y": [
|
||||
"8"
|
||||
],
|
||||
"machine_max_jerk_z": [
|
||||
"0.4"
|
||||
],
|
||||
"machine_min_extruding_rate": [
|
||||
"0"
|
||||
],
|
||||
"machine_min_travel_rate": [
|
||||
"0"
|
||||
],
|
||||
"max_layer_height": [
|
||||
"0.32"
|
||||
],
|
||||
"min_layer_height": [
|
||||
"0.08"
|
||||
],
|
||||
"printable_height": "250",
|
||||
"extruder_clearance_radius": "65",
|
||||
"extruder_clearance_height_to_rod": "36",
|
||||
"extruder_clearance_height_to_lid": "140",
|
||||
"nozzle_diameter": [
|
||||
"0.4"
|
||||
],
|
||||
"printer_settings_id": "",
|
||||
"printer_variant": "0.4",
|
||||
"retraction_minimum_travel": [
|
||||
"2"
|
||||
],
|
||||
"retract_before_wipe": [
|
||||
"70%"
|
||||
],
|
||||
"retract_when_changing_layer": [
|
||||
"1"
|
||||
],
|
||||
"retraction_length": [
|
||||
"1"
|
||||
],
|
||||
"retract_length_toolchange": [
|
||||
"1"
|
||||
],
|
||||
"z_hop": [
|
||||
"0"
|
||||
],
|
||||
"retract_restart_extra": [
|
||||
"0"
|
||||
],
|
||||
"retract_restart_extra_toolchange": [
|
||||
"0"
|
||||
],
|
||||
"retraction_speed": [
|
||||
"60"
|
||||
],
|
||||
"single_extruder_multi_material": "1",
|
||||
"change_filament_gcode": "",
|
||||
"wipe": [
|
||||
"1"
|
||||
],
|
||||
"default_print_profile": "",
|
||||
"machine_start_gcode": "G0 Z20 F9000\nG92 E0; G1 E-10 F1200\nG28\nM970 Q1 A10 B10 C130 K0\nM970 Q1 A10 B131 C250 K1\nM974 Q1 S1 P0\nM970 Q0 A10 B10 C130 H20 K0\nM970 Q0 A10 B131 C250 K1\nM974 Q0 S1 P0\nM220 S100 ;Reset Feedrate\nM221 S100 ;Reset Flowrate\nG29 ;Home\nG90;\nG92 E0 ;Reset Extruder \nG1 Z2.0 F3000 ;Move Z Axis up \nG1 X10.1 Y20 Z0.28 F5000.0 ;Move to start position\nM109 S205;\nG1 X10.1 Y200.0 Z0.28 F1500.0 E15 ;Draw the first line\nG1 X10.4 Y200.0 Z0.28 F5000.0 ;Move to side a little\nG1 X10.4 Y20 Z0.28 F1500.0 E30 ;Draw the second line\nG92 E0 ;Reset Extruder \nG1 X110 Y110 Z2.0 F3000 ;Move Z Axis up",
|
||||
"machine_end_gcode": "M400 ; wait for buffer to clear\nG92 E0 ; zero the extruder\nG1 E-4.0 F3600; retract \nG91\nG1 Z3;\nM104 S0 ; turn off hotend\nM140 S0 ; turn off bed\nM106 S0 ; turn off fan\nG90 \nG0 X110 Y200 F3600 \nprint_end",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n",
|
||||
"machine_pause_gcode": "M601"
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"type": "process",
|
||||
"name": "0.10mm Standard @Goofoo Velo+",
|
||||
"inherits": "0.20mm Standard @Goofoo Velo+",
|
||||
"from": "system",
|
||||
"setting_id": "dJV4bTrLbOPzW2YB",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": [
|
||||
"Goofoo Velo+ 0.2 nozzle"
|
||||
],
|
||||
"layer_height": "0.1",
|
||||
"initial_layer_print_height": "0.1",
|
||||
"bottom_shell_layers": "6",
|
||||
"top_shell_layers": "12",
|
||||
"wall_loops": "4",
|
||||
"line_width": "0.22",
|
||||
"initial_layer_line_width": "0.25",
|
||||
"inner_wall_line_width": "0.22",
|
||||
"outer_wall_line_width": "0.22",
|
||||
"sparse_infill_line_width": "0.22",
|
||||
"internal_solid_infill_line_width": "0.22",
|
||||
"top_surface_line_width": "0.22",
|
||||
"support_line_width": "0.22",
|
||||
"support_top_z_distance": "0.1",
|
||||
"support_bottom_z_distance": "0.1"
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"type": "process",
|
||||
"name": "0.20mm Standard @Goofoo GP1",
|
||||
"inherits": "fdm_process_klipper_common",
|
||||
"from": "system",
|
||||
"setting_id": "zrscQBUxp6L7BlQ9",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": [
|
||||
"Goofoo GP1 0.4 nozzle"
|
||||
],
|
||||
"layer_height": "0.2",
|
||||
"accel_to_decel_enable": "0",
|
||||
"bridge_acceleration": "100%",
|
||||
"bridge_speed": "100",
|
||||
"brim_type": "no_brim",
|
||||
"gap_infill_speed": "250",
|
||||
"initial_layer_acceleration": "1000",
|
||||
"initial_layer_infill_speed": "100",
|
||||
"initial_layer_speed": "70",
|
||||
"inner_wall_speed": "250",
|
||||
"internal_bridge_speed": "200%",
|
||||
"internal_solid_infill_speed": "250",
|
||||
"outer_wall_acceleration": "5000",
|
||||
"outer_wall_speed": "100",
|
||||
"overhang_4_4_speed": "0",
|
||||
"reduce_crossing_wall": "1",
|
||||
"skirt_distance": "6",
|
||||
"skirt_height": "1",
|
||||
"skirt_loops": "1",
|
||||
"sparse_infill_pattern": "grid",
|
||||
"sparse_infill_speed": "250",
|
||||
"top_surface_acceleration": "5000",
|
||||
"top_surface_speed": "200",
|
||||
"travel_acceleration": "5000"
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"type": "process",
|
||||
"name": "0.20mm Standard @Goofoo TurboM",
|
||||
"inherits": "fdm_process_klipper_common",
|
||||
"from": "system",
|
||||
"setting_id": "LVXxUtAcYGCY6irA",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": [
|
||||
"Goofoo TurboM 0.4 nozzle"
|
||||
],
|
||||
"layer_height": "0.2",
|
||||
"bridge_acceleration": "2000",
|
||||
"bridge_speed": "60",
|
||||
"brim_type": "no_brim",
|
||||
"default_acceleration": "10000",
|
||||
"enable_support": "1",
|
||||
"exclude_object": "0",
|
||||
"gap_infill_speed": "200",
|
||||
"infill_anchor": "600%",
|
||||
"infill_combination": "1",
|
||||
"infill_wall_overlap": "15%",
|
||||
"initial_layer_acceleration": "1000",
|
||||
"initial_layer_line_width": "0.45",
|
||||
"initial_layer_speed": "60",
|
||||
"inner_wall_acceleration": "3000",
|
||||
"inner_wall_line_width": "0.42",
|
||||
"inner_wall_speed": "150",
|
||||
"internal_solid_infill_acceleration": "3000",
|
||||
"internal_solid_infill_line_width": "0.42",
|
||||
"internal_solid_infill_speed": "150",
|
||||
"line_width": "0.42",
|
||||
"min_skirt_length": "0",
|
||||
"minimum_sparse_infill_area": "70",
|
||||
"only_one_wall_top": "1",
|
||||
"outer_wall_acceleration": "2500",
|
||||
"outer_wall_line_width": "0.42",
|
||||
"outer_wall_speed": "100",
|
||||
"reduce_crossing_wall": "1",
|
||||
"skirt_distance": "6",
|
||||
"skirt_height": "1",
|
||||
"skirt_loops": "4",
|
||||
"small_perimeter_speed": "200",
|
||||
"sparse_infill_acceleration": "3000",
|
||||
"sparse_infill_line_width": "0.42",
|
||||
"sparse_infill_pattern": "grid",
|
||||
"sparse_infill_speed": "150",
|
||||
"support_base_pattern": "rectilinear",
|
||||
"support_interface_bottom_layers": "-1",
|
||||
"support_interface_pattern": "rectilinear",
|
||||
"support_interface_spacing": "0",
|
||||
"support_interface_speed": "100",
|
||||
"support_interface_top_layers": "3",
|
||||
"support_line_width": "0.35",
|
||||
"support_object_xy_distance": "0.21",
|
||||
"support_speed": "100",
|
||||
"thick_bridges": "1",
|
||||
"top_shell_layers": "6",
|
||||
"top_shell_thickness": "0",
|
||||
"top_surface_acceleration": "2500",
|
||||
"top_surface_line_width": "0.42",
|
||||
"top_surface_speed": "150",
|
||||
"travel_acceleration": "10000",
|
||||
"travel_speed": "200",
|
||||
"wall_generator": "classic",
|
||||
"wall_loops": "2",
|
||||
"wall_sequence": "outer wall/inner wall"
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"type": "process",
|
||||
"name": "0.20mm Standard @Goofoo Velo+",
|
||||
"inherits": "fdm_process_klipper_common",
|
||||
"from": "system",
|
||||
"setting_id": "iWjynhCcvbF8MWev",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": [
|
||||
"Goofoo Velo+ 0.4 nozzle",
|
||||
"Goofoo Velo+ Tool 0.4"
|
||||
],
|
||||
"layer_height": "0.2",
|
||||
"initial_layer_print_height": "0.2",
|
||||
"bottom_shell_layers": "3",
|
||||
"top_shell_layers": "6",
|
||||
"bridge_acceleration": "2000",
|
||||
"bridge_speed": "60",
|
||||
"brim_type": "no_brim",
|
||||
"default_acceleration": "10000",
|
||||
"enable_support": "1",
|
||||
"exclude_object": "0",
|
||||
"gap_infill_speed": "200",
|
||||
"infill_anchor": "600%",
|
||||
"infill_combination": "1",
|
||||
"infill_wall_overlap": "15%",
|
||||
"initial_layer_acceleration": "1000",
|
||||
"initial_layer_line_width": "0.45",
|
||||
"initial_layer_speed": "60",
|
||||
"inner_wall_acceleration": "3000",
|
||||
"inner_wall_line_width": "0.42",
|
||||
"inner_wall_speed": "150",
|
||||
"internal_solid_infill_acceleration": "3000",
|
||||
"internal_solid_infill_line_width": "0.42",
|
||||
"internal_solid_infill_speed": "150",
|
||||
"line_width": "0.42",
|
||||
"min_skirt_length": "0",
|
||||
"minimum_sparse_infill_area": "70",
|
||||
"only_one_wall_top": "1",
|
||||
"outer_wall_acceleration": "2500",
|
||||
"outer_wall_line_width": "0.42",
|
||||
"outer_wall_speed": "100",
|
||||
"reduce_crossing_wall": "1",
|
||||
"skirt_distance": "6",
|
||||
"skirt_height": "1",
|
||||
"skirt_loops": "4",
|
||||
"small_perimeter_speed": "200",
|
||||
"sparse_infill_acceleration": "3000",
|
||||
"sparse_infill_line_width": "0.42",
|
||||
"sparse_infill_pattern": "grid",
|
||||
"sparse_infill_speed": "150",
|
||||
"support_base_pattern": "rectilinear",
|
||||
"support_interface_bottom_layers": "-1",
|
||||
"support_interface_pattern": "rectilinear",
|
||||
"support_interface_spacing": "0",
|
||||
"support_interface_speed": "100",
|
||||
"support_interface_top_layers": "3",
|
||||
"support_line_width": "0.35",
|
||||
"support_object_xy_distance": "0.21",
|
||||
"support_speed": "100",
|
||||
"thick_bridges": "1",
|
||||
"top_shell_thickness": "0",
|
||||
"top_surface_acceleration": "2500",
|
||||
"top_surface_line_width": "0.42",
|
||||
"top_surface_speed": "150",
|
||||
"travel_acceleration": "10000",
|
||||
"travel_speed": "300",
|
||||
"wall_generator": "classic",
|
||||
"wall_loops": "2",
|
||||
"wall_sequence": "outer wall/inner wall"
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"type": "process",
|
||||
"name": "0.30mm Standard @Goofoo Velo+",
|
||||
"inherits": "fdm_process_klipper_common",
|
||||
"from": "system",
|
||||
"setting_id": "mi8ogYOYpqb2x60z",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": [
|
||||
"Goofoo Velo+ 0.6 nozzle"
|
||||
],
|
||||
"initial_layer_print_height": "0.3",
|
||||
"layer_height": "0.3"
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"type": "process",
|
||||
"name": "fdm_process_common",
|
||||
"from": "system",
|
||||
"instantiation": "false",
|
||||
"seam_position": "aligned",
|
||||
"resolution": "0.012",
|
||||
"elefant_foot_compensation": "0.2",
|
||||
"precise_outer_wall": "1",
|
||||
"ironing_type": "no ironing",
|
||||
"ironing_speed": "30",
|
||||
"ironing_flow": "10%",
|
||||
"ironing_spacing": "0.15",
|
||||
"reduce_crossing_wall": "1",
|
||||
"max_travel_detour_distance": "0",
|
||||
"detect_overhang_wall": "1",
|
||||
"wall_loops": "3",
|
||||
"top_surface_pattern": "monotonicline",
|
||||
"top_shell_layers": "5",
|
||||
"top_shell_thickness": "0.7",
|
||||
"bottom_surface_pattern": "monotonic",
|
||||
"bottom_shell_layers": "4",
|
||||
"bottom_shell_thickness": "0.5",
|
||||
"sparse_infill_density": "15%",
|
||||
"sparse_infill_pattern": "crosshatch",
|
||||
"infill_wall_overlap": "23%",
|
||||
"infill_direction": "45",
|
||||
"minimum_sparse_infill_area": "15",
|
||||
"infill_combination": "0",
|
||||
"ensure_vertical_shell_thickness": "1",
|
||||
"initial_layer_speed": "50",
|
||||
"initial_layer_infill_speed": "50",
|
||||
"initial_layer_travel_speed": "100%",
|
||||
"outer_wall_speed": "125",
|
||||
"inner_wall_speed": "250",
|
||||
"sparse_infill_speed": "250",
|
||||
"internal_solid_infill_speed": "200",
|
||||
"top_surface_speed": "150",
|
||||
"gap_infill_speed": "150",
|
||||
"support_speed": "125",
|
||||
"support_interface_speed": "80",
|
||||
"enable_overhang_speed": "1",
|
||||
"overhang_1_4_speed": "0",
|
||||
"overhang_2_4_speed": "50",
|
||||
"overhang_3_4_speed": "25",
|
||||
"overhang_4_4_speed": "15",
|
||||
"bridge_speed": "50",
|
||||
"travel_speed": "300",
|
||||
"default_acceleration": "2500",
|
||||
"outer_wall_acceleration": "500",
|
||||
"inner_wall_acceleration": "500",
|
||||
"bridge_acceleration": "500",
|
||||
"travel_acceleration": "2500",
|
||||
"default_jerk": "15",
|
||||
"outer_wall_jerk": "10",
|
||||
"inner_wall_jerk": "15",
|
||||
"infill_jerk": "15",
|
||||
"top_surface_jerk": "12",
|
||||
"initial_layer_jerk": "12",
|
||||
"travel_jerk": "20",
|
||||
"enable_support": "0",
|
||||
"support_type": "normal(auto)",
|
||||
"support_threshold_angle": "30",
|
||||
"support_on_build_plate_only": "0",
|
||||
"raft_layers": "0",
|
||||
"support_filament": "0",
|
||||
"support_interface_filament": "0",
|
||||
"support_top_z_distance": "0.2",
|
||||
"support_base_pattern": "default",
|
||||
"support_base_pattern_spacing": "2.5",
|
||||
"support_interface_top_layers": "2",
|
||||
"support_interface_bottom_layers": "2",
|
||||
"support_interface_spacing": "0.5",
|
||||
"support_object_xy_distance": "0.5",
|
||||
"bridge_no_support": "0",
|
||||
"independent_support_layer_height": "1",
|
||||
"tree_support_branch_angle": "30",
|
||||
"tree_support_wall_count": "0",
|
||||
"skirt_loops": "2",
|
||||
"skirt_distance": "3",
|
||||
"skirt_height": "1",
|
||||
"brim_width": "5",
|
||||
"brim_object_gap": "0.1",
|
||||
"enable_prime_tower": "0",
|
||||
"prime_tower_width": "60",
|
||||
"wipe_tower_no_sparse_layers": "0",
|
||||
"print_sequence": "by layer",
|
||||
"spiral_mode": "0",
|
||||
"reduce_infill_retraction": "1",
|
||||
"filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[0]}_{printer_model}.gcode"
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"type": "process",
|
||||
"name": "fdm_process_klipper_common",
|
||||
"inherits": "fdm_process_common",
|
||||
"from": "system",
|
||||
"instantiation": "false",
|
||||
"default_acceleration": "5000",
|
||||
"top_surface_acceleration": "3000",
|
||||
"travel_acceleration": "7000",
|
||||
"inner_wall_acceleration": "5000",
|
||||
"outer_wall_acceleration": "3000",
|
||||
"initial_layer_acceleration": "500",
|
||||
"initial_layer_speed": "50",
|
||||
"initial_layer_infill_speed": "105",
|
||||
"outer_wall_speed": "120",
|
||||
"inner_wall_speed": "200",
|
||||
"internal_solid_infill_speed": "200",
|
||||
"top_surface_speed": "100",
|
||||
"gap_infill_speed": "100",
|
||||
"sparse_infill_speed": "200",
|
||||
"travel_speed": "350",
|
||||
"exclude_object": "1"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "OrcaFilamentLibrary",
|
||||
"version": "02.04.00.15",
|
||||
"version": "02.04.00.16",
|
||||
"force_update": "0",
|
||||
"description": "Orca Filament Library",
|
||||
"filament_list": [
|
||||
@@ -1396,6 +1396,10 @@
|
||||
"name": "addnorth ABS rABS",
|
||||
"sub_path": "filament/addnorth/BBL/addnorth ABS rABS.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo ABS @base",
|
||||
"sub_path": "filament/Goofoo/Goofoo ABS @base.json"
|
||||
},
|
||||
{
|
||||
"name": "NIT ABS @System",
|
||||
"sub_path": "filament/NIT/NIT ABS @System.json"
|
||||
@@ -1480,6 +1484,10 @@
|
||||
"name": "Elegoo ASA-CF @base",
|
||||
"sub_path": "filament/Elegoo/Elegoo ASA-CF @base.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo ASA @base",
|
||||
"sub_path": "filament/Goofoo/Goofoo ASA @base.json"
|
||||
},
|
||||
{
|
||||
"name": "Overture ASA @BBL X1",
|
||||
"sub_path": "filament/Overture/BBL/Overture ASA @BBL X1.json"
|
||||
@@ -1636,10 +1644,22 @@
|
||||
"name": "addnorth PVDF Adamant S1",
|
||||
"sub_path": "filament/addnorth/BBL/addnorth PVDF Adamant S1.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo PA @base",
|
||||
"sub_path": "filament/Goofoo/Goofoo PA @base.json"
|
||||
},
|
||||
{
|
||||
"name": "addnorth PA-CF Adura X",
|
||||
"sub_path": "filament/addnorth/BBL/addnorth PA-CF Adura X.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo PA-CF @base",
|
||||
"sub_path": "filament/Goofoo/Goofoo PA-CF @base.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo PA-GF10 @base",
|
||||
"sub_path": "filament/Goofoo/Goofoo PA-GF10 @base.json"
|
||||
},
|
||||
{
|
||||
"name": "INSLOGIC PA6-66 @BBL P2S",
|
||||
"sub_path": "filament/INSLOGIC/BBL/INSLOGIC PA6-66 @BBL P2S.json"
|
||||
@@ -1712,6 +1732,10 @@
|
||||
"name": "COEX PCTG PRIME @System",
|
||||
"sub_path": "filament/COEX/COEX PCTG PRIME @System.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo PE @base",
|
||||
"sub_path": "filament/Goofoo/Goofoo PE @base.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic PE-CF @System",
|
||||
"sub_path": "filament/Generic PE-CF @System.json"
|
||||
@@ -2176,6 +2200,10 @@
|
||||
"name": "addnorth PETG rPETG Matte",
|
||||
"sub_path": "filament/addnorth/BBL/addnorth PETG rPETG Matte.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo PETG @base",
|
||||
"sub_path": "filament/Goofoo/Goofoo PETG @base.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic PETG HF @System",
|
||||
"sub_path": "filament/Generic PETG HF @System.json"
|
||||
@@ -2236,6 +2264,10 @@
|
||||
"name": "eSUN PETG @System",
|
||||
"sub_path": "filament/eSUN/eSUN PETG @System.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo PHA @base",
|
||||
"sub_path": "filament/Goofoo/Goofoo PHA @base.json"
|
||||
},
|
||||
{
|
||||
"name": "AliZ PLA @P1-X1",
|
||||
"sub_path": "filament/AliZ/BBL/AliZ PLA @P1-X1.json"
|
||||
@@ -3120,6 +3152,10 @@
|
||||
"name": "addnorth PLA rPLA RE-ADD",
|
||||
"sub_path": "filament/addnorth/BBL/addnorth PLA rPLA RE-ADD.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo PLA @base",
|
||||
"sub_path": "filament/Goofoo/Goofoo PLA @base.json"
|
||||
},
|
||||
{
|
||||
"name": "addnorth PLA X-PLA High Speed",
|
||||
"sub_path": "filament/addnorth/BBL/addnorth PLA X-PLA High Speed.json"
|
||||
@@ -3136,6 +3172,10 @@
|
||||
"name": "addnorth PLA-CF Carbon Fiber",
|
||||
"sub_path": "filament/addnorth/BBL/addnorth PLA-CF Carbon Fiber.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo PLA-CF @base",
|
||||
"sub_path": "filament/Goofoo/Goofoo PLA-CF @base.json"
|
||||
},
|
||||
{
|
||||
"name": "INSLOGIC PLA Pro @BBL P2S",
|
||||
"sub_path": "filament/INSLOGIC/BBL/INSLOGIC PLA Pro @BBL P2S.json"
|
||||
@@ -3820,6 +3860,10 @@
|
||||
"name": "FILL3D PPCF @System",
|
||||
"sub_path": "filament/FILL3D/FILL3D PPCF @System.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo PP @base",
|
||||
"sub_path": "filament/Goofoo/Goofoo PP @base.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic PP-CF @System",
|
||||
"sub_path": "filament/Generic PP-CF @System.json"
|
||||
@@ -3840,6 +3884,10 @@
|
||||
"name": "Bambu PVA @System",
|
||||
"sub_path": "filament/Bambu/Bambu PVA @System.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo PVA @base",
|
||||
"sub_path": "filament/Goofoo/Goofoo PVA @base.json"
|
||||
},
|
||||
{
|
||||
"name": "FDplast SBS @System",
|
||||
"sub_path": "filament/FDplast/FDplast SBS @System.json"
|
||||
@@ -4112,6 +4160,14 @@
|
||||
"name": "addnorth TPU Pro Matte 95A",
|
||||
"sub_path": "filament/addnorth/BBL/addnorth TPU Pro Matte 95A.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo 90A TPU @base",
|
||||
"sub_path": "filament/Goofoo/Goofoo 90A TPU @base.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo 95A TPU @base",
|
||||
"sub_path": "filament/Goofoo/Goofoo 95A TPU @base.json"
|
||||
},
|
||||
{
|
||||
"name": "Overture TPU @BBL A1",
|
||||
"sub_path": "filament/Overture/BBL/Overture TPU @BBL A1.json"
|
||||
@@ -4156,10 +4212,30 @@
|
||||
"name": "Overture TPU @System",
|
||||
"sub_path": "filament/Overture/Overture TPU @System.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo ABS @System",
|
||||
"sub_path": "filament/Goofoo/Goofoo ABS @System.json"
|
||||
},
|
||||
{
|
||||
"name": "Elegoo ASA-CF @System",
|
||||
"sub_path": "filament/Elegoo/Elegoo ASA-CF @System.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo ASA @System",
|
||||
"sub_path": "filament/Goofoo/Goofoo ASA @System.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo PA @System",
|
||||
"sub_path": "filament/Goofoo/Goofoo PA @System.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo PA-CF @System",
|
||||
"sub_path": "filament/Goofoo/Goofoo PA-CF @System.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo PA-GF10 @System",
|
||||
"sub_path": "filament/Goofoo/Goofoo PA-GF10 @System.json"
|
||||
},
|
||||
{
|
||||
"name": "Elegoo PC-FR @System",
|
||||
"sub_path": "filament/Elegoo/Elegoo PC-FR @System.json"
|
||||
@@ -4176,6 +4252,14 @@
|
||||
"name": "COEX PCTG PRIME @BBL A1M 0.8 nozzle",
|
||||
"sub_path": "filament/COEX/BBL/COEX PCTG PRIME @BBL A1M 0.8 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo PE @System",
|
||||
"sub_path": "filament/Goofoo/Goofoo PE @System.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo PE-CF @base",
|
||||
"sub_path": "filament/Goofoo/Goofoo PE-CF @base.json"
|
||||
},
|
||||
{
|
||||
"name": "AliZ PETG-CF @P1-X1",
|
||||
"sub_path": "filament/AliZ/BBL/AliZ PETG-CF @P1-X1.json"
|
||||
@@ -4236,10 +4320,26 @@
|
||||
"name": "Fiberon PETG-ESD @BBL X1",
|
||||
"sub_path": "filament/Polymaker/BBL/Fiberon PETG-ESD @BBL X1.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo PETG @System",
|
||||
"sub_path": "filament/Goofoo/Goofoo PETG @System.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo PETG HF @base",
|
||||
"sub_path": "filament/Goofoo/Goofoo PETG HF @base.json"
|
||||
},
|
||||
{
|
||||
"name": "addnorth PETG-CF Rigid X",
|
||||
"sub_path": "filament/addnorth/BBL/addnorth PETG-CF Rigid X.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo PETG-CF @base",
|
||||
"sub_path": "filament/Goofoo/Goofoo PETG-CF @base.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo PHA @System",
|
||||
"sub_path": "filament/Goofoo/Goofoo PHA @System.json"
|
||||
},
|
||||
{
|
||||
"name": "COEX PLA @BBL A1M 0.2 nozzle",
|
||||
"sub_path": "filament/COEX/BBL/COEX PLA @BBL A1M 0.2 nozzle.json"
|
||||
@@ -4304,6 +4404,14 @@
|
||||
"name": "Elegoo Rapid PLA+ @System",
|
||||
"sub_path": "filament/Elegoo/Elegoo Rapid PLA+ @System.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo PLA @System",
|
||||
"sub_path": "filament/Goofoo/Goofoo PLA @System.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo PLA-CF @System",
|
||||
"sub_path": "filament/Goofoo/Goofoo PLA-CF @System.json"
|
||||
},
|
||||
{
|
||||
"name": "Numakers PLA+ @BBL A1",
|
||||
"sub_path": "filament/Numakers/BBL/Numakers PLA+ @BBL A1.json"
|
||||
@@ -5188,9 +5296,61 @@
|
||||
"name": "COEX PLA+Silk @System",
|
||||
"sub_path": "filament/COEX/COEX PLA+Silk @System.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo PLA Silk @base",
|
||||
"sub_path": "filament/Goofoo/Goofoo PLA Silk @base.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo PP @System",
|
||||
"sub_path": "filament/Goofoo/Goofoo PP @System.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo PP-CF @base",
|
||||
"sub_path": "filament/Goofoo/Goofoo PP-CF @base.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo PP-GF @base",
|
||||
"sub_path": "filament/Goofoo/Goofoo PP-GF @base.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo PVA @System",
|
||||
"sub_path": "filament/Goofoo/Goofoo PVA @System.json"
|
||||
},
|
||||
{
|
||||
"name": "Elegoo Rapid TPU 95A @System",
|
||||
"sub_path": "filament/Elegoo/Elegoo Rapid TPU 95A @System.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo 90A TPU @System",
|
||||
"sub_path": "filament/Goofoo/Goofoo 90A TPU @System.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo 95A TPU @System",
|
||||
"sub_path": "filament/Goofoo/Goofoo 95A TPU @System.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo PE-CF @System",
|
||||
"sub_path": "filament/Goofoo/Goofoo PE-CF @System.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo PETG HF @System",
|
||||
"sub_path": "filament/Goofoo/Goofoo PETG HF @System.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo PETG-CF @System",
|
||||
"sub_path": "filament/Goofoo/Goofoo PETG-CF @System.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo PLA Silk @System",
|
||||
"sub_path": "filament/Goofoo/Goofoo PLA Silk @System.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo PP-CF @System",
|
||||
"sub_path": "filament/Goofoo/Goofoo PP-CF @System.json"
|
||||
},
|
||||
{
|
||||
"name": "Goofoo PP-GF @System",
|
||||
"sub_path": "filament/Goofoo/Goofoo PP-GF @System.json"
|
||||
}
|
||||
],
|
||||
"process_list": [],
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo 90A TPU @System",
|
||||
"inherits": "Goofoo 90A TPU @base",
|
||||
"from": "system",
|
||||
"setting_id": "tjq76AQe9SCzZVo6",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": []
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo 90A TPU @base",
|
||||
"inherits": "Generic TPU @System",
|
||||
"from": "system",
|
||||
"filament_id": "OFlVOXX3",
|
||||
"instantiation": "false",
|
||||
"fan_cooling_layer_time": [
|
||||
"60"
|
||||
],
|
||||
"fan_min_speed": [
|
||||
"35"
|
||||
],
|
||||
"filament_max_volumetric_speed": [
|
||||
"2"
|
||||
],
|
||||
"hot_plate_temp": [
|
||||
"40"
|
||||
],
|
||||
"hot_plate_temp_initial_layer": [
|
||||
"40"
|
||||
],
|
||||
"nozzle_temperature": [
|
||||
"220"
|
||||
],
|
||||
"nozzle_temperature_initial_layer": [
|
||||
"220"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
"0"
|
||||
],
|
||||
"slow_down_layer_time": [
|
||||
"5"
|
||||
],
|
||||
"filament_vendor": [
|
||||
"Goofoo"
|
||||
],
|
||||
"filament_type": [
|
||||
"TPU"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo 95A TPU @System",
|
||||
"inherits": "Goofoo 95A TPU @base",
|
||||
"from": "system",
|
||||
"setting_id": "2YTvrE1gu1yHHTNM",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": []
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo 95A TPU @base",
|
||||
"inherits": "Generic TPU @System",
|
||||
"from": "system",
|
||||
"filament_id": "OFk50t30",
|
||||
"instantiation": "false",
|
||||
"fan_cooling_layer_time": [
|
||||
"60"
|
||||
],
|
||||
"fan_min_speed": [
|
||||
"35"
|
||||
],
|
||||
"hot_plate_temp": [
|
||||
"40"
|
||||
],
|
||||
"hot_plate_temp_initial_layer": [
|
||||
"40"
|
||||
],
|
||||
"nozzle_temperature": [
|
||||
"220"
|
||||
],
|
||||
"nozzle_temperature_initial_layer": [
|
||||
"220"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
"0"
|
||||
],
|
||||
"slow_down_layer_time": [
|
||||
"5"
|
||||
],
|
||||
"filament_vendor": [
|
||||
"Goofoo"
|
||||
],
|
||||
"filament_type": [
|
||||
"TPU"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo ABS @System",
|
||||
"inherits": "Goofoo ABS @base",
|
||||
"from": "system",
|
||||
"setting_id": "UeINgPGH4eaSJinC",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": []
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo ABS @base",
|
||||
"inherits": "Generic ABS @System",
|
||||
"from": "system",
|
||||
"filament_id": "OFd3PmW4",
|
||||
"instantiation": "false",
|
||||
"close_fan_the_first_x_layers": [
|
||||
"1"
|
||||
],
|
||||
"hot_plate_temp": [
|
||||
"110"
|
||||
],
|
||||
"hot_plate_temp_initial_layer": [
|
||||
"110"
|
||||
],
|
||||
"filament_vendor": [
|
||||
"Goofoo"
|
||||
],
|
||||
"filament_type": [
|
||||
"ABS"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo ASA @System",
|
||||
"inherits": "Goofoo ASA @base",
|
||||
"from": "system",
|
||||
"setting_id": "9ohqNl44uygTPfS9",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": []
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo ASA @base",
|
||||
"inherits": "Generic ASA @System",
|
||||
"from": "system",
|
||||
"filament_id": "OFyncHIG",
|
||||
"instantiation": "false",
|
||||
"filament_flow_ratio": [
|
||||
"0.93"
|
||||
],
|
||||
"hot_plate_temp_initial_layer": [
|
||||
"100"
|
||||
],
|
||||
"filament_vendor": [
|
||||
"Goofoo"
|
||||
],
|
||||
"filament_type": [
|
||||
"ASA"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo PA @System",
|
||||
"inherits": "Goofoo PA @base",
|
||||
"from": "system",
|
||||
"setting_id": "1ximJVgZZfGmGyzD",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": []
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo PA @base",
|
||||
"inherits": "Generic PA @System",
|
||||
"from": "system",
|
||||
"filament_id": "OFc2x7Ly",
|
||||
"instantiation": "false",
|
||||
"filament_max_volumetric_speed": [
|
||||
"12"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
"1"
|
||||
],
|
||||
"filament_vendor": [
|
||||
"Goofoo"
|
||||
],
|
||||
"filament_type": [
|
||||
"PA"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo PA-CF @System",
|
||||
"inherits": "Goofoo PA-CF @base",
|
||||
"from": "system",
|
||||
"setting_id": "US4zznMNti8tLUHG",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": []
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo PA-CF @base",
|
||||
"inherits": "Generic PA-CF @System",
|
||||
"from": "system",
|
||||
"filament_id": "OF7skcRj",
|
||||
"instantiation": "false",
|
||||
"filament_max_volumetric_speed": [
|
||||
"12"
|
||||
],
|
||||
"filament_vendor": [
|
||||
"Goofoo"
|
||||
],
|
||||
"filament_type": [
|
||||
"PA-CF"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo PA-GF10 @System",
|
||||
"inherits": "Goofoo PA-GF10 @base",
|
||||
"from": "system",
|
||||
"setting_id": "Lea3hf2lQG0M9eTC",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": []
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo PA-GF10 @base",
|
||||
"inherits": "Generic PA-CF @System",
|
||||
"from": "system",
|
||||
"filament_id": "OFtjqw2h",
|
||||
"instantiation": "false",
|
||||
"nozzle_temperature": [
|
||||
"230"
|
||||
],
|
||||
"nozzle_temperature_initial_layer": [
|
||||
"230"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
"1"
|
||||
],
|
||||
"filament_vendor": [
|
||||
"Goofoo"
|
||||
],
|
||||
"filament_type": [
|
||||
"PA-GF"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo PE @System",
|
||||
"inherits": "Goofoo PE @base",
|
||||
"from": "system",
|
||||
"setting_id": "muNxvgr0vdavMaOJ",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": []
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo PE @base",
|
||||
"inherits": "Generic PE @System",
|
||||
"from": "system",
|
||||
"filament_id": "OFAln723",
|
||||
"instantiation": "false",
|
||||
"filament_vendor": [
|
||||
"Goofoo"
|
||||
],
|
||||
"filament_type": [
|
||||
"PE"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo PE-CF @System",
|
||||
"inherits": "Goofoo PE-CF @base",
|
||||
"from": "system",
|
||||
"setting_id": "58fMbg6AClvalw5o",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": []
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo PE-CF @base",
|
||||
"inherits": "Generic PE-CF @System",
|
||||
"from": "system",
|
||||
"filament_id": "OF4Ll1Sk",
|
||||
"instantiation": "false",
|
||||
"filament_vendor": [
|
||||
"Goofoo"
|
||||
],
|
||||
"filament_type": [
|
||||
"PE-CF"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo PETG @System",
|
||||
"inherits": "Goofoo PETG @base",
|
||||
"from": "system",
|
||||
"setting_id": "a9OKgJeoJgzfZiAB",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": []
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo PETG @base",
|
||||
"inherits": "Generic PETG @System",
|
||||
"from": "system",
|
||||
"filament_id": "OFHdIbiu",
|
||||
"instantiation": "false",
|
||||
"hot_plate_temp": [
|
||||
"80"
|
||||
],
|
||||
"hot_plate_temp_initial_layer": [
|
||||
"80"
|
||||
],
|
||||
"nozzle_temperature": [
|
||||
"240"
|
||||
],
|
||||
"nozzle_temperature_initial_layer": [
|
||||
"240"
|
||||
],
|
||||
"filament_vendor": [
|
||||
"Goofoo"
|
||||
],
|
||||
"filament_type": [
|
||||
"PETG"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo PETG HF @System",
|
||||
"inherits": "Goofoo PETG HF @base",
|
||||
"from": "system",
|
||||
"setting_id": "eMjhTRf5qPeeqSgk",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": []
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo PETG HF @base",
|
||||
"inherits": "Generic PETG HF @System",
|
||||
"from": "system",
|
||||
"filament_id": "OF5bjxyx",
|
||||
"instantiation": "false",
|
||||
"filament_vendor": [
|
||||
"Goofoo"
|
||||
],
|
||||
"filament_type": [
|
||||
"PETG HF"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo PETG-CF @System",
|
||||
"inherits": "Goofoo PETG-CF @base",
|
||||
"from": "system",
|
||||
"setting_id": "ULNFo6zjgjc5MEIa",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": []
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo PETG-CF @base",
|
||||
"inherits": "Generic PETG-CF @System",
|
||||
"from": "system",
|
||||
"filament_id": "OFo6Ff38",
|
||||
"instantiation": "false",
|
||||
"filament_vendor": [
|
||||
"Goofoo"
|
||||
],
|
||||
"filament_type": [
|
||||
"PETG-CF"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo PHA @System",
|
||||
"inherits": "Goofoo PHA @base",
|
||||
"from": "system",
|
||||
"setting_id": "cfdeMyqld72Lqh38",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": []
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo PHA @base",
|
||||
"inherits": "Generic PHA @System",
|
||||
"from": "system",
|
||||
"filament_id": "OFicmNhN",
|
||||
"instantiation": "false",
|
||||
"filament_vendor": [
|
||||
"Goofoo"
|
||||
],
|
||||
"filament_type": [
|
||||
"PHA"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo PLA @System",
|
||||
"inherits": "Goofoo PLA @base",
|
||||
"from": "system",
|
||||
"setting_id": "UFWRE1b3706Cu1Iq",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": []
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo PLA @base",
|
||||
"inherits": "Generic PLA @System",
|
||||
"from": "system",
|
||||
"filament_id": "OFoq0Cmt",
|
||||
"instantiation": "false",
|
||||
"hot_plate_temp": [
|
||||
"40"
|
||||
],
|
||||
"hot_plate_temp_initial_layer": [
|
||||
"40"
|
||||
],
|
||||
"nozzle_temperature": [
|
||||
"230"
|
||||
],
|
||||
"nozzle_temperature_initial_layer": [
|
||||
"230"
|
||||
],
|
||||
"filament_vendor": [
|
||||
"Goofoo"
|
||||
],
|
||||
"filament_type": [
|
||||
"PLA"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo PLA Silk @System",
|
||||
"inherits": "Goofoo PLA Silk @base",
|
||||
"from": "system",
|
||||
"setting_id": "2pW2NEoY4hFCkNun",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": []
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo PLA Silk @base",
|
||||
"inherits": "Generic PLA Silk @System",
|
||||
"from": "system",
|
||||
"filament_id": "OFpta2gc",
|
||||
"instantiation": "false",
|
||||
"filament_vendor": [
|
||||
"Goofoo"
|
||||
],
|
||||
"filament_type": [
|
||||
"PLA Silk"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo PLA-CF @System",
|
||||
"inherits": "Goofoo PLA-CF @base",
|
||||
"from": "system",
|
||||
"setting_id": "R0UMDUPY9ryat8gQ",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": []
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo PLA-CF @base",
|
||||
"inherits": "Generic PLA-CF @System",
|
||||
"from": "system",
|
||||
"filament_id": "OFMNABGJ",
|
||||
"instantiation": "false",
|
||||
"filament_flow_ratio": [
|
||||
"0.98"
|
||||
],
|
||||
"hot_plate_temp": [
|
||||
"40"
|
||||
],
|
||||
"hot_plate_temp_initial_layer": [
|
||||
"40"
|
||||
],
|
||||
"nozzle_temperature": [
|
||||
"240"
|
||||
],
|
||||
"nozzle_temperature_initial_layer": [
|
||||
"240"
|
||||
],
|
||||
"filament_vendor": [
|
||||
"Goofoo"
|
||||
],
|
||||
"filament_type": [
|
||||
"PLA-CF"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo PP @System",
|
||||
"inherits": "Goofoo PP @base",
|
||||
"from": "system",
|
||||
"setting_id": "TaB8JUtjJWgRcNrv",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": []
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo PP @base",
|
||||
"inherits": "Generic PP @System",
|
||||
"from": "system",
|
||||
"filament_id": "OFlqlUSx",
|
||||
"instantiation": "false",
|
||||
"filament_vendor": [
|
||||
"Goofoo"
|
||||
],
|
||||
"filament_type": [
|
||||
"PP"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo PP-CF @System",
|
||||
"inherits": "Goofoo PP-CF @base",
|
||||
"from": "system",
|
||||
"setting_id": "uHocbocT6flJhlBz",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": []
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo PP-CF @base",
|
||||
"inherits": "Generic PP-CF @System",
|
||||
"from": "system",
|
||||
"filament_id": "OFUUiuIV",
|
||||
"instantiation": "false",
|
||||
"filament_vendor": [
|
||||
"Goofoo"
|
||||
],
|
||||
"filament_type": [
|
||||
"PP-CF"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo PP-GF @System",
|
||||
"inherits": "Goofoo PP-GF @base",
|
||||
"from": "system",
|
||||
"setting_id": "mns8gICV0aNwJ02L",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": []
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo PP-GF @base",
|
||||
"inherits": "Generic PP-GF @System",
|
||||
"from": "system",
|
||||
"filament_id": "OFaOhzl8",
|
||||
"instantiation": "false",
|
||||
"filament_vendor": [
|
||||
"Goofoo"
|
||||
],
|
||||
"filament_type": [
|
||||
"PP-GF"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo PVA @System",
|
||||
"inherits": "Goofoo PVA @base",
|
||||
"from": "system",
|
||||
"setting_id": "tUXbv31nYkXJ87OT",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": []
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Goofoo PVA @base",
|
||||
"inherits": "Generic PVA @System",
|
||||
"from": "system",
|
||||
"filament_id": "OF86rkt6",
|
||||
"instantiation": "false",
|
||||
"filament_vendor": [
|
||||
"Goofoo"
|
||||
],
|
||||
"filament_type": [
|
||||
"PVA"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,410 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.12"
|
||||
#
|
||||
# [tool.orcaslicer.plugin]
|
||||
# id = "reload-local-bundle-demo"
|
||||
# name = "Reload Local Bundle Demo"
|
||||
# description = "Manages local filament, process, and printer profiles with live bundle reload."
|
||||
# author = "OrcaSlicer contributors"
|
||||
# version = "0.1.0"
|
||||
# ///
|
||||
"""A small interactive consumer of ``orca.host.preset.bundles.reload_local``."""
|
||||
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import orca
|
||||
|
||||
|
||||
BUNDLE_ID = "reload-local-bundle-demo"
|
||||
STATE_FILE = ".reload-local-bundle-demo-state.json"
|
||||
DEMO_PRESETS = (
|
||||
{
|
||||
"id": "filament",
|
||||
"name": "Local Bundle Demo Filament",
|
||||
"kind": "Filament",
|
||||
"type": "filament",
|
||||
"directory": "filament",
|
||||
"collection": "filaments",
|
||||
"setting_key": "filament_settings_id",
|
||||
},
|
||||
{
|
||||
"id": "process",
|
||||
"name": "Local Bundle Demo Process",
|
||||
"kind": "Process",
|
||||
"type": "process",
|
||||
"directory": "process",
|
||||
"collection": "prints",
|
||||
"setting_key": "print_settings_id",
|
||||
},
|
||||
{
|
||||
"id": "printer",
|
||||
"name": "Local Bundle Demo Printer",
|
||||
"kind": "Printer",
|
||||
"type": "machine",
|
||||
"directory": "machine",
|
||||
"collection": "printers",
|
||||
"setting_key": "printer_settings_id",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _data_dir():
|
||||
parts = os.path.abspath(__file__).replace("\\", "/").split("/")
|
||||
if "orca_plugins" not in parts:
|
||||
raise RuntimeError("Install this file as a local OrcaSlicer plugin")
|
||||
return "/".join(parts[: parts.index("orca_plugins")])
|
||||
|
||||
|
||||
def _folder_from_preset_file(path):
|
||||
parts = os.path.normpath(path or "").replace("\\", "/").split("/")
|
||||
try:
|
||||
return parts[parts.index("user") + 1]
|
||||
except (ValueError, IndexError):
|
||||
return None
|
||||
|
||||
|
||||
def _bundle_dir(bundle):
|
||||
for collection in (bundle.filaments, bundle.prints, bundle.printers):
|
||||
for index in range(collection.size()):
|
||||
folder = _folder_from_preset_file(collection.preset(index).file)
|
||||
if folder:
|
||||
return os.path.join(_data_dir(), "user", folder, "_local", BUNDLE_ID)
|
||||
return os.path.join(_data_dir(), "user", "default", "_local", BUNDLE_ID)
|
||||
|
||||
|
||||
def _source_path(bundle, item):
|
||||
return os.path.join(_bundle_dir(bundle), item["directory"], item["name"] + ".json")
|
||||
|
||||
|
||||
def _state_path(bundle):
|
||||
return os.path.join(_bundle_dir(bundle), STATE_FILE)
|
||||
|
||||
|
||||
def _write_json_atomic(path, value):
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
descriptor, temporary = tempfile.mkstemp(
|
||||
prefix="." + os.path.basename(path) + ".", suffix=".tmp", dir=os.path.dirname(path)
|
||||
)
|
||||
try:
|
||||
with os.fdopen(descriptor, "w", encoding="utf-8") as stream:
|
||||
json.dump(value, stream, ensure_ascii=False, indent=2)
|
||||
stream.write("\n")
|
||||
os.replace(temporary, path)
|
||||
except Exception:
|
||||
try:
|
||||
os.unlink(temporary)
|
||||
except OSError:
|
||||
pass
|
||||
raise
|
||||
|
||||
|
||||
def _remove_file(path):
|
||||
try:
|
||||
os.unlink(path)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
|
||||
def _read_state(bundle):
|
||||
try:
|
||||
with open(_state_path(bundle), "r", encoding="utf-8") as stream:
|
||||
state = json.load(stream)
|
||||
except FileNotFoundError:
|
||||
return {"profiles": {}}
|
||||
except (OSError, ValueError) as error:
|
||||
raise RuntimeError("The demo state file cannot be read: %s" % error)
|
||||
if not isinstance(state, dict) or not isinstance(state.get("profiles"), dict):
|
||||
raise RuntimeError("The demo state file has an invalid shape")
|
||||
return state
|
||||
|
||||
|
||||
def _profile_state(state, item):
|
||||
profiles = state["profiles"]
|
||||
profile = profiles.get(item["id"])
|
||||
if profile is None:
|
||||
profile = {}
|
||||
profiles[item["id"]] = profile
|
||||
if not isinstance(profile, dict):
|
||||
raise RuntimeError("The demo state for %s has an invalid shape" % item["name"])
|
||||
profile["enabled"] = profile.get("enabled") is True
|
||||
profile["deleted"] = profile.get("deleted") is True
|
||||
if profile["deleted"]:
|
||||
profile["enabled"] = False
|
||||
profile["parent_name"] = str(profile.get("parent_name") or "")
|
||||
return profile
|
||||
|
||||
|
||||
def _collection(bundle, item):
|
||||
return getattr(bundle, item["collection"])
|
||||
|
||||
|
||||
def _target_name(item):
|
||||
return "_local/%s/%s" % (BUNDLE_ID, item["name"])
|
||||
|
||||
|
||||
def _parent_name(bundle, item):
|
||||
collection = _collection(bundle, item)
|
||||
own_names = {
|
||||
_target_name(candidate)
|
||||
for candidate in DEMO_PRESETS
|
||||
if candidate["collection"] == item["collection"]
|
||||
}
|
||||
candidates = [collection.selected_preset()]
|
||||
candidates.extend(collection.preset(index) for index in range(collection.size()))
|
||||
for preset in candidates:
|
||||
if preset.name not in own_names:
|
||||
return preset.name
|
||||
raise RuntimeError("A parent %s preset is required for this demo" % item["kind"].lower())
|
||||
|
||||
|
||||
def _parent_is_live(bundle, item, name):
|
||||
return bool(name) and name not in {_target_name(item)} and _collection(bundle, item).find_preset(name) is not None
|
||||
|
||||
|
||||
def _profile(item, parent_name):
|
||||
return {
|
||||
"type": item["type"],
|
||||
"name": item["name"],
|
||||
"from": "User",
|
||||
"instantiation": "true",
|
||||
"version": "1.0.0",
|
||||
"inherits": parent_name,
|
||||
item["setting_key"]: item["name"],
|
||||
}
|
||||
|
||||
|
||||
def _reconcile(bundle, state):
|
||||
for item in DEMO_PRESETS:
|
||||
profile = _profile_state(state, item)
|
||||
if profile["enabled"] and not profile["deleted"] and not _parent_is_live(bundle, item, profile["parent_name"]):
|
||||
profile["parent_name"] = _parent_name(bundle, item)
|
||||
|
||||
for item in DEMO_PRESETS:
|
||||
profile = _profile_state(state, item)
|
||||
path = _source_path(bundle, item)
|
||||
if profile["enabled"] and not profile["deleted"]:
|
||||
_write_json_atomic(path, _profile(item, profile["parent_name"]))
|
||||
else:
|
||||
_remove_file(path)
|
||||
_write_json_atomic(
|
||||
os.path.join(_bundle_dir(bundle), "bundle_metadata.json"),
|
||||
{"id": BUNDLE_ID, "name": "Reload Local Bundle Demo", "version": "0.1.0"},
|
||||
)
|
||||
_write_json_atomic(_state_path(bundle), state)
|
||||
orca.host.preset.bundles.reload_local(BUNDLE_ID)
|
||||
|
||||
|
||||
def _state_payload(bundle, state, message="", error=""):
|
||||
items = []
|
||||
for item in DEMO_PRESETS:
|
||||
profile = _profile_state(state, item)
|
||||
items.append(
|
||||
{
|
||||
"id": item["id"],
|
||||
"name": item["name"],
|
||||
"kind": item["kind"],
|
||||
"enabled": profile["enabled"],
|
||||
"deleted": profile["deleted"],
|
||||
"loaded": _collection(bundle, item).find_preset(_target_name(item)) is not None,
|
||||
}
|
||||
)
|
||||
return {
|
||||
"command": "state",
|
||||
"items": items,
|
||||
"bundles": orca.host.preset.bundles.list_ids(),
|
||||
"message": message,
|
||||
"error": error,
|
||||
}
|
||||
|
||||
|
||||
PAGE = r"""<!doctype html>
|
||||
<html><head><meta charset="utf-8"><style>
|
||||
:root { color-scheme: light dark; font: 14px system-ui, sans-serif; }
|
||||
body { margin: 0; padding: 16px; background: Canvas; color: CanvasText; }
|
||||
.profiles { display: grid; gap: 9px; }
|
||||
.profile { border: 1px solid rgba(127, 127, 127, .4); border-radius: 8px; padding: 11px; }
|
||||
.top, .bottom { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
||||
.top { margin-bottom: 10px; }
|
||||
.name { font-weight: 650; }
|
||||
.kind, .message { color: GrayText; font-size: 12px; }
|
||||
.status { border-radius: 999px; padding: 3px 8px; font-size: 12px; white-space: nowrap; }
|
||||
.loaded { background: #0b8f5a; color: white; }
|
||||
.disabled, .deleted { background: rgba(127, 127, 127, .25); }
|
||||
.missing { background: #be4b0a; color: white; }
|
||||
.toggle { display: inline-flex; align-items: center; gap: 7px; cursor: pointer; }
|
||||
.toggle input { position: absolute; opacity: 0; pointer-events: none; }
|
||||
.switch { width: 34px; height: 20px; border-radius: 999px; background: rgba(127, 127, 127, .45); position: relative; }
|
||||
.switch::after { content: ""; position: absolute; top: 3px; left: 3px; width: 14px; height: 14px; border-radius: 50%; background: white; transition: transform .15s ease; }
|
||||
.toggle input:checked + .switch { background: #008f7a; }
|
||||
.toggle input:checked + .switch::after { transform: translateX(14px); }
|
||||
.toggle input:disabled + .switch, button:disabled { opacity: .5; cursor: wait; }
|
||||
button { border: 0; border-radius: 6px; padding: 6px 10px; font: inherit; cursor: pointer; background: #b64036; color: white; }
|
||||
button.secondary { background: rgba(127, 127, 127, .25); color: CanvasText; }
|
||||
.actions { display: flex; justify-content: flex-end; margin-top: 14px; }
|
||||
.message { min-height: 18px; margin-top: 10px; }
|
||||
.error { color: #d13c32; }
|
||||
.help { margin: 0 0 12px; color: GrayText; font-size: 12px; line-height: 1.4; }
|
||||
.help summary { color: CanvasText; cursor: pointer; font-weight: 650; }
|
||||
.help p { margin: 7px 0 0; }
|
||||
h2 { margin: 16px 0 8px; font-size: 13px; }
|
||||
.bundles { display: grid; gap: 6px; }
|
||||
.bundle { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
||||
.bundle code { overflow-wrap: anywhere; }
|
||||
</style></head><body>
|
||||
<details class="help"><summary>How it works</summary><p>Each row controls one profile in this local bundle. A switch adds or removes its source file; Delete removes the demo record and Restore returns it disabled. Every action reloads this bundle across filament, process, and machine. Local bundles lists every bundle id that orca.host.preset.bundles.list_ids() reports; Reload calls orca.host.preset.bundles.reload_local() for that id.</p></details>
|
||||
<div class="profiles" id="profiles"></div>
|
||||
<h2>Local bundles</h2>
|
||||
<div class="bundles" id="bundles"></div>
|
||||
<div class="actions"><button class="secondary" id="close">Close</button></div>
|
||||
<div class="message" id="message"></div>
|
||||
<script>
|
||||
'use strict';
|
||||
const $ = id => document.getElementById(id);
|
||||
let items = [];
|
||||
let bundles = [];
|
||||
let busy = false;
|
||||
function esc(value) {
|
||||
return String(value == null ? '' : value).replace(/[&<>"']/g, c =>
|
||||
({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||||
}
|
||||
function status(item) {
|
||||
if (item.deleted) return ['deleted', 'Deleted'];
|
||||
if (item.loaded) return ['loaded', 'Loaded'];
|
||||
if (item.enabled) return ['missing', 'Missing after reload'];
|
||||
return ['disabled', 'Not in Orca'];
|
||||
}
|
||||
function setBusy(value) {
|
||||
document.querySelectorAll('[data-toggle], [data-action]').forEach(control => control.disabled = value);
|
||||
}
|
||||
function render(message, error) {
|
||||
$('profiles').innerHTML = items.map(item => {
|
||||
const state = status(item);
|
||||
const deleted = item.deleted ? ' disabled' : '';
|
||||
const action = item.deleted
|
||||
? '<button class="secondary" data-action="restore" data-id="' + esc(item.id) + '">Restore</button>'
|
||||
: '<button data-action="delete" data-id="' + esc(item.id) + '">Delete</button>';
|
||||
return '<section class="profile"><div class="top"><div><div class="name">' + esc(item.name) +
|
||||
'</div><div class="kind">' + esc(item.kind) + '</div></div><span class="status ' + state[0] +
|
||||
'">' + state[1] + '</span></div><div class="bottom"><label class="toggle"><input type="checkbox" data-toggle="' +
|
||||
esc(item.id) + '"' + (item.enabled ? ' checked' : '') + deleted + '><span class="switch"></span>Available in Orca</label>' +
|
||||
action + '</div></section>';
|
||||
}).join('');
|
||||
$('bundles').innerHTML = bundles.length ? bundles.map(id =>
|
||||
'<div class="bundle"><code>' + esc(id) + '</code><button class="secondary" data-action="reload_bundle" data-bundle="' +
|
||||
esc(id) + '">Reload</button></div>').join('') : '<div class="kind">No local bundles found.</div>';
|
||||
$('message').textContent = error || message || '';
|
||||
$('message').className = error ? 'message error' : 'message';
|
||||
if (busy) setBusy(true);
|
||||
}
|
||||
function post(message) {
|
||||
busy = true;
|
||||
setBusy(true);
|
||||
$('message').textContent = 'Reloading local bundle…';
|
||||
$('message').className = 'message';
|
||||
orca.postMessage(message);
|
||||
}
|
||||
$('close').addEventListener('click', () => orca.close());
|
||||
$('profiles').addEventListener('change', event => {
|
||||
if (!busy && event.target.matches('[data-toggle]')) {
|
||||
post({command: 'set_enabled', id: event.target.dataset.toggle, enabled: event.target.checked});
|
||||
}
|
||||
});
|
||||
$('profiles').addEventListener('click', event => {
|
||||
const button = event.target.closest('[data-action]');
|
||||
if (!button || busy) return;
|
||||
if (button.dataset.action === 'delete' && !confirm('Delete this demo profile from the local bundle?')) return;
|
||||
post({command: button.dataset.action, id: button.dataset.id});
|
||||
});
|
||||
$('bundles').addEventListener('click', event => {
|
||||
const button = event.target.closest('[data-action="reload_bundle"]');
|
||||
if (button && !busy) post({command: 'reload_bundle', bundle_id: button.dataset.bundle});
|
||||
});
|
||||
orca.onMessage(message => {
|
||||
if (!message || message.command !== 'state') return;
|
||||
items = message.items || [];
|
||||
bundles = message.bundles || [];
|
||||
busy = false;
|
||||
render(message.message, message.error);
|
||||
});
|
||||
orca.postMessage({command: 'state'});
|
||||
</script></body></html>"""
|
||||
|
||||
|
||||
class ReloadLocalBundleDemo(orca.script.ScriptPluginCapabilityBase):
|
||||
window = None
|
||||
|
||||
def get_name(self):
|
||||
return "Reload local bundle demo"
|
||||
|
||||
def execute(self):
|
||||
try:
|
||||
if self.window is not None and self.window.is_open():
|
||||
self._post_state()
|
||||
return orca.ExecutionResult.success("Reload local bundle demo refreshed.")
|
||||
self.window = orca.host.ui.create_window(
|
||||
title="Reload local bundle demo", html=PAGE, width=500, height=420,
|
||||
on_message=self.on_message, on_close=self.on_close,
|
||||
)
|
||||
return orca.ExecutionResult.success("Reload local bundle demo opened.")
|
||||
except Exception as error:
|
||||
message = "Reload local bundle demo failed: %s" % error
|
||||
orca.host.ui.message(message, title="Reload local bundle demo", buttons="ok", icon="error")
|
||||
return orca.ExecutionResult.failure(orca.PluginResult.RecoverableError, message)
|
||||
|
||||
def on_close(self):
|
||||
self.window = None
|
||||
|
||||
def _post_state(self, message="", error=""):
|
||||
try:
|
||||
bundle = orca.host.preset_bundle()
|
||||
payload = _state_payload(bundle, _read_state(bundle), message, error)
|
||||
except Exception as state_error:
|
||||
payload = {"command": "state", "items": [], "message": message, "error": str(state_error)}
|
||||
if self.window is not None and self.window.is_open():
|
||||
self.window.post(payload)
|
||||
|
||||
def on_message(self, message):
|
||||
try:
|
||||
message = message or {}
|
||||
bundle = orca.host.preset_bundle()
|
||||
state = _read_state(bundle)
|
||||
if message.get("command") == "state":
|
||||
self._post_state()
|
||||
return
|
||||
if message.get("command") == "reload_bundle":
|
||||
bundle_id = message.get("bundle_id")
|
||||
if not isinstance(bundle_id, str) or not bundle_id:
|
||||
raise RuntimeError("A bundle id is required")
|
||||
orca.host.preset.bundles.reload_local(bundle_id)
|
||||
self._post_state(message="Reloaded local bundle %s." % bundle_id)
|
||||
return
|
||||
item = next((item for item in DEMO_PRESETS if item["id"] == message.get("id")), None)
|
||||
if item is None:
|
||||
raise RuntimeError("Unknown demo preset")
|
||||
profile = _profile_state(state, item)
|
||||
command = message.get("command")
|
||||
if command == "set_enabled":
|
||||
enabled = message.get("enabled")
|
||||
if not isinstance(enabled, bool):
|
||||
raise RuntimeError("Preset availability must be a boolean")
|
||||
if profile["deleted"]:
|
||||
raise RuntimeError("Restore the deleted demo preset before enabling it")
|
||||
profile["enabled"] = enabled
|
||||
elif command == "delete":
|
||||
profile.update({"enabled": False, "deleted": True, "parent_name": ""})
|
||||
elif command == "restore":
|
||||
profile.update({"enabled": False, "deleted": False, "parent_name": ""})
|
||||
else:
|
||||
raise RuntimeError("Unknown demo action")
|
||||
_reconcile(bundle, state)
|
||||
self._post_state(message="The change was applied. Check the corresponding preset selector in Orca.")
|
||||
except Exception as error:
|
||||
self._post_state(error=str(error))
|
||||
|
||||
|
||||
@orca.plugin
|
||||
class ReloadLocalBundleDemoPackage(orca.base):
|
||||
def register_capabilities(self):
|
||||
orca.register_capability(ReloadLocalBundleDemo)
|
||||
@@ -1822,7 +1822,7 @@ PresetCollection::UserPresetLoad PresetCollection::resolve_user_preset(
|
||||
const boost::filesystem::path &file, const std::string &canonical_name,
|
||||
const PresetOrigin &load_origin, ForwardCompatibilitySubstitutionRule substitution_rule,
|
||||
const std::string &extruder_id_name, const std::string &extruder_variant_name,
|
||||
std::set<std::string> *key_set1, std::set<std::string> *key_set2) const
|
||||
std::set<std::string> *key_set1, std::set<std::string> *key_set2, bool read_only) const
|
||||
{
|
||||
UserPresetLoad out;
|
||||
out.preset = Preset(m_type, canonical_name, false);
|
||||
@@ -1856,7 +1856,10 @@ PresetCollection::UserPresetLoad PresetCollection::resolve_user_preset(
|
||||
|
||||
std::string version_str = key_values[BBL_JSON_KEY_VERSION];
|
||||
boost::optional<Semver> version = Semver::parse(version_str);
|
||||
if (!version) return out;
|
||||
if (!version) {
|
||||
out.errors.push_back((boost::format("invalid version %1% in config %2%") % version_str % preset.file).str());
|
||||
return out;
|
||||
}
|
||||
preset.version = *version;
|
||||
|
||||
if (key_values.find(BBL_JSON_KEY_FILAMENT_ID) != key_values.end())
|
||||
@@ -1878,6 +1881,12 @@ PresetCollection::UserPresetLoad PresetCollection::resolve_user_preset(
|
||||
inherit_preset = this->find_preset2(inherits_value);
|
||||
Preset::normalize_inherits(config, inherit_preset);
|
||||
}
|
||||
// Local hot reload only supports FFF; no SLA default printer is installed.
|
||||
if (read_only && load_origin.kind == PresetOrigin::Kind::LocalBundle && m_type == Preset::TYPE_PRINTER &&
|
||||
Preset::printer_technology(config) != ptFFF) {
|
||||
out.errors.push_back((boost::format("local bundle printer %1% is not FFF") % preset.file).str());
|
||||
return out;
|
||||
}
|
||||
const Preset& default_preset = this->default_preset_for(config);
|
||||
if (inherit_preset) {
|
||||
preset.config = inherit_preset->config;
|
||||
@@ -2042,7 +2051,7 @@ void PresetCollection::load_presets(
|
||||
resolve_then_commit<CNumericLocalesSetter>(files.size(),
|
||||
[&](size_t i) {
|
||||
return this->resolve_user_preset(files[i].path, files[i].canonical_name, resolved_origin, substitution_rule,
|
||||
extruder_id_name, extruder_variant_name, key_set1, key_set2);
|
||||
extruder_id_name, extruder_variant_name, key_set1, key_set2, read_only);
|
||||
},
|
||||
[&](size_t i, UserPresetLoad &&loaded) {
|
||||
// Resolve read the files without the lock, so another instance may have saved
|
||||
@@ -2062,7 +2071,7 @@ void PresetCollection::load_presets(
|
||||
if (! (on_disk == loaded.on_disk)) {
|
||||
CNumericLocalesSetter locales_setter;
|
||||
loaded = this->resolve_user_preset(files[i].path, files[i].canonical_name, resolved_origin, substitution_rule,
|
||||
extruder_id_name, extruder_variant_name, key_set1, key_set2);
|
||||
extruder_id_name, extruder_variant_name, key_set1, key_set2, read_only);
|
||||
}
|
||||
}
|
||||
const bool leave_files = read_only || (! lock_path.empty() && ! instance_lock.locked());
|
||||
@@ -3135,7 +3144,9 @@ std::pair<Preset*, bool> PresetCollection::load_external_preset(
|
||||
}
|
||||
// Insert a new profile.
|
||||
//BBS: add project embedded preset logic
|
||||
bool from_project = boost::algorithm::iends_with(name, ".3mf");
|
||||
// A G-code's settings are shown like a project's, so a preset made from them lives with the project
|
||||
// rather than in the user's library.
|
||||
bool from_project = boost::algorithm::iends_with(name, ".3mf") || is_gcode_file(name);
|
||||
if (m_type == Preset::TYPE_PRINT)
|
||||
cfg.option<ConfigOptionString >("print_settings_id", true)->value = new_name;
|
||||
else if (m_type == Preset::TYPE_FILAMENT)
|
||||
|
||||
@@ -991,7 +991,8 @@ private:
|
||||
UserPresetLoad resolve_user_preset(const boost::filesystem::path &file, const std::string &canonical_name,
|
||||
const PresetOrigin &load_origin, ForwardCompatibilitySubstitutionRule substitution_rule,
|
||||
const std::string &extruder_id_name, const std::string &extruder_variant_name,
|
||||
std::set<std::string> *key_set1, std::set<std::string> *key_set2) const;
|
||||
std::set<std::string> *key_set1, std::set<std::string> *key_set2,
|
||||
bool read_only) const;
|
||||
|
||||
// Install one resolved preset. The collection, its alias maps, the error count
|
||||
// and the preset files on disk are touched here and only here.
|
||||
|
||||
+435
-30
@@ -1,3 +1,4 @@
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
@@ -56,8 +57,10 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <mutex>
|
||||
#include <numeric>
|
||||
#include <set>
|
||||
#include <shared_mutex>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
@@ -1383,6 +1386,27 @@ std::string PresetBundle::get_hotend_model_for_printer_model(std::string model_n
|
||||
return out;
|
||||
}
|
||||
|
||||
static void load_bundle_presets(PresetBundle& bundle, const std::string& bundle_dir, BundleMetadata& metadata,
|
||||
const PresetOrigin& origin, PresetsConfigSubstitutions& substitutions,
|
||||
ForwardCompatibilitySubstitutionRule substitution_rule, bool read_only = false)
|
||||
{
|
||||
struct Collection {
|
||||
PresetCollection& presets;
|
||||
const char* directory;
|
||||
std::vector<std::string>& names;
|
||||
};
|
||||
|
||||
metadata.print_presets.clear();
|
||||
metadata.filament_presets.clear();
|
||||
metadata.printer_presets.clear();
|
||||
for (Collection collection : {
|
||||
Collection{ bundle.prints, PRESET_PRINT_NAME, metadata.print_presets },
|
||||
Collection{ bundle.filaments, PRESET_FILAMENT_NAME, metadata.filament_presets },
|
||||
Collection{ bundle.printers, PRESET_PRINTER_NAME, metadata.printer_presets } })
|
||||
collection.presets.load_presets(bundle_dir, collection.directory, substitutions, substitution_rule,
|
||||
[&](Preset& preset) { collection.names.push_back(preset.name); }, origin, read_only);
|
||||
}
|
||||
|
||||
PresetsConfigSubstitutions PresetBundle::load_user_presets(std::string user, ForwardCompatibilitySubstitutionRule substitution_rule, bool read_only)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " entry and user is: " << user;
|
||||
@@ -1431,19 +1455,8 @@ PresetsConfigSubstitutions PresetBundle::load_user_presets(std::string user, For
|
||||
|
||||
BundleMetadata metadata;
|
||||
if (!load_bundle_metadata(metadata_file, metadata)) continue;
|
||||
metadata.print_presets.clear();
|
||||
metadata.filament_presets.clear();
|
||||
metadata.printer_presets.clear();
|
||||
|
||||
this->prints.load_presets(bundle_dir, PRESET_PRINT_NAME, substitutions, substitution_rule, [&](Preset& preset) {
|
||||
metadata.print_presets.push_back(preset.name);
|
||||
}, PresetOrigin(PresetOrigin::Kind::LocalBundle, metadata.id), read_only);
|
||||
this->filaments.load_presets(bundle_dir, PRESET_FILAMENT_NAME, substitutions, substitution_rule, [&](Preset& preset) {
|
||||
metadata.filament_presets.push_back(preset.name);
|
||||
}, PresetOrigin(PresetOrigin::Kind::LocalBundle, metadata.id), read_only);
|
||||
this->printers.load_presets(bundle_dir, PRESET_PRINTER_NAME, substitutions, substitution_rule, [&](Preset& preset) {
|
||||
metadata.printer_presets.push_back(preset.name);
|
||||
}, PresetOrigin(PresetOrigin::Kind::LocalBundle, metadata.id), read_only);
|
||||
load_bundle_presets(*this, bundle_dir, metadata, PresetOrigin(PresetOrigin::Kind::LocalBundle, metadata.id),
|
||||
substitutions, substitution_rule, read_only);
|
||||
metadata.bundle_type = BundleType::Local;
|
||||
metadata.path = metadata_file.string();
|
||||
|
||||
@@ -1466,20 +1479,10 @@ PresetsConfigSubstitutions PresetBundle::load_user_presets(std::string user, For
|
||||
|
||||
BundleMetadata metadata;
|
||||
if (!load_bundle_metadata(metadata_file, metadata)) continue;
|
||||
metadata.print_presets.clear();
|
||||
metadata.filament_presets.clear();
|
||||
metadata.printer_presets.clear();
|
||||
metadata.is_subscribed = true;
|
||||
|
||||
this->prints.load_presets(bundle_dir, PRESET_PRINT_NAME, substitutions, substitution_rule, [&](Preset& preset) {
|
||||
metadata.print_presets.push_back(preset.name);
|
||||
}, PresetOrigin(PresetOrigin::Kind::SubscribedBundle, metadata.id), read_only);
|
||||
this->filaments.load_presets(bundle_dir, PRESET_FILAMENT_NAME, substitutions, substitution_rule, [&](Preset& preset) {
|
||||
metadata.filament_presets.push_back(preset.name);
|
||||
}, PresetOrigin(PresetOrigin::Kind::SubscribedBundle, metadata.id), read_only);
|
||||
this->printers.load_presets(bundle_dir, PRESET_PRINTER_NAME, substitutions, substitution_rule, [&](Preset& preset) {
|
||||
metadata.printer_presets.push_back(preset.name);
|
||||
}, PresetOrigin(PresetOrigin::Kind::SubscribedBundle, metadata.id), read_only);
|
||||
load_bundle_presets(*this, bundle_dir, metadata, PresetOrigin(PresetOrigin::Kind::SubscribedBundle, metadata.id),
|
||||
substitutions, substitution_rule, read_only);
|
||||
|
||||
metadata.bundle_type = BundleType::Subscribed;
|
||||
metadata.path = metadata_file.string();
|
||||
@@ -1534,6 +1537,335 @@ PresetsConfigSubstitutions PresetBundle::load_user_presets(std::string user, For
|
||||
return PresetsConfigSubstitutions();
|
||||
}
|
||||
|
||||
static bool is_single_path_component(const std::string& value)
|
||||
{
|
||||
const fs::path path(value);
|
||||
return !value.empty() && value != "." && value != ".." && value.find_first_of("/\\") == std::string::npos &&
|
||||
!path.has_root_name() && !path.has_root_directory();
|
||||
}
|
||||
|
||||
static bool check_local_bundle(PresetBundleMetadata& bundles, const fs::path& user_dir, const std::string& bundle_id,
|
||||
BundleMetadata& metadata, std::string& error)
|
||||
{
|
||||
if (!is_single_path_component(bundle_id)) {
|
||||
error = "Local bundle id must be a single path component";
|
||||
return false;
|
||||
}
|
||||
const fs::path bundle_dir = user_dir / PRESET_LOCAL_DIR / bundle_id;
|
||||
if (!fs::is_directory(bundle_dir)) {
|
||||
error = "Local bundle directory does not exist";
|
||||
return false;
|
||||
}
|
||||
if (fs::exists(user_dir / PRESET_SUBSCRIBED_DIR / bundle_id)) {
|
||||
error = "A subscribed bundle already uses this id";
|
||||
return false;
|
||||
}
|
||||
{
|
||||
std::shared_lock<std::shared_mutex> lock(bundles.RWMtx);
|
||||
const auto it = bundles.m_bundles.find(bundle_id);
|
||||
if (it != bundles.m_bundles.end() && (it->second.bundle_type == BundleType::Subscribed || it->second.is_subscribed)) {
|
||||
error = "A subscribed bundle already uses this id";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!metadata.load_from_json((bundle_dir / PRESET_BUNDLE_METADATA).string()) || metadata.id != bundle_id) {
|
||||
error = "Local bundle metadata is missing, malformed, or has a different id";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::string> PresetBundle::list_local_bundle_ids(const std::string& preset_folder)
|
||||
{
|
||||
std::vector<std::string> ids;
|
||||
const std::string user = preset_folder.empty() ? DEFAULT_USER_FOLDER_NAME : preset_folder;
|
||||
if (!is_single_path_component(user))
|
||||
return ids;
|
||||
|
||||
const fs::path user_dir = fs::path(data_dir()) / PRESET_USER_DIR / user;
|
||||
const fs::path local_dir = user_dir / PRESET_LOCAL_DIR;
|
||||
try {
|
||||
if (!fs::is_directory(local_dir))
|
||||
return ids;
|
||||
for (const auto& entry : fs::directory_iterator(local_dir)) {
|
||||
const std::string bundle_id = entry.path().filename().string();
|
||||
try {
|
||||
BundleMetadata metadata;
|
||||
std::string error;
|
||||
if (check_local_bundle(bundles, user_dir, bundle_id, metadata, error))
|
||||
ids.push_back(bundle_id);
|
||||
} catch (const std::exception& exception) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": skipping " << bundle_id << ": " << exception.what();
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& exception) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": " << exception.what();
|
||||
}
|
||||
std::sort(ids.begin(), ids.end());
|
||||
return ids;
|
||||
}
|
||||
|
||||
bool PresetBundle::reload_local_bundle(const std::string& preset_folder, const std::string& bundle_id, std::string* error)
|
||||
{
|
||||
auto fail = [error](const std::string& message) {
|
||||
if (error != nullptr)
|
||||
*error = message;
|
||||
return false;
|
||||
};
|
||||
if (!is_single_path_component(bundle_id))
|
||||
return fail("Local bundle id must be a single path component");
|
||||
|
||||
const std::string user = preset_folder.empty() ? DEFAULT_USER_FOLDER_NAME : preset_folder;
|
||||
if (!is_single_path_component(user))
|
||||
return fail("Preset folder must be a single path component");
|
||||
|
||||
try {
|
||||
const fs::path user_dir = fs::path(data_dir()) / PRESET_USER_DIR / user;
|
||||
const fs::path bundle_dir = user_dir / PRESET_LOCAL_DIR / bundle_id;
|
||||
const fs::path metadata_file = bundle_dir / PRESET_BUNDLE_METADATA;
|
||||
|
||||
BundleMetadata metadata;
|
||||
std::string check_error;
|
||||
if (!check_local_bundle(bundles, user_dir, bundle_id, metadata, check_error))
|
||||
return fail(check_error);
|
||||
|
||||
// Keep background preset sync from changing the collections between copying and committing them.
|
||||
std::scoped_lock collection_lock(prints.m_mutex, filaments.m_mutex, printers.m_mutex);
|
||||
const std::string target_prefix = std::string(PRESET_LOCAL_DIR) + "/" + bundle_id + "/";
|
||||
const auto is_target = [&](const Preset& preset) {
|
||||
return preset.bundle_id == bundle_id && boost::starts_with(preset.name, target_prefix);
|
||||
};
|
||||
struct Selection {
|
||||
std::string name;
|
||||
Preset base{Preset::TYPE_INVALID, std::string()};
|
||||
Preset edited{Preset::TYPE_INVALID, std::string()};
|
||||
std::vector<std::string> dirty_options;
|
||||
};
|
||||
auto snapshot = [&](const PresetCollection& collection, Selection& selection) {
|
||||
selection.name = collection.get_selected_preset_name();
|
||||
if (selection.name.empty())
|
||||
return false;
|
||||
for (const Preset& preset : collection.get_presets()) {
|
||||
if (preset.name == selection.name) {
|
||||
selection.base = preset;
|
||||
selection.edited = collection.get_edited_preset();
|
||||
selection.dirty_options = PresetCollection::dirty_options(&selection.edited, &selection.base);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
Selection print_selection, filament_selection, printer_selection;
|
||||
if (!snapshot(prints, print_selection) || !snapshot(filaments, filament_selection) || !snapshot(printers, printer_selection))
|
||||
return fail("Preset selections are not initialized");
|
||||
|
||||
PresetBundle staged;
|
||||
// Collection copies keep vendor pointers into the live vendor map, which is not replaced by this reload.
|
||||
staged.prints = prints;
|
||||
staged.filaments = filaments;
|
||||
staged.printers = printers;
|
||||
staged.filament_presets = filament_presets;
|
||||
staged.project_config.apply_only(project_config, {"flush_volumes_matrix", "flush_volumes_vector", "flush_multiplier"});
|
||||
auto remove_target = [&](PresetCollection& collection) {
|
||||
auto begin = collection.m_presets.begin() + collection.m_num_default_presets;
|
||||
collection.m_presets.erase(std::remove_if(begin, collection.m_presets.end(), is_target), collection.m_presets.end());
|
||||
collection.sort_presets();
|
||||
};
|
||||
remove_target(staged.prints);
|
||||
remove_target(staged.filaments);
|
||||
remove_target(staged.printers);
|
||||
|
||||
const std::array<int, 3> errors_before = { staged.prints.error_count(), staged.filaments.error_count(), staged.printers.error_count() };
|
||||
PresetsConfigSubstitutions substitutions;
|
||||
load_bundle_presets(staged, bundle_dir.string(), metadata, PresetOrigin(PresetOrigin::Kind::LocalBundle, bundle_id),
|
||||
substitutions, ForwardCompatibilitySubstitutionRule::Enable, true);
|
||||
if (staged.prints.error_count() != errors_before[0] || staged.filaments.error_count() != errors_before[1] || staged.printers.error_count() != errors_before[2])
|
||||
return fail("Local bundle contains an invalid preset");
|
||||
|
||||
auto dirty_target_was_removed = [&](const Selection& selection, const PresetCollection& collection) {
|
||||
if (selection.dirty_options.empty() || !is_target(selection.base))
|
||||
return false;
|
||||
for (const Preset& preset : collection.get_presets())
|
||||
if (preset.name == selection.name && is_target(preset))
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
if (dirty_target_was_removed(print_selection, staged.prints) ||
|
||||
dirty_target_was_removed(filament_selection, staged.filaments) ||
|
||||
dirty_target_was_removed(printer_selection, staged.printers))
|
||||
return fail("A modified selected preset was removed or renamed");
|
||||
|
||||
auto external_child_name = [&](const PresetCollection& collection) {
|
||||
auto depends_on_target = [&](const Preset& preset) {
|
||||
if (is_target(preset))
|
||||
return false;
|
||||
const Preset* parent = &preset;
|
||||
std::unordered_set<const Preset*> visited;
|
||||
while ((parent = collection.get_preset_parent(*parent)) != nullptr && visited.insert(parent).second)
|
||||
if (is_target(*parent))
|
||||
return true;
|
||||
for (const char* key : {"compatible_prints", "compatible_printers"}) {
|
||||
const auto* names = preset.config.option<ConfigOptionStrings>(key);
|
||||
if (names == nullptr)
|
||||
continue;
|
||||
const PresetCollection& referenced = std::string(key) == "compatible_printers" ? printers : prints;
|
||||
for (const std::string& name : names->values) {
|
||||
const Preset* dependency = referenced.find_preset(name, false);
|
||||
if (dependency != nullptr && is_target(*dependency))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
for (const Preset& preset : collection.get_presets())
|
||||
if (depends_on_target(preset))
|
||||
return preset.name;
|
||||
if (depends_on_target(collection.get_edited_preset()))
|
||||
return collection.get_edited_preset().name;
|
||||
return std::string();
|
||||
};
|
||||
for (const PresetCollection* collection : std::array<const PresetCollection*, 3>{&prints, &filaments, &printers}) {
|
||||
const std::string child_name = external_child_name(*collection);
|
||||
if (!child_name.empty())
|
||||
return fail("Preset outside this bundle depends on it: " + child_name);
|
||||
}
|
||||
|
||||
auto restore_selection = [](PresetCollection& collection, const Selection& selection) {
|
||||
if (collection.find_preset(selection.name, false, true) == nullptr) {
|
||||
collection.select_preset(collection.first_visible_idx());
|
||||
return false;
|
||||
}
|
||||
collection.select_preset_by_name(selection.name, true);
|
||||
if (collection.get_selected_preset_name() != selection.name)
|
||||
return false;
|
||||
Preset& edited = collection.get_edited_preset();
|
||||
for (const std::string& key : selection.dirty_options) {
|
||||
if (const ConfigOption* option = selection.edited.config.option(key))
|
||||
edited.config.set_key_value(key, option->clone());
|
||||
else
|
||||
edited.config.erase(key);
|
||||
}
|
||||
collection.update_dirty();
|
||||
return true;
|
||||
};
|
||||
|
||||
staged.prints.update_map_alias_to_profile_name();
|
||||
staged.filaments.update_map_alias_to_profile_name();
|
||||
staged.printers.update_map_alias_to_profile_name();
|
||||
const bool printer_preserved = restore_selection(staged.printers, printer_selection);
|
||||
const bool print_preserved = restore_selection(staged.prints, print_selection);
|
||||
const bool filament_preserved = restore_selection(staged.filaments, filament_selection);
|
||||
const auto selection_mode = [printer_preserved](bool selection_preserved) {
|
||||
if (!selection_preserved)
|
||||
return PresetSelectCompatibleType::Always;
|
||||
return printer_preserved ? PresetSelectCompatibleType::Never : PresetSelectCompatibleType::OnlyIfWasCompatible;
|
||||
};
|
||||
if (staged.printers.get_edited_preset().printer_technology() != ptFFF)
|
||||
return fail("Local bundle reload requires an FFF printer selection");
|
||||
staged.update_compatible(selection_mode(print_preserved), selection_mode(filament_preserved));
|
||||
for (std::string& name : staged.filament_presets)
|
||||
if (boost::starts_with(name, target_prefix) && staged.filaments.find_preset(name, false) == nullptr)
|
||||
name = staged.filaments.first_compatible().name;
|
||||
staged.update_multi_material_filament_presets();
|
||||
|
||||
struct Collection
|
||||
{
|
||||
PresetCollection& live;
|
||||
PresetCollection& candidate;
|
||||
size_t original_size;
|
||||
};
|
||||
std::array<Collection, 3> collections{{{prints, staged.prints, prints.size()},
|
||||
{filaments, staged.filaments, filaments.size()},
|
||||
{printers, staged.printers, printers.size()}}};
|
||||
for (size_t i = 0; i < collections.size(); ++i) {
|
||||
const auto& collection = collections[i];
|
||||
const auto dirty_options = collection.live.current_dirty_options();
|
||||
if (dirty_options.empty())
|
||||
continue;
|
||||
if (collection.live.get_selected_preset_name() != collection.candidate.get_selected_preset_name())
|
||||
return fail("Reload would discard a modified selected preset");
|
||||
for (const std::string& key : dirty_options) {
|
||||
const ConfigOption* before = collection.live.get_edited_preset().config.option(key);
|
||||
const ConfigOption* after = collection.candidate.get_edited_preset().config.option(key);
|
||||
if ((before == nullptr) != (after == nullptr) || (before != nullptr && *before != *after))
|
||||
return fail("Reload would discard a modified preset option: " + key);
|
||||
}
|
||||
}
|
||||
|
||||
if (calibrate_printer != nullptr && staged.printers.find_preset(calibrate_printer->name, false, true) != nullptr)
|
||||
staged.set_calibrate_printer(calibrate_printer->name);
|
||||
std::array<std::pair<ConfigOptionFloats*, ConfigOptionFloats*>, 3> flush_options;
|
||||
size_t i = 0;
|
||||
for (const char* key : {"flush_volumes_matrix", "flush_volumes_vector", "flush_multiplier"})
|
||||
flush_options[i++] = {project_config.option<ConfigOptionFloats>(key), staged.project_config.option<ConfigOptionFloats>(key)};
|
||||
|
||||
metadata.bundle_type = BundleType::Local;
|
||||
metadata.is_subscribed = false;
|
||||
metadata.update_available = false;
|
||||
metadata.path = metadata_file.string();
|
||||
fs::path local_dir = user_dir / PRESET_LOCAL_DIR;
|
||||
std::unique_lock<std::shared_mutex> lock(bundles.RWMtx);
|
||||
const auto existing = bundles.m_bundles.find(bundle_id);
|
||||
if (existing != bundles.m_bundles.end() &&
|
||||
(existing->second.bundle_type == BundleType::Subscribed || existing->second.is_subscribed))
|
||||
return fail("A subscribed bundle already uses this id");
|
||||
|
||||
std::set<const Preset*> calibration_filaments;
|
||||
const Preset* calibration_printer = nullptr;
|
||||
try {
|
||||
// Growing a deque preserves existing objects. Roll back appended copies if preparation fails.
|
||||
for (size_t i = 0; i < collections.size(); ++i) {
|
||||
auto& collection = collections[i];
|
||||
while (collection.live.size() < collection.candidate.size())
|
||||
collection.live.m_presets.emplace_back(collection.candidate.m_presets[collection.live.size()]);
|
||||
}
|
||||
auto calibration_preset = [](const Preset* preset, PresetCollection& live, PresetCollection& candidate) -> const Preset* {
|
||||
if (preset == nullptr)
|
||||
return nullptr;
|
||||
if (preset == &candidate.get_edited_preset())
|
||||
return &live.get_edited_preset();
|
||||
const auto it = candidate.find_preset_internal(preset->name);
|
||||
return &live.m_presets[it - candidate.m_presets.begin()];
|
||||
};
|
||||
calibration_printer = calibration_preset(staged.calibrate_printer, printers, staged.printers);
|
||||
for (const Preset* preset : staged.calibrate_filaments)
|
||||
calibration_filaments.insert(calibration_preset(preset, filaments, staged.filaments));
|
||||
// Insertion is the last allocating operation. Replacing metadata only moves its members.
|
||||
bundles.m_bundles.insert_or_assign(bundle_id, std::move(metadata));
|
||||
} catch (...) {
|
||||
for (size_t i = 0; i < collections.size(); ++i)
|
||||
while (collections[i].live.size() > collections[i].original_size)
|
||||
collections[i].live.m_presets.pop_back();
|
||||
throw;
|
||||
}
|
||||
auto install_target = [](PresetCollection& live, PresetCollection& candidate) noexcept {
|
||||
// Keep stored Preset references alive when the collection's layout is unchanged.
|
||||
// Adding/removing presets retains the collection's usual sorted-position semantics.
|
||||
for (size_t i = 0; i < candidate.size(); ++i)
|
||||
live.m_presets[i] = std::move(candidate.m_presets[i]);
|
||||
while (live.size() > candidate.size())
|
||||
live.m_presets.pop_back();
|
||||
live.m_map_alias_to_profile_name.swap(candidate.m_map_alias_to_profile_name);
|
||||
// Move assignment does not allocate (including Semver); swapping Presets would construct temporary containers.
|
||||
live.m_edited_preset = std::move(candidate.m_edited_preset);
|
||||
live.m_saved_preset = std::move(candidate.m_saved_preset);
|
||||
live.m_idx_selected = candidate.m_idx_selected;
|
||||
};
|
||||
for (size_t i = 0; i < collections.size(); ++i)
|
||||
install_target(collections[i].live, collections[i].candidate);
|
||||
filament_presets.swap(staged.filament_presets);
|
||||
for (const auto& options : flush_options)
|
||||
options.first->values.swap(options.second->values);
|
||||
calibrate_printer = calibration_printer;
|
||||
calibrate_filaments.swap(calibration_filaments);
|
||||
dir_user_presets_local.swap(local_dir);
|
||||
return true;
|
||||
} catch (const std::exception& exception) {
|
||||
return fail(std::string("Failed to reload local bundle: ") + exception.what());
|
||||
}
|
||||
}
|
||||
|
||||
PresetsConfigSubstitutions PresetBundle::load_user_presets(AppConfig & config,
|
||||
std::map<std::string, std::map<std::string, std::string>> &my_presets,
|
||||
ForwardCompatibilitySubstitutionRule substitution_rule)
|
||||
@@ -3898,6 +4230,8 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p
|
||||
// BBS: change directories by design
|
||||
void PresetBundle::export_selections(AppConfig &config)
|
||||
{
|
||||
if (m_selection_export_blockers > 0)
|
||||
return;
|
||||
assert(this->printers.get_edited_preset().printer_technology() != ptFFF || filament_presets.size() >= 1);
|
||||
//assert(this->printers.get_edited_preset().printer_technology() != ptFFF || filament_presets.size() > 1 || filaments.get_selected_preset_name() == filament_presets.front());
|
||||
config.clear_section("presets");
|
||||
@@ -5524,12 +5858,10 @@ DynamicPrintConfig PresetBundle::full_sla_config() const
|
||||
ConfigSubstitutions PresetBundle::load_config_file(const std::string &path, ForwardCompatibilitySubstitutionRule compatibility_rule)
|
||||
{
|
||||
if (is_gcode_file(path)) {
|
||||
DynamicPrintConfig config;
|
||||
//BBS: add config related logs
|
||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" enter, gcodefile %1%, compatibility_rule %2%")%path %compatibility_rule;
|
||||
config.apply(FullPrintConfig::defaults());
|
||||
ConfigSubstitutions config_substitutions = config.load_from_gcode_file(path, compatibility_rule);
|
||||
Preset::normalize(config);
|
||||
ConfigSubstitutions config_substitutions;
|
||||
DynamicPrintConfig config = config_from_gcode(path, compatibility_rule, config_substitutions);
|
||||
load_config_file_config(path, true, std::move(config));
|
||||
return config_substitutions;
|
||||
}
|
||||
@@ -5537,10 +5869,83 @@ ConfigSubstitutions PresetBundle::load_config_file(const std::string &path, Forw
|
||||
//BBS: add config related logs
|
||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" can not load config file %1% not from gcode")%path ;
|
||||
throw Slic3r::RuntimeError((boost::format(L("Unknown configuration file: %1%")) % path).str());
|
||||
|
||||
|
||||
return ConfigSubstitutions{};
|
||||
}
|
||||
|
||||
DynamicPrintConfig PresetBundle::config_from_gcode(const std::string &path, ForwardCompatibilitySubstitutionRule compatibility_rule,
|
||||
ConfigSubstitutions &substitutions) const
|
||||
{
|
||||
DynamicPrintConfig config;
|
||||
config.apply(FullPrintConfig::defaults());
|
||||
// The config block leaves a few project settings out (filament_colour_type, prime_volume_mode,
|
||||
// flush_multiplier_fast); those keep this bundle's values rather than falling back to the defaults.
|
||||
config.apply(this->project_config);
|
||||
const DynamicPrintConfig base = config;
|
||||
substitutions = config.load_from_gcode_file(path, compatibility_rule);
|
||||
// An empty list of choices sets nothing: G-code written by a tool rather than from presets leaves per-extruder
|
||||
// lists such as nozzle_volume_type empty, and the settings tabs expect a choice per extruder. An empty text
|
||||
// list, such as filament_multi_colour, is a value of its own and stays.
|
||||
for (const std::string &key : config.keys())
|
||||
if (const ConfigOption *opt = config.option(key); opt->type() == coEnums && static_cast<const ConfigOptionVectorBase *>(opt)->size() == 0)
|
||||
if (const ConfigOption *below = base.option(key); below != nullptr && static_cast<const ConfigOptionVectorBase *>(below)->size() > 0)
|
||||
config.set_key_value(key, below->clone());
|
||||
Preset::normalize(config);
|
||||
// The block holds flush_volumes_matrix already multiplied by the multiplier the slice used, so a
|
||||
// multiplier of 1 keeps the purge volumes the file was sliced with. G-code from before 2.3.2 held the
|
||||
// plain matrix and loses a multiplier other than 1.
|
||||
for (const char *key : { "flush_multiplier", "flush_multiplier_fast" })
|
||||
if (auto *multiplier = config.option<ConfigOptionFloats>(key))
|
||||
std::fill(multiplier->values.begin(), multiplier->values.end(), 1.);
|
||||
return config;
|
||||
}
|
||||
|
||||
std::optional<PresetBundle::Selection> PresetBundle::selection() const
|
||||
{
|
||||
auto capture = [](const PresetCollection &presets, Selection::Collection &out) {
|
||||
if (std::any_of(presets.m_presets.begin(), presets.m_presets.end(), [](const Preset &preset) { return preset.is_project_embedded; }))
|
||||
return false;
|
||||
out.selected = presets.get_selected_preset_name();
|
||||
out.edited = presets.get_edited_preset().config;
|
||||
out.saved = presets.m_saved_preset.config;
|
||||
out.visible.reserve(presets.m_presets.size());
|
||||
for (const Preset &preset : presets.m_presets)
|
||||
out.visible.push_back(preset.is_visible);
|
||||
return true;
|
||||
};
|
||||
Selection selection;
|
||||
if (!capture(this->prints, selection.prints) || !capture(this->filaments, selection.filaments) || !capture(this->printers, selection.printers))
|
||||
return std::nullopt;
|
||||
selection.filament_presets = this->filament_presets;
|
||||
selection.project_config = this->project_config;
|
||||
selection.extruder_ams_counts = this->extruder_ams_counts;
|
||||
return selection;
|
||||
}
|
||||
|
||||
void PresetBundle::restore_selection(const Selection &selection)
|
||||
{
|
||||
auto restore = [](PresetCollection &presets, const Selection::Collection &saved) {
|
||||
presets.reset_project_embedded_presets();
|
||||
// Before selecting: select_preset_by_name() passes over a hidden preset.
|
||||
assert(presets.m_presets.size() == saved.visible.size());
|
||||
for (size_t i = 0; i < std::min(presets.m_presets.size(), saved.visible.size()); ++i)
|
||||
presets.m_presets[i].is_visible = saved.visible[i];
|
||||
presets.select_preset_by_name(saved.selected, true);
|
||||
presets.m_saved_preset.config = saved.saved;
|
||||
presets.get_edited_preset().config = saved.edited;
|
||||
presets.update_dirty();
|
||||
};
|
||||
restore(this->printers, selection.printers);
|
||||
restore(this->prints, selection.prints);
|
||||
restore(this->filaments, selection.filaments);
|
||||
this->update_compatible(PresetSelectCompatibleType::Never);
|
||||
this->update_multi_material_filament_presets();
|
||||
// Last, as update_multi_material_filament_presets() resizes filament_presets and the flush data in project_config.
|
||||
this->filament_presets = selection.filament_presets;
|
||||
this->project_config = selection.project_config;
|
||||
this->extruder_ams_counts = selection.extruder_ams_counts;
|
||||
}
|
||||
|
||||
|
||||
//some filament presets split from one to sperate ones
|
||||
//following map recording these filament presets
|
||||
|
||||
@@ -302,6 +302,9 @@ public:
|
||||
// BBS Load user presets
|
||||
PresetsConfigSubstitutions load_user_presets(std::string user, ForwardCompatibilitySubstitutionRule rule, bool read_only = false);
|
||||
PresetsConfigSubstitutions load_user_presets(AppConfig &config, std::map<std::string, std::map<std::string, std::string>>& my_presets, ForwardCompatibilitySubstitutionRule rule);
|
||||
|
||||
bool reload_local_bundle(const std::string& preset_folder, const std::string& bundle_id, std::string* error = nullptr);
|
||||
std::vector<std::string> list_local_bundle_ids(const std::string& preset_folder);
|
||||
// Orca: Import subscribed bundle presets (load and save to disk in one operation), handles one bundle at a time
|
||||
PresetsConfigSubstitutions update_subscribed_presets(AppConfig& config,
|
||||
const std::map<std::string, std::map<std::string, std::string>>& bundle_presets,
|
||||
@@ -397,6 +400,28 @@ public:
|
||||
std::vector<Preset*> get_current_project_embedded_presets();
|
||||
void reset_project_embedded_presets();
|
||||
|
||||
// What importing a file's settings replaces, so that an import which fails part-way, or whose
|
||||
// file is then rejected, can be undone without copying every preset.
|
||||
struct Selection
|
||||
{
|
||||
struct Collection
|
||||
{
|
||||
std::string selected;
|
||||
DynamicPrintConfig edited;
|
||||
// The baseline saved_is_dirty() compares against, which feeds the project dirty state.
|
||||
DynamicPrintConfig saved;
|
||||
// By index: an import only inserts project-embedded presets, which a restore drops first.
|
||||
std::vector<bool> visible;
|
||||
};
|
||||
Collection prints, filaments, printers;
|
||||
std::vector<std::string> filament_presets;
|
||||
DynamicPrintConfig project_config;
|
||||
std::vector<std::map<int, int>> extruder_ams_counts;
|
||||
};
|
||||
// Nothing while project-embedded presets exist: a restore drops them, which would lose a loaded project's presets.
|
||||
std::optional<Selection> selection() const;
|
||||
void restore_selection(const Selection &selection);
|
||||
|
||||
//BBS: find printer model
|
||||
std::string get_texture_for_printer_model(std::string model_name);
|
||||
std::string get_stl_model_for_printer_model(std::string model_name);
|
||||
@@ -405,6 +430,21 @@ public:
|
||||
// Export selections (current print, current filaments, current printer) into config.ini
|
||||
void export_selections(AppConfig &config);
|
||||
|
||||
// While one lives, export_selections() writes nothing, so a selection only shown for a while (the
|
||||
// settings of a G-code being previewed) is not remembered as the user's. It also covers the
|
||||
// exports the GUI makes on its own while refreshing.
|
||||
class SelectionExportBlocker
|
||||
{
|
||||
public:
|
||||
explicit SelectionExportBlocker(PresetBundle &bundle) : m_bundle(bundle) { ++m_bundle.m_selection_export_blockers; }
|
||||
~SelectionExportBlocker() { --m_bundle.m_selection_export_blockers; }
|
||||
SelectionExportBlocker(const SelectionExportBlocker &) = delete;
|
||||
SelectionExportBlocker &operator=(const SelectionExportBlocker &) = delete;
|
||||
|
||||
private:
|
||||
PresetBundle &m_bundle;
|
||||
};
|
||||
|
||||
// n is the total slot count, and growth appends at the raw tail - which is where the mixed
|
||||
// slots live. A caller adding physical filaments has to add num_mixed_filaments() on top and
|
||||
// then move the new slots ahead of the mixed tail, as Sidebar::add_custom_filament does.
|
||||
@@ -546,6 +586,11 @@ public:
|
||||
// If the file is loaded successfully, its print / filament / printer profiles will be activated.
|
||||
ConfigSubstitutions load_config_file(const std::string &path, ForwardCompatibilitySubstitutionRule compatibility_rule);
|
||||
|
||||
// The settings an OrcaSlicer G-code was sliced with, from its config block, ready for load_config_model().
|
||||
// Throws when the file carries no such block (another slicer, or the block was left out).
|
||||
DynamicPrintConfig config_from_gcode(const std::string &path, ForwardCompatibilitySubstitutionRule compatibility_rule,
|
||||
ConfigSubstitutions &substitutions) const;
|
||||
|
||||
// Load a config bundle file, into presets and store the loaded presets into separate files
|
||||
// of the local configuration directory.
|
||||
// Load settings into the provided settings instance.
|
||||
@@ -896,6 +941,9 @@ private:
|
||||
std::string vendor_to_validate = "";
|
||||
int m_errors = 0;
|
||||
|
||||
// The live SelectionExportBlockers.
|
||||
int m_selection_export_blockers = 0;
|
||||
|
||||
// Helper function: save preset to bundle directory with common logic
|
||||
bool save_preset_to_bundle_dir(Preset& preset, PresetCollection* collection,
|
||||
const std::string& bundle_id, const std::string& type_subdir,
|
||||
|
||||
@@ -174,6 +174,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/Gizmos/GLGizmoAssembly.hpp
|
||||
GUI/Gizmos/GLGizmoBase.cpp
|
||||
GUI/Gizmos/GLGizmoBase.hpp
|
||||
GUI/Gizmos/GLGizmoAlignment.cpp
|
||||
GUI/Gizmos/GLGizmoAlignment.hpp
|
||||
GUI/Gizmos/GLGizmoBrimEars.cpp
|
||||
GUI/Gizmos/GLGizmoBrimEars.hpp
|
||||
GUI/Gizmos/GLGizmoCut.cpp
|
||||
|
||||
@@ -1797,7 +1797,9 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
|
||||
}
|
||||
else {*/
|
||||
//BBS: add only gcode mode
|
||||
if (m_only_gcode_in_preview) {
|
||||
// When the file's own settings were loaded into the presets, the bed already is the file's, with the
|
||||
// model and texture of a user or custom printer too, which the lookup below cannot resolve.
|
||||
if (m_only_gcode_in_preview && !wxGetApp().plater()->gcode_settings_loaded()) {
|
||||
Pointfs printable_area;
|
||||
//BBS: add bed exclude area
|
||||
Pointfs bed_exclude_area = Pointfs();
|
||||
|
||||
@@ -5520,7 +5520,7 @@ void GLCanvas3D::set_tooltip(const std::string& tooltip)
|
||||
m_tooltip.set_text(tooltip);
|
||||
}
|
||||
|
||||
void GLCanvas3D::do_move(const std::string& snapshot_type)
|
||||
void GLCanvas3D::do_move(const std::string& snapshot_type, bool force_volume_move)
|
||||
{
|
||||
if (m_model == nullptr)
|
||||
return;
|
||||
@@ -5552,10 +5552,18 @@ void GLCanvas3D::do_move(const std::string& snapshot_type)
|
||||
|
||||
// Move instances/volumes
|
||||
ModelObject* model_object = m_model->objects[object_idx];
|
||||
if (model_object == nullptr)
|
||||
if (model_object == nullptr)
|
||||
continue;
|
||||
|
||||
if (selection_mode == Selection::Instance) {
|
||||
if (force_volume_move || selection_mode == Selection::Volume) {
|
||||
auto cur_mv = model_object->volumes[volume_idx];
|
||||
if (cur_mv->get_transformation() != v->get_volume_transformation()) {
|
||||
cur_mv->set_transformation(v->get_volume_transformation());
|
||||
// BBS: backup
|
||||
Slic3r::save_object_mesh(*model_object);
|
||||
}
|
||||
}
|
||||
else if (selection_mode == Selection::Instance) {
|
||||
if (m_canvas_type == GLCanvas3D::ECanvasType::CanvasAssembleView) {
|
||||
if ((model_object->instances[instance_idx]->get_assemble_offset() - v->get_instance_offset()).norm() > 1e-2) {
|
||||
model_object->instances[instance_idx]->set_assemble_transformation(v->get_instance_transformation());
|
||||
@@ -5564,14 +5572,6 @@ void GLCanvas3D::do_move(const std::string& snapshot_type)
|
||||
model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation());
|
||||
}
|
||||
}
|
||||
else if (selection_mode == Selection::Volume) {
|
||||
auto cur_mv = model_object->volumes[volume_idx];
|
||||
if (cur_mv->get_transformation() != v->get_volume_transformation()) {
|
||||
cur_mv->set_transformation(v->get_volume_transformation());
|
||||
// BBS: backup
|
||||
Slic3r::save_object_mesh(*model_object);
|
||||
}
|
||||
}
|
||||
|
||||
object_moved = true;
|
||||
model_object->invalidate_bounding_box();
|
||||
|
||||
@@ -1305,7 +1305,7 @@ public:
|
||||
void set_tooltip(const std::string& tooltip);
|
||||
|
||||
// the following methods add a snapshot to the undo/redo stack, unless the given string is empty
|
||||
void do_move(const std::string& snapshot_type);
|
||||
void do_move(const std::string& snapshot_type, bool force_volume_move = false);
|
||||
void do_rotate(const std::string& snapshot_type);
|
||||
void do_scale(const std::string& snapshot_type);
|
||||
void do_center();
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "Selection.hpp"
|
||||
//BBS: add partplate related logic
|
||||
#include "PartPlate.hpp"
|
||||
#include "Gizmos/GLGizmoAlignment.hpp"
|
||||
#include "Gizmos/GLGizmoEmboss.hpp"
|
||||
#include "Gizmos/GLGizmoSVG.hpp"
|
||||
|
||||
@@ -1672,6 +1673,8 @@ void MenuFactory::create_extra_object_menu()
|
||||
append_menu_item_merge_parts_to_single_part(&m_object_menu);
|
||||
// Object Center
|
||||
append_menu_item_center(&m_object_menu);
|
||||
// Object Align/Distribute
|
||||
append_menu_item_align_distribute(&m_object_menu);
|
||||
// Object Drop
|
||||
append_menu_item_drop(&m_object_menu);
|
||||
// Object Split
|
||||
@@ -1779,6 +1782,7 @@ void MenuFactory::create_text_part_menu()
|
||||
append_menu_item_fix_through_cgal(menu);
|
||||
append_menu_item_simplify(menu);
|
||||
append_menu_item_center(menu);
|
||||
append_menu_item_align_distribute(menu);
|
||||
append_menu_items_mirror(menu);
|
||||
menu->AppendSeparator();
|
||||
append_menu_item_per_object_process(menu);
|
||||
@@ -1811,6 +1815,7 @@ void MenuFactory::create_bbl_part_menu()
|
||||
append_menu_item_simplify(menu);
|
||||
append_menu_item_smooth_mesh(menu);
|
||||
append_menu_item_center(menu);
|
||||
append_menu_item_align_distribute(menu);
|
||||
append_menu_item_drop(menu);
|
||||
append_menu_items_mirror(menu);
|
||||
wxMenu* split_menu = new wxMenu();
|
||||
@@ -2136,6 +2141,7 @@ wxMenu* MenuFactory::multi_selection_menu()
|
||||
index++;
|
||||
}
|
||||
append_menu_item_center(menu);
|
||||
append_menu_item_align_distribute(menu);
|
||||
append_menu_item_drop(menu);
|
||||
append_menu_item_fix_through_cgal(menu);
|
||||
//append_menu_item_simplify(menu);
|
||||
@@ -2144,13 +2150,13 @@ wxMenu* MenuFactory::multi_selection_menu()
|
||||
|
||||
append_menu_item_set_printable(menu);
|
||||
menu->AppendSeparator();
|
||||
|
||||
|
||||
append_menu_item_set_auto_drop(menu);
|
||||
menu->AppendSeparator();
|
||||
|
||||
|
||||
append_menu_item_per_object_process(menu);
|
||||
menu->AppendSeparator();
|
||||
|
||||
|
||||
append_menu_items_convert_unit(menu);
|
||||
append_menu_item_replace_all_with_stl(menu);
|
||||
//BBS
|
||||
@@ -2161,6 +2167,7 @@ wxMenu* MenuFactory::multi_selection_menu()
|
||||
}
|
||||
else {
|
||||
append_menu_item_center(menu);
|
||||
append_menu_item_align_distribute(menu);
|
||||
append_menu_item_drop(menu);
|
||||
append_menu_item_fix_through_cgal(menu);
|
||||
//append_menu_item_simplify(menu);
|
||||
@@ -2325,6 +2332,247 @@ void MenuFactory::append_menu_item_center(wxMenu* menu)
|
||||
}, m_parent);
|
||||
}
|
||||
|
||||
void MenuFactory::append_menu_item_align_distribute(wxMenu *menu)
|
||||
{
|
||||
wxMenu* align_distribute_menu = new wxMenu();
|
||||
if (!align_distribute_menu)
|
||||
return;
|
||||
|
||||
append_menu_item(
|
||||
align_distribute_menu, wxID_ANY, _L("Distribute left-right") + " (X)", "",
|
||||
[](wxCommandEvent &) {
|
||||
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||
canvas3d->get_gizmos_manager().check_object_located_outside_plate(true);
|
||||
plater()->distribute_selection_x();
|
||||
},
|
||||
"", nullptr,
|
||||
[]() {
|
||||
if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D)
|
||||
return false;
|
||||
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||
GLGizmoAlignment alignment_helper(*canvas3d);
|
||||
return alignment_helper.can_distribute(GLGizmoAlignment::AlignType::DISTRIBUTE_X);
|
||||
},
|
||||
m_parent);
|
||||
|
||||
append_menu_item(
|
||||
align_distribute_menu, wxID_ANY, _L("Distribute front-back") + " (Y)", "",
|
||||
[](wxCommandEvent &) {
|
||||
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||
canvas3d->get_gizmos_manager().check_object_located_outside_plate(true);
|
||||
plater()->distribute_selection_y();
|
||||
},
|
||||
"", nullptr,
|
||||
[]() {
|
||||
if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D)
|
||||
return false;
|
||||
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||
GLGizmoAlignment alignment_helper(*canvas3d);
|
||||
return alignment_helper.can_distribute(GLGizmoAlignment::AlignType::DISTRIBUTE_Y);
|
||||
},
|
||||
m_parent);
|
||||
|
||||
append_menu_item(
|
||||
align_distribute_menu, wxID_ANY, _L("Distribute top-bottom") + " (Z)", "",
|
||||
[](wxCommandEvent &) {
|
||||
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||
canvas3d->get_gizmos_manager().check_object_located_outside_plate(true);
|
||||
plater()->distribute_selection_z();
|
||||
},
|
||||
"", nullptr,
|
||||
[]() {
|
||||
if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D)
|
||||
return false;
|
||||
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||
GLGizmoAlignment alignment_helper(*canvas3d);
|
||||
return alignment_helper.can_distribute(GLGizmoAlignment::AlignType::DISTRIBUTE_Z);
|
||||
},
|
||||
m_parent);
|
||||
|
||||
align_distribute_menu->AppendSeparator();
|
||||
|
||||
append_menu_item(
|
||||
align_distribute_menu, wxID_ANY, _L("Align left") + " (-X)", "",
|
||||
[](wxCommandEvent &) {
|
||||
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||
canvas3d->get_gizmos_manager().check_object_located_outside_plate(true);
|
||||
plater()->align_selection_x_min();
|
||||
},
|
||||
"", nullptr,
|
||||
[]() {
|
||||
if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D)
|
||||
return false;
|
||||
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||
GLGizmoAlignment alignment_helper(*canvas3d);
|
||||
return alignment_helper.can_align(GLGizmoAlignment::AlignType::X_MIN);
|
||||
},
|
||||
m_parent);
|
||||
|
||||
append_menu_item(
|
||||
align_distribute_menu, wxID_ANY, _L("Align left-right center") + " (X)", "",
|
||||
[](wxCommandEvent &) {
|
||||
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||
canvas3d->get_gizmos_manager().check_object_located_outside_plate(true);
|
||||
plater()->align_selection_x_center();
|
||||
},
|
||||
"", nullptr,
|
||||
[]() {
|
||||
if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D)
|
||||
return false;
|
||||
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||
GLGizmoAlignment alignment_helper(*canvas3d);
|
||||
return alignment_helper.can_align(GLGizmoAlignment::AlignType::CENTER_X);
|
||||
},
|
||||
m_parent);
|
||||
|
||||
append_menu_item(
|
||||
align_distribute_menu, wxID_ANY, _L("Align right") + " (+X)", "",
|
||||
[](wxCommandEvent &) {
|
||||
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||
canvas3d->get_gizmos_manager().check_object_located_outside_plate(true);
|
||||
plater()->align_selection_x_max();
|
||||
},
|
||||
"", nullptr,
|
||||
[]() {
|
||||
if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D)
|
||||
return false;
|
||||
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||
GLGizmoAlignment alignment_helper(*canvas3d);
|
||||
return alignment_helper.can_align(GLGizmoAlignment::AlignType::X_MAX);
|
||||
},
|
||||
m_parent);
|
||||
|
||||
align_distribute_menu->AppendSeparator();
|
||||
|
||||
append_menu_item(
|
||||
align_distribute_menu, wxID_ANY, _L("Align front") + " (-Y)", "",
|
||||
[](wxCommandEvent &) {
|
||||
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||
canvas3d->get_gizmos_manager().check_object_located_outside_plate(true);
|
||||
plater()->align_selection_y_min();
|
||||
},
|
||||
"", nullptr,
|
||||
[]() {
|
||||
if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D)
|
||||
return false;
|
||||
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||
GLGizmoAlignment alignment_helper(*canvas3d);
|
||||
return alignment_helper.can_align(GLGizmoAlignment::AlignType::Y_MIN);
|
||||
},
|
||||
m_parent);
|
||||
|
||||
append_menu_item(
|
||||
align_distribute_menu, wxID_ANY, _L("Align front-back center") + " (Y)", "",
|
||||
[](wxCommandEvent &) {
|
||||
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||
canvas3d->get_gizmos_manager().check_object_located_outside_plate(true);
|
||||
plater()->align_selection_y_center();
|
||||
},
|
||||
"", nullptr,
|
||||
[]() {
|
||||
if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D)
|
||||
return false;
|
||||
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||
GLGizmoAlignment alignment_helper(*canvas3d);
|
||||
return alignment_helper.can_align(GLGizmoAlignment::AlignType::CENTER_Y);
|
||||
},
|
||||
m_parent);
|
||||
|
||||
append_menu_item(
|
||||
align_distribute_menu, wxID_ANY, _L("Align back") + " (+Y)", "",
|
||||
[](wxCommandEvent &) {
|
||||
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||
canvas3d->get_gizmos_manager().check_object_located_outside_plate(true);
|
||||
plater()->align_selection_y_max();
|
||||
},
|
||||
"", nullptr,
|
||||
[]() {
|
||||
if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D)
|
||||
return false;
|
||||
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||
GLGizmoAlignment alignment_helper(*canvas3d);
|
||||
return alignment_helper.can_align(GLGizmoAlignment::AlignType::Y_MAX);
|
||||
},
|
||||
m_parent);
|
||||
|
||||
align_distribute_menu->AppendSeparator();
|
||||
|
||||
append_menu_item(
|
||||
align_distribute_menu, wxID_ANY, _L("Align bottom") + " (-Z)", "",
|
||||
[](wxCommandEvent &) {
|
||||
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||
canvas3d->get_gizmos_manager().check_object_located_outside_plate(true);
|
||||
plater()->align_selection_z_min();
|
||||
},
|
||||
"", nullptr,
|
||||
[]() {
|
||||
if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D)
|
||||
return false;
|
||||
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||
GLGizmoAlignment alignment_helper(*canvas3d);
|
||||
return alignment_helper.can_align(GLGizmoAlignment::AlignType::Z_MIN);
|
||||
},
|
||||
m_parent);
|
||||
|
||||
append_menu_item(
|
||||
align_distribute_menu, wxID_ANY, _L("Align top-bottom center") + " (Z)", "",
|
||||
[](wxCommandEvent &) {
|
||||
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||
canvas3d->get_gizmos_manager().check_object_located_outside_plate(true);
|
||||
plater()->align_selection_z_center();
|
||||
},
|
||||
"", nullptr,
|
||||
[]() {
|
||||
if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D)
|
||||
return false;
|
||||
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||
GLGizmoAlignment alignment_helper(*canvas3d);
|
||||
return alignment_helper.can_align(GLGizmoAlignment::AlignType::CENTER_Z);
|
||||
},
|
||||
m_parent);
|
||||
|
||||
append_menu_item(
|
||||
align_distribute_menu, wxID_ANY, _L("Align top") + " (+Z)", "",
|
||||
[](wxCommandEvent &) {
|
||||
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||
canvas3d->get_gizmos_manager().check_object_located_outside_plate(true);
|
||||
plater()->align_selection_z_max();
|
||||
},
|
||||
"", nullptr,
|
||||
[]() {
|
||||
if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D)
|
||||
return false;
|
||||
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||
GLGizmoAlignment alignment_helper(*canvas3d);
|
||||
return alignment_helper.can_align(GLGizmoAlignment::AlignType::Z_MAX);
|
||||
},
|
||||
m_parent);
|
||||
|
||||
append_submenu(menu, align_distribute_menu, wxID_ANY, _L("Align/Distribute"), _L("Align and distribute objects"), "",
|
||||
[]() {
|
||||
if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D)
|
||||
return false;
|
||||
auto canvas3d = plater()->get_view3D_canvas3D();
|
||||
GLGizmoAlignment alignment_helper(*canvas3d);
|
||||
|
||||
bool has_any_align = alignment_helper.can_align(GLGizmoAlignment::AlignType::CENTER_X) ||
|
||||
alignment_helper.can_align(GLGizmoAlignment::AlignType::CENTER_Y) ||
|
||||
alignment_helper.can_align(GLGizmoAlignment::AlignType::CENTER_Z) ||
|
||||
alignment_helper.can_align(GLGizmoAlignment::AlignType::X_MAX) ||
|
||||
alignment_helper.can_align(GLGizmoAlignment::AlignType::X_MIN) ||
|
||||
alignment_helper.can_align(GLGizmoAlignment::AlignType::Y_MAX) ||
|
||||
alignment_helper.can_align(GLGizmoAlignment::AlignType::Y_MIN) ||
|
||||
alignment_helper.can_align(GLGizmoAlignment::AlignType::Z_MAX) ||
|
||||
alignment_helper.can_align(GLGizmoAlignment::AlignType::Z_MIN);
|
||||
|
||||
bool has_any_distribute = alignment_helper.can_distribute(GLGizmoAlignment::AlignType::DISTRIBUTE_X) ||
|
||||
alignment_helper.can_distribute(GLGizmoAlignment::AlignType::DISTRIBUTE_Y) ||
|
||||
alignment_helper.can_distribute(GLGizmoAlignment::AlignType::DISTRIBUTE_Z);
|
||||
|
||||
return has_any_align || has_any_distribute;
|
||||
}, m_parent);
|
||||
}
|
||||
|
||||
void MenuFactory::append_menu_item_drop(wxMenu* menu)
|
||||
{
|
||||
append_menu_item(menu, wxID_ANY, _L("Drop") , "",
|
||||
|
||||
@@ -175,6 +175,7 @@ private:
|
||||
void append_menu_item_merge_to_single_object(wxMenu* menu);
|
||||
void append_menu_item_merge_parts_to_single_part(wxMenu *menu);
|
||||
void append_menu_items_mirror(wxMenu *menu);
|
||||
void append_menu_item_align_distribute(wxMenu *menu);
|
||||
void append_menu_item_precise_seam_submenu(wxMenu* menu);
|
||||
void append_menu_item_invalidate_cut_info(wxMenu *menu);
|
||||
void append_menu_item_edit_text(wxMenu *menu);
|
||||
|
||||
@@ -0,0 +1,694 @@
|
||||
#include "GLGizmoAlignment.hpp"
|
||||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
GLGizmoAlignment::GLGizmoAlignment(GLCanvas3D& canvas) : m_canvas(canvas)
|
||||
{
|
||||
}
|
||||
|
||||
bool GLGizmoAlignment::align_objects(AlignType type, bool align_parent)
|
||||
{
|
||||
if (!validate_selection_for_align()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case AlignType::CENTER_X: return align_to_center(type, align_parent);
|
||||
case AlignType::CENTER_Y: return align_to_center(type, align_parent);
|
||||
case AlignType::CENTER_Z: return align_to_center(type, align_parent);
|
||||
case AlignType::Y_MAX: return align_to_y_max(align_parent);
|
||||
case AlignType::Y_MIN: return align_to_y_min(align_parent);
|
||||
case AlignType::X_MAX: return align_to_x_max(align_parent);
|
||||
case AlignType::X_MIN: return align_to_x_min(align_parent);
|
||||
case AlignType::Z_MAX: return align_to_z_max(align_parent);
|
||||
case AlignType::Z_MIN: return align_to_z_min(align_parent);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool GLGizmoAlignment::distribute_objects(AlignType type)
|
||||
{
|
||||
if (!can_distribute(type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case AlignType::DISTRIBUTE_X:
|
||||
return distribute_x();
|
||||
case AlignType::DISTRIBUTE_Y:
|
||||
return distribute_y();
|
||||
case AlignType::DISTRIBUTE_Z:
|
||||
return distribute_z();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool GLGizmoAlignment::align_to_y_max(bool align_parent)
|
||||
{
|
||||
return align_objects_generic(
|
||||
[](const ObjectInfo& obj) { return obj.bbox.max.y(); },
|
||||
[](Vec3d& displacement, double target, double current_max) {
|
||||
displacement.y() = target - current_max;
|
||||
}, "Align Y Max", align_parent
|
||||
);
|
||||
}
|
||||
|
||||
bool GLGizmoAlignment::align_to_y_min(bool align_parent)
|
||||
{
|
||||
return align_objects_generic(
|
||||
[](const ObjectInfo& obj) { return obj.bbox.min.y(); },
|
||||
[](Vec3d& displacement, double target, double current_min) {
|
||||
displacement.y() = target - current_min;
|
||||
}, "Align Y Min", align_parent
|
||||
);
|
||||
}
|
||||
|
||||
bool GLGizmoAlignment::align_to_x_max(bool align_parent)
|
||||
{
|
||||
return align_objects_generic(
|
||||
[](const ObjectInfo& obj) { return obj.bbox.max.x(); },
|
||||
[](Vec3d& displacement, double target, double current_max) {
|
||||
displacement.x() = target - current_max;
|
||||
}, "Align X Max", align_parent
|
||||
);
|
||||
}
|
||||
|
||||
bool GLGizmoAlignment::align_to_x_min(bool align_parent)
|
||||
{
|
||||
return align_objects_generic(
|
||||
[](const ObjectInfo& obj) { return obj.bbox.min.x(); },
|
||||
[](Vec3d& displacement, double target, double current_min) {
|
||||
displacement.x() = target - current_min;
|
||||
}, "Align X Min", align_parent
|
||||
);
|
||||
}
|
||||
|
||||
bool GLGizmoAlignment::align_to_center(AlignType type,bool align_parent)
|
||||
{
|
||||
const Selection& selection = get_selection();
|
||||
std::string type_str = type == AlignType::CENTER_X ? "X" : (type == AlignType::CENTER_Y ? "Y" : "Z");
|
||||
std::string operation_name = type == AlignType::CENTER_X ? "X Center" : (type == AlignType::CENTER_Y ? "Y Center" : "Z Center");
|
||||
double selection_center = 0.f;
|
||||
if (align_parent) {
|
||||
selection_center = get_current_coord(operation_name, m_parent_box);
|
||||
|
||||
// Handle multiple volume selection
|
||||
if (is_part_align_parent()) {
|
||||
if (!take_snapshot("Align to parent center " + type_str)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
get_selection().setup_cache();
|
||||
|
||||
auto selection_bbox = selection.get_bounding_box();
|
||||
double current_coord = get_current_coord(operation_name, selection_bbox);
|
||||
Vec3d displacement = generate_displacement(operation_name, selection_center - current_coord);
|
||||
// Move each selected volume to align to the center of their collective bounding box
|
||||
for (unsigned int idx : selection.get_volume_idxs()) {
|
||||
const GLVolume *volume = selection.get_volume(idx);
|
||||
if (volume) {
|
||||
get_selection().translate(volume->object_idx(), volume->instance_idx(), volume->volume_idx(), displacement);
|
||||
}
|
||||
}
|
||||
finish_operation("Align to parent center " + type_str);
|
||||
return true;
|
||||
}
|
||||
BoundingBoxf3 big_bb;
|
||||
auto objects = get_selected_objects_info(big_bb);
|
||||
if (objects.empty()) return false;
|
||||
// Handle multiple objects or parts selection
|
||||
|
||||
BoundingBoxf3 selection_bbox = selection.get_bounding_box();
|
||||
if (!take_snapshot("Align to parent center " + type_str)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
get_selection().setup_cache();
|
||||
double current_coord = get_current_coord(operation_name, big_bb);
|
||||
Vec3d displacement = generate_displacement(operation_name, selection_center - current_coord);
|
||||
// Multiple objects: use selection bounding box for alignment
|
||||
for (const auto &obj : objects) {
|
||||
apply_transformation(obj.object_idx, obj.instance_idx, displacement);
|
||||
}
|
||||
|
||||
finish_operation("Align to parent center " + type_str);
|
||||
return true;
|
||||
}
|
||||
// Handle single object selection// Handle multiple volume selection
|
||||
if (selection.is_single_full_object() ||
|
||||
selection.is_multiple_volume() || selection.is_multiple_modifier() || selection.is_single_volume() || selection.is_single_modifier()) {
|
||||
if (!take_snapshot("Align Parts Center " + type_str)){
|
||||
return false;
|
||||
}
|
||||
|
||||
get_selection().setup_cache();
|
||||
|
||||
BoundingBoxf3 selection_bbox = selection.get_bounding_box();
|
||||
selection_center = get_current_coord(operation_name, selection_bbox);
|
||||
|
||||
// Move each selected volume to align to the center of their collective bounding box
|
||||
for (unsigned int idx : selection.get_volume_idxs()) {
|
||||
const GLVolume* volume = selection.get_volume(idx);
|
||||
if (volume) {
|
||||
auto temp_box = volume->transformed_convex_hull_bounding_box();
|
||||
double current = get_current_coord(operation_name, temp_box);
|
||||
double offset = selection_center - current;
|
||||
Vec3d displacement = generate_displacement(operation_name, offset);
|
||||
get_selection().translate(volume->object_idx(), volume->instance_idx(), volume->volume_idx(), displacement);
|
||||
}
|
||||
}
|
||||
|
||||
finish_operation("Align Parts Center" + type_str, true);
|
||||
return true;
|
||||
}
|
||||
BoundingBoxf3 big_bb;
|
||||
auto objects = get_selected_objects_info(big_bb);
|
||||
if (objects.empty()) return false;
|
||||
// Handle multiple objects or parts selection
|
||||
if (selection.is_multiple_full_object()) {
|
||||
BoundingBoxf3 selection_bbox = selection.get_bounding_box();
|
||||
double selection_center = get_current_coord(operation_name, selection_bbox);
|
||||
|
||||
if (!take_snapshot("Align Objects Center" + type_str)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
get_selection().setup_cache();
|
||||
// Multiple objects: use selection bounding box for alignment
|
||||
for (const auto& obj : objects) {
|
||||
BoundingBoxf3 temp_bbox = obj.bbox;
|
||||
double current_coord = get_current_coord(operation_name, temp_bbox);
|
||||
Vec3d displacement = generate_displacement(operation_name, selection_center - current_coord);
|
||||
apply_transformation(obj.object_idx, obj.instance_idx, displacement);
|
||||
}
|
||||
finish_operation("Align Objects Center" + type_str);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GLGizmoAlignment::align_to_z_max(bool align_parent)
|
||||
{
|
||||
return align_objects_generic(
|
||||
[](const ObjectInfo& obj) { return obj.bbox.max.z(); },
|
||||
[](Vec3d& displacement, double target, double current_max) {
|
||||
displacement.z() = target - current_max;
|
||||
}, "Align Z Max", align_parent
|
||||
);
|
||||
}
|
||||
|
||||
bool GLGizmoAlignment::align_to_z_min(bool align_parent)
|
||||
{
|
||||
return align_objects_generic(
|
||||
[](const ObjectInfo& obj) { return obj.bbox.min.z(); },
|
||||
[](Vec3d& displacement, double target, double current_min) {
|
||||
displacement.z() = target - current_min;
|
||||
}, "Align Z Min", align_parent
|
||||
);
|
||||
}
|
||||
|
||||
bool GLGizmoAlignment::distribute_y()
|
||||
{
|
||||
return distribute_objects_generic(
|
||||
[](const ObjectInfo& obj) { return obj.center.y(); },
|
||||
1,
|
||||
"Distribute Y"
|
||||
);
|
||||
}
|
||||
|
||||
bool GLGizmoAlignment::distribute_x()
|
||||
{
|
||||
return distribute_objects_generic(
|
||||
[](const ObjectInfo& obj) { return obj.center.x(); },
|
||||
0,
|
||||
"Distribute X"
|
||||
);
|
||||
}
|
||||
|
||||
bool GLGizmoAlignment::distribute_z()
|
||||
{
|
||||
return distribute_objects_generic(
|
||||
[](const ObjectInfo& obj) { return obj.center.z(); },
|
||||
2,
|
||||
"Distribute Z"
|
||||
);
|
||||
}
|
||||
|
||||
template<typename GetCoordFunc, typename SetCoordFunc>
|
||||
bool GLGizmoAlignment::align_objects_generic(GetCoordFunc get_coord, SetCoordFunc set_coord, const std::string &operation_name ,bool align_parent)
|
||||
{
|
||||
const Selection& selection = get_selection();
|
||||
if (align_parent) {
|
||||
// Find reference coordinate
|
||||
double reference_coord = get_current_coord(operation_name, m_parent_box);
|
||||
// Handle parts selection differently
|
||||
if (is_part_align_parent()) {
|
||||
if (!take_snapshot("align to parent node " + operation_name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
get_selection().setup_cache();
|
||||
|
||||
// Get all selected volumes
|
||||
std::vector<const GLVolume *> volumes;
|
||||
for (unsigned int idx : selection.get_volume_idxs()) {
|
||||
const GLVolume *volume = selection.get_volume(idx);
|
||||
if (volume) { volumes.push_back(volume); }
|
||||
}
|
||||
|
||||
if (volumes.empty())
|
||||
return false;
|
||||
BoundingBoxf3 selection_bbox = selection.get_bounding_box();
|
||||
double current_coord = get_current_coord(operation_name, selection_bbox);
|
||||
Vec3d displacement = generate_displacement(operation_name, reference_coord - current_coord);
|
||||
for (const GLVolume *volume : volumes) {
|
||||
if (displacement.norm() > 1e-6) {
|
||||
get_selection().translate(volume->object_idx(), volume->instance_idx(), volume->volume_idx(), displacement);
|
||||
}
|
||||
}
|
||||
|
||||
finish_operation("align to parent node " + operation_name);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle objects selection (original logic)
|
||||
BoundingBoxf3 big_bb;
|
||||
auto objects = get_selected_objects_info(big_bb);
|
||||
if (objects.empty()) return false;
|
||||
|
||||
if (!take_snapshot("align to parent node " + operation_name)) { return false; }
|
||||
|
||||
get_selection().setup_cache();
|
||||
|
||||
double current_coord = get_current_coord(operation_name, big_bb);
|
||||
Vec3d displacement = generate_displacement(operation_name, reference_coord - current_coord);
|
||||
|
||||
for (const auto &obj : objects) {
|
||||
if (displacement.norm() > 1e-6) {
|
||||
apply_transformation(obj.object_idx, obj.instance_idx, displacement);
|
||||
}
|
||||
}
|
||||
|
||||
finish_operation("align to parent node " + operation_name);
|
||||
return true;
|
||||
}
|
||||
// Handle parts selection differently
|
||||
if (selection.is_single_full_object() || selection.is_multiple_volume() || selection.is_multiple_modifier()) {
|
||||
if (!take_snapshot(operation_name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
get_selection().setup_cache();
|
||||
|
||||
// Get all selected volumes
|
||||
std::vector<const GLVolume*> volumes;
|
||||
for (unsigned int idx : selection.get_volume_idxs()) {
|
||||
const GLVolume* volume = selection.get_volume(idx);
|
||||
if (volume) {
|
||||
volumes.push_back(volume);
|
||||
}
|
||||
}
|
||||
|
||||
if (volumes.empty()) return false;
|
||||
|
||||
// Find reference coordinate
|
||||
double reference_coord = 0.0;
|
||||
bool first = true;
|
||||
|
||||
for (const GLVolume* volume : volumes) {
|
||||
BoundingBoxf3 bbox = volume->transformed_convex_hull_bounding_box();
|
||||
double coord = 0.0;
|
||||
|
||||
if (operation_name.find("X Max") != std::string::npos) {
|
||||
coord = bbox.max.x();
|
||||
} else if (operation_name.find("X Min") != std::string::npos) {
|
||||
coord = bbox.min.x();
|
||||
} else if (operation_name.find("Y Max") != std::string::npos) {
|
||||
coord = bbox.max.y();
|
||||
} else if (operation_name.find("Y Min") != std::string::npos) {
|
||||
coord = bbox.min.y();
|
||||
} else if (operation_name.find("Z Max") != std::string::npos) {
|
||||
coord = bbox.max.z();
|
||||
} else if (operation_name.find("Z Min") != std::string::npos) {
|
||||
coord = bbox.min.z();
|
||||
}
|
||||
|
||||
if (first) {
|
||||
reference_coord = coord;
|
||||
first = false;
|
||||
} else {
|
||||
if (operation_name.find("Max") != std::string::npos) {
|
||||
reference_coord = std::max(reference_coord, coord);
|
||||
} else if (operation_name.find("Min") != std::string::npos) {
|
||||
reference_coord = std::min(reference_coord, coord);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const GLVolume* volume : volumes) {
|
||||
BoundingBoxf3 bbox = volume->transformed_convex_hull_bounding_box();
|
||||
double current_coord = 0.0;
|
||||
|
||||
if (operation_name.find("X Max") != std::string::npos) {
|
||||
current_coord = bbox.max.x();
|
||||
} else if (operation_name.find("X Min") != std::string::npos) {
|
||||
current_coord = bbox.min.x();
|
||||
} else if (operation_name.find("Y Max") != std::string::npos) {
|
||||
current_coord = bbox.max.y();
|
||||
} else if (operation_name.find("Y Min") != std::string::npos) {
|
||||
current_coord = bbox.min.y();
|
||||
} else if (operation_name.find("Z Max") != std::string::npos) {
|
||||
current_coord = bbox.max.z();
|
||||
} else if (operation_name.find("Z Min") != std::string::npos) {
|
||||
current_coord = bbox.min.z();
|
||||
}
|
||||
|
||||
Vec3d displacement = Vec3d::Zero();
|
||||
|
||||
if (operation_name.find("X") != std::string::npos) {
|
||||
displacement.x() = reference_coord - current_coord;
|
||||
} else if (operation_name.find("Y") != std::string::npos) {
|
||||
displacement.y() = reference_coord - current_coord;
|
||||
} else if (operation_name.find("Z") != std::string::npos) {
|
||||
displacement.z() = reference_coord - current_coord;
|
||||
}
|
||||
|
||||
if (displacement.norm() > 1e-6) {
|
||||
get_selection().translate(volume->object_idx(), volume->instance_idx(), volume->volume_idx(), displacement);
|
||||
}
|
||||
}
|
||||
|
||||
finish_operation(operation_name,true);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle objects selection (original logic)
|
||||
BoundingBoxf3 big_bb;
|
||||
auto objects = get_selected_objects_info(big_bb);
|
||||
if (objects.empty()) return false;
|
||||
|
||||
if (!take_snapshot(operation_name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
get_selection().setup_cache();
|
||||
|
||||
double reference_coord = get_coord(objects[0]);
|
||||
for (const auto& obj : objects) {
|
||||
double coord = get_coord(obj);
|
||||
if (operation_name.find("Max") != std::string::npos) {
|
||||
reference_coord = std::max(reference_coord, coord);
|
||||
} else if (operation_name.find("Min") != std::string::npos) {
|
||||
reference_coord = std::min(reference_coord, coord);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& obj : objects) {
|
||||
double current_coord = get_coord(obj);
|
||||
Vec3d displacement = Vec3d::Zero();
|
||||
set_coord(displacement, reference_coord, current_coord);
|
||||
|
||||
if (displacement.norm() > 1e-6) {
|
||||
apply_transformation(obj.object_idx, obj.instance_idx, displacement);
|
||||
}
|
||||
}
|
||||
|
||||
finish_operation(operation_name);
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename GetCoordFunc>
|
||||
bool GLGizmoAlignment::distribute_objects_generic(GetCoordFunc get_coord, int axis, const std::string& operation_name)
|
||||
{
|
||||
const Selection& selection = get_selection();
|
||||
|
||||
if (selection.is_single_full_object() || selection.is_multiple_volume() || selection.is_multiple_modifier()) {
|
||||
std::vector<const GLVolume*> volumes;
|
||||
for (unsigned int idx : selection.get_volume_idxs()) {
|
||||
const GLVolume* v = selection.get_volume(idx);
|
||||
if (v) volumes.push_back(v);
|
||||
}
|
||||
|
||||
if (volumes.size() < 3) return false;
|
||||
if (!take_snapshot(operation_name)) {
|
||||
return false;
|
||||
}
|
||||
get_selection().setup_cache();
|
||||
|
||||
struct VolEntry { const GLVolume* v; double coord; };
|
||||
std::vector<VolEntry> entries;
|
||||
entries.reserve(volumes.size());
|
||||
for (const GLVolume* v : volumes) {
|
||||
BoundingBoxf3 bbox = v->transformed_convex_hull_bounding_box();
|
||||
Vec3d c = bbox.center();
|
||||
double coord = (axis == 0 ? c.x() : axis == 1 ? c.y() : c.z());
|
||||
entries.push_back({v, coord});
|
||||
}
|
||||
|
||||
std::sort(entries.begin(), entries.end(), [](const VolEntry& a, const VolEntry& b){ return a.coord < b.coord; });
|
||||
|
||||
BoundingBoxf3 selection_bbox = selection.get_bounding_box();
|
||||
BoundingBoxf3 left_bbox = entries.front().v->transformed_convex_hull_bounding_box();
|
||||
BoundingBoxf3 right_bbox = entries.back().v->transformed_convex_hull_bounding_box();
|
||||
auto axis_extent = [&](const BoundingBoxf3 &bb) { return (axis == 0 ? (bb.max.x() - bb.min.x()) : axis == 1 ? (bb.max.y() - bb.min.y()) : (bb.max.z() - bb.min.z())); };
|
||||
double left_width = axis_extent(left_bbox);
|
||||
double right_width = axis_extent(right_bbox);
|
||||
double bbox_min = (axis == 0 ? selection_bbox.min.x() : axis == 1 ? selection_bbox.min.y() : selection_bbox.min.z());
|
||||
double bbox_max = (axis == 0 ? selection_bbox.max.x() : axis == 1 ? selection_bbox.max.y() : selection_bbox.max.z());
|
||||
double min_center = bbox_min + left_width * 0.5;
|
||||
double max_center = bbox_max - right_width * 0.5;
|
||||
double total_distance = max_center - min_center;
|
||||
double interval = (entries.size() > 1) ? total_distance / (entries.size() - 1) : 0;
|
||||
for (size_t i = 1; i < entries.size() - 1; ++i) {
|
||||
double target_coord = min_center + i * interval;
|
||||
double current_coord = entries[i].coord;
|
||||
double offset = target_coord - current_coord;
|
||||
|
||||
if (std::abs(offset) > 1e-6) {
|
||||
Vec3d displacement = Vec3d::Zero();
|
||||
displacement[axis] = offset;
|
||||
const GLVolume *v = entries[i].v;
|
||||
get_selection().translate(v->object_idx(), v->instance_idx(), v->volume_idx(), displacement);
|
||||
}
|
||||
}
|
||||
|
||||
finish_operation(operation_name,true);
|
||||
return true;
|
||||
}
|
||||
BoundingBoxf3 big_bb;
|
||||
auto objects = get_selected_objects_info(big_bb);
|
||||
if (objects.size() < 3) return false;
|
||||
|
||||
if (!take_snapshot(operation_name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
get_selection().setup_cache();
|
||||
|
||||
std::sort(objects.begin(), objects.end(),
|
||||
[&get_coord](const ObjectInfo& a, const ObjectInfo& b) {
|
||||
return get_coord(a) < get_coord(b);
|
||||
});
|
||||
|
||||
double min_coord = get_coord(objects.front());
|
||||
double max_coord = get_coord(objects.back());
|
||||
double total_distance = max_coord - min_coord;
|
||||
double interval = (objects.size() > 1) ? total_distance / (objects.size() - 1) : 0;
|
||||
for (size_t i = 1; i < objects.size() - 1; ++i) {
|
||||
double target_coord = min_coord + i * interval;
|
||||
double current_coord = get_coord(objects[i]);
|
||||
double offset = target_coord - current_coord;
|
||||
|
||||
if (std::abs(offset) > 1e-6) {
|
||||
Vec3d displacement = Vec3d::Zero();
|
||||
displacement[axis] = offset;
|
||||
apply_transformation(objects[i].object_idx, objects[i].instance_idx, displacement);
|
||||
}
|
||||
}
|
||||
|
||||
finish_operation(operation_name);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GLGizmoAlignment::can_align(AlignType type) const
|
||||
{
|
||||
const Selection& selection = get_selection();
|
||||
|
||||
bool is_single_object = selection.is_single_full_object();
|
||||
bool is_multiple_objects = selection.is_multiple_full_object();
|
||||
bool is_single_part = selection.is_single_volume() || selection.is_single_modifier();
|
||||
bool is_multiple_parts = selection.is_multiple_volume() || selection.is_multiple_modifier();
|
||||
|
||||
|
||||
return validate_selection_for_align();
|
||||
}
|
||||
|
||||
bool GLGizmoAlignment::can_distribute(AlignType type) const
|
||||
{
|
||||
const Selection& selection = get_selection();
|
||||
if (selection.is_single_full_object()) {
|
||||
size_t count = selection.get_volume_idxs().size();
|
||||
if (count >= 3)
|
||||
return true;
|
||||
}
|
||||
if (selection.is_single_volume() ||
|
||||
selection.is_single_modifier()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (selection.is_multiple_volume() || selection.is_multiple_modifier()) {
|
||||
size_t count = selection.get_volume_idxs().size();
|
||||
if (count < 3) return false;
|
||||
return (type == AlignType::DISTRIBUTE_X ||
|
||||
type == AlignType::DISTRIBUTE_Y ||
|
||||
type == AlignType::DISTRIBUTE_Z);
|
||||
}
|
||||
|
||||
if (selection.is_multiple_full_object()) {
|
||||
BoundingBoxf3 big_bb;
|
||||
auto objects = get_selected_objects_info(big_bb);
|
||||
if (objects.size() < 3) return false;
|
||||
return (type == AlignType::DISTRIBUTE_X || type == AlignType::DISTRIBUTE_Y || type == AlignType::DISTRIBUTE_Z);
|
||||
}
|
||||
|
||||
return validate_selection_for_distribute();
|
||||
}
|
||||
|
||||
double GLGizmoAlignment::get_current_coord(std::string operation_name, BoundingBoxf3 &bbox)
|
||||
{
|
||||
double current_coord = 0.0;
|
||||
if (operation_name.find("X Max") != std::string::npos) {
|
||||
current_coord = bbox.max.x();
|
||||
} else if (operation_name.find("X Min") != std::string::npos) {
|
||||
current_coord = bbox.min.x();
|
||||
} else if (operation_name.find("Y Max") != std::string::npos) {
|
||||
current_coord = bbox.max.y();
|
||||
} else if (operation_name.find("Y Min") != std::string::npos) {
|
||||
current_coord = bbox.min.y();
|
||||
} else if (operation_name.find("Z Max") != std::string::npos) {
|
||||
current_coord = bbox.max.z();
|
||||
} else if (operation_name.find("Z Min") != std::string::npos) {
|
||||
current_coord = bbox.min.z();
|
||||
} else if (operation_name.find("X Center") != std::string::npos) {
|
||||
current_coord = bbox.center().x();
|
||||
} else if (operation_name.find("Y Center") != std::string::npos) {
|
||||
current_coord = bbox.center().y();
|
||||
} else if (operation_name.find("Z Center") != std::string::npos) {
|
||||
current_coord = bbox.center().z();
|
||||
}
|
||||
return current_coord;
|
||||
}
|
||||
|
||||
Vec3d GLGizmoAlignment::generate_displacement(std::string operation_name, double offset) {
|
||||
Vec3d displacement = Vec3d::Zero();
|
||||
if (operation_name.find("X") != std::string::npos) {
|
||||
displacement.x() = offset;
|
||||
} else if (operation_name.find("Y") != std::string::npos) {
|
||||
displacement.y() = offset;
|
||||
} else if (operation_name.find("Z") != std::string::npos) {
|
||||
displacement.z() = offset;
|
||||
}
|
||||
return displacement;
|
||||
}
|
||||
|
||||
bool GLGizmoAlignment::is_part_align_parent()
|
||||
{
|
||||
Selection &selection = get_selection();
|
||||
if (selection.is_multiple_volume() || selection.is_multiple_modifier() || selection.is_single_volume() || selection.is_single_modifier()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<GLGizmoAlignment::ObjectInfo> GLGizmoAlignment::get_selected_objects_info(BoundingBoxf3 &big_bb) const
|
||||
{
|
||||
std::vector<ObjectInfo> objects;
|
||||
Selection& selection = get_selection();
|
||||
|
||||
if (selection.is_empty()) return objects;
|
||||
|
||||
const auto& content = selection.get_content();
|
||||
for (const auto& obj_it : content) {
|
||||
for (const auto& inst_idx : obj_it.second) {
|
||||
int obj_idx = obj_it.first;
|
||||
|
||||
if (obj_idx >= 0 && obj_idx < selection.get_model()->objects.size()) {
|
||||
ModelObject* object = selection.get_model()->objects[obj_idx];
|
||||
if (inst_idx >= 0 && inst_idx < object->instances.size()) {
|
||||
BoundingBoxf3 bbox = object->instance_bounding_box(inst_idx);
|
||||
big_bb.merge(bbox);
|
||||
objects.emplace_back(obj_idx, inst_idx, bbox);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return objects;
|
||||
}
|
||||
|
||||
void GLGizmoAlignment::set_parent_box(const BoundingBoxf3 &bb) {
|
||||
m_parent_box = bb;
|
||||
}
|
||||
|
||||
Selection& GLGizmoAlignment::get_selection() const
|
||||
{
|
||||
return m_canvas.get_selection();
|
||||
}
|
||||
|
||||
bool GLGizmoAlignment::take_snapshot(const std::string& name)
|
||||
{
|
||||
wxGetApp().plater()->take_snapshot(name);
|
||||
return true;
|
||||
}
|
||||
|
||||
void GLGizmoAlignment::apply_transformation(int obj_idx, int inst_idx, const Vec3d& displacement)
|
||||
{
|
||||
get_selection().translate(obj_idx, inst_idx, displacement);
|
||||
}
|
||||
|
||||
void GLGizmoAlignment::finish_operation(const std::string &operation_name, bool force_volume_move)
|
||||
{
|
||||
get_selection().notify_instance_update(-1, -1);
|
||||
m_canvas.do_move(operation_name, force_volume_move);
|
||||
}
|
||||
|
||||
bool GLGizmoAlignment::validate_selection_for_align() const
|
||||
{
|
||||
const Selection& selection = get_selection();
|
||||
|
||||
if (selection.is_single_full_object() ||
|
||||
selection.is_single_volume() ||
|
||||
selection.is_single_modifier()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return selection.get_volume_idxs().size() >= 2;
|
||||
}
|
||||
|
||||
bool GLGizmoAlignment::validate_selection_for_distribute() const
|
||||
{
|
||||
const Selection& selection = get_selection();
|
||||
|
||||
if (selection.is_single_full_object() || selection.is_multiple_full_object()) {
|
||||
std::set<int> object_indices;
|
||||
for (int volume_idx : selection.get_volume_idxs()) {
|
||||
const GLVolume* volume = selection.get_volume(volume_idx);
|
||||
if (volume) {
|
||||
object_indices.insert(volume->object_idx());
|
||||
}
|
||||
}
|
||||
return object_indices.size() >= 3;
|
||||
}
|
||||
|
||||
return selection.get_volume_idxs().size() >= 3;
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
@@ -0,0 +1,95 @@
|
||||
#ifndef slic3r_GLGizmoAlignment_hpp_
|
||||
#define slic3r_GLGizmoAlignment_hpp_
|
||||
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "slic3r/GUI/Selection.hpp"
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
class GLCanvas3D;
|
||||
|
||||
class GLGizmoAlignment
|
||||
{
|
||||
public:
|
||||
enum class AlignType {
|
||||
NONE = -1,
|
||||
CENTER_X,
|
||||
CENTER_Y,
|
||||
CENTER_Z,
|
||||
Y_MAX,
|
||||
Y_MIN,
|
||||
X_MAX,
|
||||
X_MIN,
|
||||
Z_MAX,
|
||||
Z_MIN,
|
||||
DISTRIBUTE_X,
|
||||
DISTRIBUTE_Y,
|
||||
DISTRIBUTE_Z
|
||||
};
|
||||
struct ObjectInfo {
|
||||
int object_idx;
|
||||
int instance_idx;
|
||||
BoundingBoxf3 bbox;
|
||||
Vec3d center;
|
||||
|
||||
ObjectInfo(int obj_idx, int inst_idx, const BoundingBoxf3& bb)
|
||||
: object_idx(obj_idx), instance_idx(inst_idx), bbox(bb), center(bb.center()) {}
|
||||
};
|
||||
|
||||
explicit GLGizmoAlignment(GLCanvas3D& canvas);
|
||||
~GLGizmoAlignment() = default;
|
||||
|
||||
bool align_objects(AlignType type,bool align_parent = false);
|
||||
bool distribute_objects(AlignType type);
|
||||
|
||||
bool align_to_center(AlignType type,bool align_parent = false);
|
||||
bool align_to_y_max(bool align_parent = false);
|
||||
bool align_to_y_min(bool align_parent = false);
|
||||
bool align_to_x_max(bool align_parent = false);
|
||||
bool align_to_x_min(bool align_parent = false);
|
||||
bool align_to_z_max(bool align_parent = false);
|
||||
bool align_to_z_min(bool align_parent = false);
|
||||
|
||||
bool distribute_x();
|
||||
bool distribute_y();
|
||||
bool distribute_z();
|
||||
|
||||
bool can_align(AlignType type) const;
|
||||
bool can_distribute(AlignType type) const;
|
||||
|
||||
std::vector<ObjectInfo> get_selected_objects_info(BoundingBoxf3 &big_bb) const;
|
||||
double get_current_coord(std::string operation_name, BoundingBoxf3 &bbox);
|
||||
Vec3d generate_displacement(std::string operation_name, double offset);
|
||||
bool is_part_align_parent();
|
||||
void set_parent_box(const BoundingBoxf3 &bb);
|
||||
|
||||
private:
|
||||
GLCanvas3D& m_canvas;
|
||||
|
||||
template<typename GetCoordFunc, typename SetCoordFunc>
|
||||
bool align_objects_generic(GetCoordFunc get_coord, SetCoordFunc set_coord, const std::string &operation_name, bool align_parent = false);
|
||||
|
||||
template<typename GetCoordFunc>
|
||||
bool distribute_objects_generic(GetCoordFunc get_coord, int axis,
|
||||
const std::string& operation_name);
|
||||
|
||||
Selection& get_selection() const;
|
||||
bool take_snapshot(const std::string& name);
|
||||
void apply_transformation(int obj_idx, int inst_idx, const Vec3d& displacement);
|
||||
void finish_operation(const std::string &operation_name, bool force_volume_move = false);
|
||||
|
||||
bool validate_selection_for_align() const;
|
||||
bool validate_selection_for_distribute() const;
|
||||
|
||||
private:
|
||||
BoundingBoxf3 m_parent_box;
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GLGizmoAlignment_hpp_
|
||||
@@ -470,6 +470,37 @@ bool GLGizmosManager::check_gizmos_closed_except(EType type) const
|
||||
return true;
|
||||
}
|
||||
|
||||
// Switches the active plate to the one containing the first selected object,
|
||||
// so that subsequent operations (align/distribute) act on a plate the user
|
||||
// can actually see.
|
||||
void GLGizmosManager::check_object_located_outside_plate(bool change_plate)
|
||||
{
|
||||
PartPlateList& plate_list = wxGetApp().plater()->get_partplate_list();
|
||||
auto curr_plate_index = plate_list.get_curr_plate_index();
|
||||
Selection& selection = m_parent.get_selection();
|
||||
auto idxs = selection.get_volume_idxs();
|
||||
if (idxs.empty())
|
||||
return;
|
||||
|
||||
const GLVolume* v = selection.get_volume(*idxs.begin());
|
||||
int object_idx = v->object_idx();
|
||||
const Model* model = m_parent.get_model();
|
||||
if (object_idx < 0 || object_idx >= (int) model->objects.size())
|
||||
return;
|
||||
|
||||
ModelObject* model_object = model->objects[object_idx];
|
||||
for (size_t i = 0; i < plate_list.get_plate_count(); ++i) {
|
||||
auto plate = plate_list.get_plate(i);
|
||||
for (auto object : plate->get_objects_on_this_plate()) {
|
||||
if (model_object == object) {
|
||||
if (change_plate && curr_plate_index != (int) i)
|
||||
plate_list.select_plate(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GLGizmosManager::set_hover_id(int id)
|
||||
{
|
||||
// Measure and assembly handles hover by itself
|
||||
|
||||
@@ -261,6 +261,7 @@ public:
|
||||
void reset_all_states();
|
||||
bool open_gizmo(EType type);
|
||||
bool check_gizmos_closed_except(EType) const;
|
||||
void check_object_located_outside_plate(bool change_plate = true);
|
||||
|
||||
void set_hover_id(int id);
|
||||
|
||||
|
||||
+236
-57
@@ -245,6 +245,7 @@
|
||||
#include "Widgets/MultiNozzleSync.hpp" // NozzleOption, tryPopUpMultiNozzleDialog, setExtruderNozzleCount
|
||||
#include "DeviceCore/DevNozzleSystem.h" // DevNozzle, GetExtNozzles / GetRackNozzles
|
||||
#include "ProjectDirtyStateManager.hpp"
|
||||
#include "Gizmos/GLGizmoAlignment.hpp"
|
||||
#include "Gizmos/GLGizmoSimplify.hpp" // create suggestion notification
|
||||
#include "Gizmos/GLGizmoSVG.hpp" // Drop SVG file
|
||||
#include "Gizmos/GizmoObjectManipulation.hpp"
|
||||
@@ -6013,7 +6014,8 @@ void Sidebar::add_custom_filament(wxColour new_col, const std::string& preset_na
|
||||
|
||||
bool Sidebar::is_new_project_in_gcode3mf()
|
||||
{
|
||||
if (p->plater->using_exported_file()) {
|
||||
// A .gcode previewed with its own settings shows them here too, and is closed the same way.
|
||||
if (p->plater->using_exported_file() || p->plater->gcode_settings_loaded()) {
|
||||
auto filename = p->plater->get_preview_only_filename();
|
||||
auto text = wxString::Format(_L("After completing your operation, %s project will be closed and create a new project."), filename);
|
||||
MessageDialog dlg(wxGetApp().plater(), text, _L("Warning"),
|
||||
@@ -7367,6 +7369,9 @@ struct Plater::priv
|
||||
void enable_sidebar(bool enabled);
|
||||
void collapse_sidebar(bool collapse);
|
||||
void update_sidebar(bool force_update = false);
|
||||
// The sidebar shows the selected presets, which a G-code previewed without its own settings has nothing to do
|
||||
// with, so it stays hidden then. With the file's settings loaded it shows them, as for a .gcode.3mf.
|
||||
bool gcode_preview_without_settings() const { return q->m_only_gcode && !q->m_gcode_settings_loaded; }
|
||||
void reset_window_layout();
|
||||
Sidebar::DockingState get_sidebar_docking_state();
|
||||
void add_dock_pane(wxWindow* window, const std::string& name, const wxString& caption, const std::string& dock,
|
||||
@@ -7480,6 +7485,18 @@ struct Plater::priv
|
||||
void delete_all_objects_from_model();
|
||||
void reset(bool apply_presets_change = false, bool reload_presets = true);
|
||||
void center_selection();
|
||||
void distribute_selection_x();
|
||||
void distribute_selection_y();
|
||||
void distribute_selection_z();
|
||||
void align_selection_x_min();
|
||||
void align_selection_x_center();
|
||||
void align_selection_x_max();
|
||||
void align_selection_y_min();
|
||||
void align_selection_y_center();
|
||||
void align_selection_y_max();
|
||||
void align_selection_z_min();
|
||||
void align_selection_z_center();
|
||||
void align_selection_z_max();
|
||||
void drop_selection();
|
||||
void mirror(Axis axis);
|
||||
void split_object(bool auto_drop = true);
|
||||
@@ -8592,7 +8609,7 @@ void Plater::priv::select_next_view_3D()
|
||||
|
||||
void Plater::priv::enable_sidebar(bool enabled)
|
||||
{
|
||||
if (q->m_only_gcode)
|
||||
if (gcode_preview_without_settings())
|
||||
enabled = false;
|
||||
|
||||
sidebar_layout.is_enabled = enabled;
|
||||
@@ -8601,7 +8618,7 @@ void Plater::priv::enable_sidebar(bool enabled)
|
||||
|
||||
void Plater::priv::collapse_sidebar(bool collapse)
|
||||
{
|
||||
if (q->m_only_gcode)
|
||||
if (gcode_preview_without_settings())
|
||||
return;
|
||||
|
||||
sidebar_layout.is_collapsed = collapse;
|
||||
@@ -8864,6 +8881,64 @@ static void log_substitutions(const PresetsConfigSubstitutions& substitutions, c
|
||||
log_substitutions(preset.substitutions, source + " (preset " + preset.preset_name + ")");
|
||||
}
|
||||
|
||||
// After the settings of a project or a G-code were loaded into the presets: size the per-filament project
|
||||
// lists to the loaded filament count, and drop state only a connected printer may set.
|
||||
static void sync_loaded_project_filament_config(DynamicConfig& proj_cfg)
|
||||
{
|
||||
ConfigOptionStrings* filament_color = proj_cfg.opt<ConfigOptionStrings>("filament_colour");
|
||||
if (filament_color) {
|
||||
size_t filament_count = filament_color->size();
|
||||
|
||||
// Sync filament map
|
||||
ConfigOptionInts* filament_map = proj_cfg.opt<ConfigOptionInts>("filament_map", true);
|
||||
if (filament_map->size() != filament_count) {
|
||||
filament_map->values.resize(filament_count, 1);
|
||||
}
|
||||
|
||||
ConfigOptionInts* filament_nozzle_map = proj_cfg.opt<ConfigOptionInts>("filament_nozzle_map", true);
|
||||
if (filament_nozzle_map->size() != filament_count) {
|
||||
filament_nozzle_map->values.resize(filament_count, 0);
|
||||
}
|
||||
|
||||
ConfigOptionInts* filament_volume_map = proj_cfg.opt<ConfigOptionInts>("filament_volume_map", true);
|
||||
if (filament_volume_map->size() != filament_count) {
|
||||
filament_volume_map->values.resize(filament_count, static_cast<int>(NozzleVolumeType::nvtStandard));
|
||||
}
|
||||
|
||||
// Sync filament multi colour
|
||||
ConfigOptionStrings* filament_multi_color = proj_cfg.opt<ConfigOptionStrings>("filament_multi_colour", true);
|
||||
if (filament_multi_color->size() != filament_count) {
|
||||
filament_multi_color->values.resize(filament_count);
|
||||
}
|
||||
// If there is no multi-color data or color is not match, use single color as default value
|
||||
for (size_t i = 0; i < filament_count; i++) {
|
||||
std::vector<std::string> colors = Slic3r::split_string(filament_multi_color->values[i], ' ');
|
||||
if (i >= filament_multi_color->values.size() || colors.empty() || colors[0] != filament_color->values[i] ) {
|
||||
filament_multi_color->values[i] = filament_color->values[i];
|
||||
}
|
||||
}
|
||||
// Sync filament colour type
|
||||
ConfigOptionStrings* filament_color_type = proj_cfg.opt<ConfigOptionStrings>("filament_colour_type", true);
|
||||
if (filament_color_type && filament_color_type->size() != filament_count) {
|
||||
filament_color_type->values.resize(filament_count);
|
||||
|
||||
for (size_t i = 0; i < filament_count; i++) {
|
||||
if (i >= filament_color_type->values.size() || filament_color_type->values[i].empty()) {
|
||||
filament_color_type->values[i] = "1";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Filament Track Switch state is derived live from the connected
|
||||
// printer; a loaded project must not carry a stale installed/active
|
||||
// flag, so clear both and let device sync re-derive them.
|
||||
if (auto* has_switcher = proj_cfg.opt<ConfigOptionBool>("has_filament_switcher"))
|
||||
has_switcher->value = false;
|
||||
if (auto* dynamic_map = proj_cfg.opt<ConfigOptionBool>("enable_filament_dynamic_map"))
|
||||
dynamic_map->value = false;
|
||||
}
|
||||
|
||||
// BBS: backup & restore
|
||||
std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_files,
|
||||
LoadStrategy strategy,
|
||||
@@ -9680,58 +9755,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
if (file_wipe_tower_y)
|
||||
*wipe_tower_y = *file_wipe_tower_y;
|
||||
|
||||
ConfigOptionStrings* filament_color = proj_cfg.opt<ConfigOptionStrings>("filament_colour");
|
||||
if (filament_color) {
|
||||
size_t filament_count = filament_color->size();
|
||||
|
||||
// Sync filament map
|
||||
ConfigOptionInts* filament_map = proj_cfg.opt<ConfigOptionInts>("filament_map", true);
|
||||
if (filament_map->size() != filament_count) {
|
||||
filament_map->values.resize(filament_count, 1);
|
||||
}
|
||||
|
||||
ConfigOptionInts* filament_nozzle_map = proj_cfg.opt<ConfigOptionInts>("filament_nozzle_map", true);
|
||||
if (filament_nozzle_map->size() != filament_count) {
|
||||
filament_nozzle_map->values.resize(filament_count, 0);
|
||||
}
|
||||
|
||||
ConfigOptionInts* filament_volume_map = proj_cfg.opt<ConfigOptionInts>("filament_volume_map", true);
|
||||
if (filament_volume_map->size() != filament_count) {
|
||||
filament_volume_map->values.resize(filament_count, static_cast<int>(NozzleVolumeType::nvtStandard));
|
||||
}
|
||||
|
||||
// Sync filament multi colour
|
||||
ConfigOptionStrings* filament_multi_color = proj_cfg.opt<ConfigOptionStrings>("filament_multi_colour", true);
|
||||
if (filament_multi_color->size() != filament_count) {
|
||||
filament_multi_color->values.resize(filament_count);
|
||||
}
|
||||
// If there is no multi-color data or color is not match, use single color as default value
|
||||
for (size_t i = 0; i < filament_count; i++) {
|
||||
std::vector<std::string> colors = Slic3r::split_string(filament_multi_color->values[i], ' ');
|
||||
if (i >= filament_multi_color->values.size() || colors.empty() || colors[0] != filament_color->values[i] ) {
|
||||
filament_multi_color->values[i] = filament_color->values[i];
|
||||
}
|
||||
}
|
||||
// Sync filament colour type
|
||||
ConfigOptionStrings* filament_color_type = proj_cfg.opt<ConfigOptionStrings>("filament_colour_type", true);
|
||||
if (filament_color_type && filament_color_type->size() != filament_count) {
|
||||
filament_color_type->values.resize(filament_count);
|
||||
|
||||
for (size_t i = 0; i < filament_count; i++) {
|
||||
if (i >= filament_color_type->values.size() || filament_color_type->values[i].empty()) {
|
||||
filament_color_type->values[i] = "1";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Filament Track Switch state is derived live from the connected
|
||||
// printer; a loaded project must not carry a stale installed/active
|
||||
// flag, so clear both and let device sync re-derive them.
|
||||
if (auto* has_switcher = proj_cfg.opt<ConfigOptionBool>("has_filament_switcher"))
|
||||
has_switcher->value = false;
|
||||
if (auto* dynamic_map = proj_cfg.opt<ConfigOptionBool>("enable_filament_dynamic_map"))
|
||||
dynamic_map->value = false;
|
||||
sync_loaded_project_filament_config(proj_cfg);
|
||||
}
|
||||
// Update filament combobox after loading config
|
||||
if (published_config.published) {
|
||||
@@ -10902,6 +10926,78 @@ void Plater::priv::center_selection()
|
||||
view3D->center_selected();
|
||||
}
|
||||
|
||||
void Plater::priv::distribute_selection_x()
|
||||
{
|
||||
GLGizmoAlignment alignment_helper(*view3D->get_canvas3d());
|
||||
alignment_helper.distribute_objects(GLGizmoAlignment::AlignType::DISTRIBUTE_X);
|
||||
}
|
||||
|
||||
void Plater::priv::distribute_selection_y()
|
||||
{
|
||||
GLGizmoAlignment alignment_helper(*view3D->get_canvas3d());
|
||||
alignment_helper.distribute_objects(GLGizmoAlignment::AlignType::DISTRIBUTE_Y);
|
||||
}
|
||||
|
||||
void Plater::priv::distribute_selection_z()
|
||||
{
|
||||
GLGizmoAlignment alignment_helper(*view3D->get_canvas3d());
|
||||
alignment_helper.distribute_objects(GLGizmoAlignment::AlignType::DISTRIBUTE_Z);
|
||||
}
|
||||
|
||||
void Plater::priv::align_selection_x_min()
|
||||
{
|
||||
GLGizmoAlignment alignment_helper(*view3D->get_canvas3d());
|
||||
alignment_helper.align_objects(GLGizmoAlignment::AlignType::X_MIN);
|
||||
}
|
||||
|
||||
void Plater::priv::align_selection_x_center()
|
||||
{
|
||||
GLGizmoAlignment alignment_helper(*view3D->get_canvas3d());
|
||||
alignment_helper.align_objects(GLGizmoAlignment::AlignType::CENTER_X);
|
||||
}
|
||||
|
||||
void Plater::priv::align_selection_x_max()
|
||||
{
|
||||
GLGizmoAlignment alignment_helper(*view3D->get_canvas3d());
|
||||
alignment_helper.align_objects(GLGizmoAlignment::AlignType::X_MAX);
|
||||
}
|
||||
|
||||
void Plater::priv::align_selection_y_min()
|
||||
{
|
||||
GLGizmoAlignment alignment_helper(*view3D->get_canvas3d());
|
||||
alignment_helper.align_objects(GLGizmoAlignment::AlignType::Y_MIN);
|
||||
}
|
||||
|
||||
void Plater::priv::align_selection_y_center()
|
||||
{
|
||||
GLGizmoAlignment alignment_helper(*view3D->get_canvas3d());
|
||||
alignment_helper.align_objects(GLGizmoAlignment::AlignType::CENTER_Y);
|
||||
}
|
||||
|
||||
void Plater::priv::align_selection_y_max()
|
||||
{
|
||||
GLGizmoAlignment alignment_helper(*view3D->get_canvas3d());
|
||||
alignment_helper.align_objects(GLGizmoAlignment::AlignType::Y_MAX);
|
||||
}
|
||||
|
||||
void Plater::priv::align_selection_z_min()
|
||||
{
|
||||
GLGizmoAlignment alignment_helper(*view3D->get_canvas3d());
|
||||
alignment_helper.align_objects(GLGizmoAlignment::AlignType::Z_MIN);
|
||||
}
|
||||
|
||||
void Plater::priv::align_selection_z_center()
|
||||
{
|
||||
GLGizmoAlignment alignment_helper(*view3D->get_canvas3d());
|
||||
alignment_helper.align_objects(GLGizmoAlignment::AlignType::CENTER_Z);
|
||||
}
|
||||
|
||||
void Plater::priv::align_selection_z_max()
|
||||
{
|
||||
GLGizmoAlignment alignment_helper(*view3D->get_canvas3d());
|
||||
alignment_helper.align_objects(GLGizmoAlignment::AlignType::Z_MAX);
|
||||
}
|
||||
|
||||
void Plater::priv::drop_selection()
|
||||
{
|
||||
view3D->drop_selected();
|
||||
@@ -12560,7 +12656,7 @@ void Plater::priv::set_current_panel(wxPanel* panel, bool no_slice)
|
||||
|
||||
// Add sidebar and toolbar collapse logic
|
||||
if (panel == view3D || panel == preview) {
|
||||
this->enable_sidebar(!q->only_gcode_mode());
|
||||
this->enable_sidebar(true);
|
||||
}
|
||||
if (panel == preview) {
|
||||
if (q->only_gcode_mode()) {
|
||||
@@ -18067,11 +18163,80 @@ void Plater::load_gcode(const wxString& filename)
|
||||
|
||||
m_last_loaded_gcode = filename;
|
||||
|
||||
// An OrcaSlicer G-code carries the settings it was sliced with. They replace the selected presets as a
|
||||
// .gcode.3mf's do, so unsaved preset changes get the save-or-discard choice of opening a project rather
|
||||
// than the new project's choice to keep them.
|
||||
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
|
||||
const std::string path = into_u8(filename);
|
||||
std::optional<DynamicPrintConfig> file_config;
|
||||
ConfigSubstitutions substitutions;
|
||||
{
|
||||
wxBusyCursor wait;
|
||||
try {
|
||||
file_config = preset_bundle->config_from_gcode(path, ForwardCompatibilitySubstitutionRule::Enable, substitutions);
|
||||
} catch (const std::exception& ex) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": no settings to load from " << path << ": " << ex.what();
|
||||
}
|
||||
}
|
||||
|
||||
// BSS: create a new project when load_gcode, force close previous one
|
||||
if (new_project(false, true) != wxID_YES)
|
||||
if (file_config) {
|
||||
auto check = [](bool yes_or_no) {
|
||||
return yes_or_no || wxGetApp().check_and_save_current_preset_changes(_L("Load project"), _L("Some presets are modified."));
|
||||
};
|
||||
if (close_with_confirm(check) == wxID_CANCEL || new_project(true, true) != wxID_YES)
|
||||
return;
|
||||
} else if (new_project(false, true) != wxID_YES)
|
||||
return;
|
||||
|
||||
m_only_gcode = true;
|
||||
m_gcode_settings_loaded = false;
|
||||
// A previewed G-code's settings are not the selection the app should remember.
|
||||
PresetBundle::SelectionExportBlocker no_selection_export(*preset_bundle);
|
||||
// What loading the file's settings replaces, to put back if that load fails or the file is rejected.
|
||||
std::optional<PresetBundle::Selection> previous;
|
||||
if (file_config) {
|
||||
previous = preset_bundle->selection();
|
||||
if (!previous)
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": project presets are still loaded, not loading the settings of " << path;
|
||||
}
|
||||
auto unload_file_settings = [this, preset_bundle, &previous]() {
|
||||
if (!previous)
|
||||
return;
|
||||
preset_bundle->restore_selection(*previous);
|
||||
wxGetApp().load_current_presets(false, false);
|
||||
// Switching back to the previous printer re-derives some project settings (TabPrinter::on_preset_loaded),
|
||||
// and only the printer refresh shows them in the sidebar's flow selectors.
|
||||
preset_bundle->project_config = std::move(previous->project_config);
|
||||
sidebar().update_presets(Preset::TYPE_PRINTER);
|
||||
reset_project_dirty_initial_presets();
|
||||
update_project_dirty_from_presets();
|
||||
m_gcode_settings_loaded = false;
|
||||
// The rejections after the switch to the preview have already shown the sidebar with the file's settings.
|
||||
enable_sidebar(false);
|
||||
previous.reset();
|
||||
};
|
||||
if (previous) {
|
||||
try {
|
||||
preset_bundle->load_config_model(path, std::move(*file_config));
|
||||
m_gcode_settings_loaded = true;
|
||||
} catch (const std::exception& ex) {
|
||||
unload_file_settings();
|
||||
get_notification_manager()->push_notification(NotificationType::CustomNotification,
|
||||
NotificationManager::NotificationLevel::WarningNotificationLevel,
|
||||
GUI::format(_L("Failed loading file \"%1%\". An invalid configuration was found."), path) +
|
||||
"\n" + ex.what());
|
||||
}
|
||||
}
|
||||
if (m_gcode_settings_loaded) {
|
||||
wxGetApp().load_current_presets(false, false);
|
||||
sync_loaded_project_filament_config(preset_bundle->project_config);
|
||||
sidebar().update_presets(Preset::TYPE_FILAMENT);
|
||||
reset_project_dirty_initial_presets();
|
||||
update_project_dirty_from_presets();
|
||||
if (!substitutions.empty())
|
||||
show_substitutions_info(substitutions, path);
|
||||
}
|
||||
|
||||
// cleanup view before to start loading/processing
|
||||
//BBS: update gcode to current partplate's
|
||||
@@ -18099,6 +18264,7 @@ void Plater::load_gcode(const wxString& filename)
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
unload_file_settings();
|
||||
show_error(this, ex.what());
|
||||
return;
|
||||
}
|
||||
@@ -18146,6 +18312,7 @@ void Plater::load_gcode(const wxString& filename)
|
||||
p->preview->get_canvas3d()->zoom_to_plate(0);
|
||||
|
||||
if (p->preview->get_canvas3d()->get_gcode_layers_zs().empty()) {
|
||||
unload_file_settings();
|
||||
MessageDialog(this, format_wxstr(_L("The selected file:\n%1%\ndoes not contain valid G-code."), filename),
|
||||
wxString(GCODEVIEWER_APP_NAME) + " - " + _L("An Error has occurred while loading the G-code file."), wxCLOSE | wxICON_WARNING | wxCENTRE).ShowModal();
|
||||
set_project_filename(_L(DEFAULT_PROJECT_NAME));
|
||||
@@ -22144,6 +22311,18 @@ void Plater::suppress_background_process(const bool stop_background_process)
|
||||
BackgroundSlicingProcess& Plater::background_process() { return p->background_process; }
|
||||
|
||||
void Plater::center_selection() { p->center_selection(); }
|
||||
void Plater::distribute_selection_x() { p->distribute_selection_x(); }
|
||||
void Plater::distribute_selection_y() { p->distribute_selection_y(); }
|
||||
void Plater::distribute_selection_z() { p->distribute_selection_z(); }
|
||||
void Plater::align_selection_x_min() { p->align_selection_x_min(); }
|
||||
void Plater::align_selection_x_center() { p->align_selection_x_center(); }
|
||||
void Plater::align_selection_x_max() { p->align_selection_x_max(); }
|
||||
void Plater::align_selection_y_min() { p->align_selection_y_min(); }
|
||||
void Plater::align_selection_y_center() { p->align_selection_y_center(); }
|
||||
void Plater::align_selection_y_max() { p->align_selection_y_max(); }
|
||||
void Plater::align_selection_z_min() { p->align_selection_z_min(); }
|
||||
void Plater::align_selection_z_center() { p->align_selection_z_center(); }
|
||||
void Plater::align_selection_z_max() { p->align_selection_z_max(); }
|
||||
void Plater::drop_selection() { p->drop_selection(); }
|
||||
void Plater::mirror(Axis axis) { p->mirror(axis); }
|
||||
void Plater::split_object(bool auto_drop) { p->split_object(auto_drop); }
|
||||
|
||||
@@ -418,6 +418,9 @@ public:
|
||||
bool is_gcode_3mf() { return m_exported_file; }
|
||||
bool only_gcode_mode() { return m_only_gcode; }
|
||||
void set_only_gcode(bool only_gcode) { m_only_gcode = only_gcode; }
|
||||
// The previewed G-code's own settings were loaded into the presets, as a .gcode.3mf's are: the bed already is the
|
||||
// one it was sliced for, and the sidebar shows them.
|
||||
bool gcode_settings_loaded() const { return m_only_gcode && m_gcode_settings_loaded; }
|
||||
|
||||
//BBS: add only gcode mode
|
||||
bool using_exported_file() { return m_exported_file; }
|
||||
@@ -759,6 +762,18 @@ public:
|
||||
//BBS: add clone logic
|
||||
void clone_selection();
|
||||
void center_selection();
|
||||
void distribute_selection_x();
|
||||
void distribute_selection_y();
|
||||
void distribute_selection_z();
|
||||
void align_selection_x_min();
|
||||
void align_selection_x_center();
|
||||
void align_selection_x_max();
|
||||
void align_selection_y_min();
|
||||
void align_selection_y_center();
|
||||
void align_selection_y_max();
|
||||
void align_selection_z_min();
|
||||
void align_selection_z_center();
|
||||
void align_selection_z_max();
|
||||
void drop_selection();
|
||||
void search(bool plater_is_active, Preset::Type type, wxWindow *tag, TextInput *etag, wxWindow *stag);
|
||||
void mirror(Axis axis);
|
||||
@@ -1082,6 +1097,8 @@ private:
|
||||
wxString m_last_loaded_gcode;
|
||||
//BBS: add only gcode mode
|
||||
bool m_only_gcode { false };//just for .gcode file not for .gcode.3mf
|
||||
// Set by load_gcode() for the file it previews, so it means nothing once m_only_gcode is cleared.
|
||||
bool m_gcode_settings_loaded { false };
|
||||
bool m_exported_file { false };
|
||||
bool skip_thumbnail_invalid { false };
|
||||
bool m_loading_project { false };
|
||||
|
||||
@@ -56,7 +56,12 @@ namespace fs = boost::filesystem;
|
||||
#define WEBKIT_API
|
||||
struct WebKitWebView;
|
||||
struct WebKitJavascriptResult;
|
||||
struct WebKitSettings;
|
||||
extern "C" {
|
||||
WEBKIT_API WebKitSettings *
|
||||
webkit_settings_new (void);
|
||||
WEBKIT_API const gchar *
|
||||
webkit_settings_get_user_agent (WebKitSettings *settings);
|
||||
WEBKIT_API void
|
||||
webkit_web_view_run_javascript (WebKitWebView *web_view,
|
||||
const gchar *script,
|
||||
@@ -284,6 +289,23 @@ static WebViewRef *webview_ref(wxWebView *webView)
|
||||
return webView ? static_cast<WebViewRef *>(webView->GetRefData()) : nullptr;
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
// WebKitGTK's own User-Agent with the tags the bundled pages (globalapi.js) and Bambu's cloud read
|
||||
// appended. wx's GTK backend cannot report the stock string, so it comes from a throwaway settings object.
|
||||
static wxString linux_user_agent()
|
||||
{
|
||||
static const wxString stock = [] {
|
||||
WebKitSettings *settings = webkit_settings_new();
|
||||
wxString ua = wxString::FromUTF8(webkit_settings_get_user_agent(settings));
|
||||
g_object_unref(settings);
|
||||
return ua;
|
||||
}();
|
||||
return wxString::Format("%s BBL-Slicer/v%s (%s) BBL-Language/%s", stock, Slic3r::GUI::wxGetApp().get_bbl_client_version(),
|
||||
Slic3r::GUI::wxGetApp().dark_mode() ? "dark" : "light",
|
||||
Slic3r::GUI::wxGetApp().current_language_code().BeforeFirst('_'));
|
||||
}
|
||||
#endif
|
||||
|
||||
wxWebView* WebView::CreateWebView(wxWindow * parent, wxString const & url)
|
||||
{
|
||||
#if wxUSE_WEBVIEW_EDGE
|
||||
@@ -336,7 +358,9 @@ wxWebView* WebView::CreateWebView(wxWindow * parent, wxString const & url)
|
||||
s_schemes_registered = true;
|
||||
}
|
||||
webView->Create(parent, wxID_ANY, url2, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
|
||||
#ifndef __linux__
|
||||
#ifdef __linux__
|
||||
webView->SetUserAgent(linux_user_agent());
|
||||
#else
|
||||
webView->SetUserAgent(wxString::Format("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) BBL-Slicer/v%s (%s) BBL-Language/%s",
|
||||
Slic3r::GUI::wxGetApp().get_bbl_client_version(), Slic3r::GUI::wxGetApp().dark_mode() ? "dark" : "light", language_code.mb_str()));
|
||||
#endif
|
||||
@@ -472,7 +496,9 @@ void WebView::RecreateAll()
|
||||
wxString language_code = Slic3r::GUI::wxGetApp().current_language_code().BeforeFirst('_');
|
||||
language_code = language_code.ToStdString();
|
||||
for (auto webView : g_webviews) {
|
||||
#ifndef __linux__
|
||||
#ifdef __linux__
|
||||
webView->SetUserAgent(linux_user_agent());
|
||||
#else
|
||||
webView->SetUserAgent(wxString::Format("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) BBL-Slicer/v%s (%s) BBL-Language/%s",
|
||||
Slic3r::GUI::wxGetApp().get_bbl_client_version(), dark ? "dark" : "light", language_code.mb_str()));
|
||||
#endif
|
||||
|
||||
@@ -1,16 +1,27 @@
|
||||
#include "PluginHostBindings.hpp"
|
||||
#include "PluginHostUi.hpp"
|
||||
#include "slic3r/plugin/PluginBindingUtils.hpp"
|
||||
|
||||
#include <pybind11/pytypes.h>
|
||||
#include <pybind11/cast.h>
|
||||
#include <pybind11/pybind11.h>
|
||||
#include "slic3r/plugin/PluginBindingUtils.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <libslic3r/AppConfig.hpp>
|
||||
#include <libslic3r/Preset.hpp>
|
||||
#include <libslic3r/PresetBundle.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <slic3r/GUI/GUI.hpp>
|
||||
#include <slic3r/GUI/GUI_App.hpp>
|
||||
#include <slic3r/GUI/MainFrame.hpp>
|
||||
#include <slic3r/GUI/Plater.hpp>
|
||||
#include <slic3r/GUI/Tab.hpp>
|
||||
|
||||
#include <pybind11/stl.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -37,10 +48,66 @@ PresetCollection& printer_presets(PresetBundle& bundle)
|
||||
return static_cast<PresetCollection&>(bundle.printers);
|
||||
}
|
||||
|
||||
void reload_local_bundle(const std::string& bundle_id)
|
||||
{
|
||||
bool reloaded = false;
|
||||
std::string error;
|
||||
PluginHostUi::run_on_ui_thread([&] {
|
||||
GUI::GUI_App& app = GUI::wxGetApp();
|
||||
if (app.is_closing())
|
||||
throw std::runtime_error("OrcaSlicer is closing");
|
||||
if (app.preset_bundle == nullptr)
|
||||
throw std::runtime_error("Preset bundle is not available");
|
||||
if (app.app_config == nullptr)
|
||||
throw std::runtime_error("Application configuration is not available");
|
||||
if (app.mainframe == nullptr)
|
||||
throw std::runtime_error("OrcaSlicer main window is not available");
|
||||
|
||||
reloaded = app.preset_bundle->reload_local_bundle(app.app_config->get("preset_folder"), bundle_id, &error);
|
||||
if (!reloaded)
|
||||
return;
|
||||
|
||||
app.mainframe->update_side_preset_ui();
|
||||
for (GUI::Tab* tab : app.tabs_list) {
|
||||
tab->reload_config();
|
||||
tab->update_changed_ui();
|
||||
}
|
||||
if (GUI::Plater* plater = app.plater())
|
||||
plater->sidebar().update_all_preset_comboboxes();
|
||||
});
|
||||
if (!reloaded)
|
||||
throw std::runtime_error(error.empty() ? "Local bundle was not reloaded" : error);
|
||||
}
|
||||
|
||||
std::vector<std::string> list_bundle_ids()
|
||||
{
|
||||
std::vector<std::string> ids;
|
||||
PluginHostUi::run_on_ui_thread([&] {
|
||||
GUI::GUI_App& app = GUI::wxGetApp();
|
||||
if (app.is_closing())
|
||||
throw std::runtime_error("OrcaSlicer is closing");
|
||||
if (app.preset_bundle == nullptr)
|
||||
throw std::runtime_error("Preset bundle is not available");
|
||||
if (app.app_config == nullptr)
|
||||
throw std::runtime_error("Application configuration is not available");
|
||||
|
||||
ids = app.preset_bundle->list_local_bundle_ids(app.app_config->get("preset_folder"));
|
||||
});
|
||||
return ids;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void host_bindings::register_presets(py::module_& host)
|
||||
{
|
||||
auto preset = host.def_submodule("preset", "Preset operations");
|
||||
auto bundles = preset.def_submodule("bundles", "Local preset bundle operations");
|
||||
bundles.def("reload_local", &reload_local_bundle, py::arg("bundle_id"),
|
||||
"Reload one local preset bundle from the active user's _local directory.");
|
||||
bundles.def("list_ids", &list_bundle_ids,
|
||||
"IDs of local preset bundles in the active user's _local directory that reload_local() accepts, "
|
||||
"including bundles added after startup.");
|
||||
|
||||
py::enum_<Preset::Type>(host, "PresetType")
|
||||
.value("Invalid", Preset::TYPE_INVALID)
|
||||
.value("Print", Preset::TYPE_PRINT)
|
||||
|
||||
@@ -646,6 +646,11 @@ void plater_notification(GUI::NotificationManager::NotificationLevel notificatio
|
||||
|
||||
} // namespace
|
||||
|
||||
void PluginHostUi::run_on_ui_thread(std::function<void()> fn)
|
||||
{
|
||||
run_on_ui_blocking(std::move(fn));
|
||||
}
|
||||
|
||||
void PluginHostUi::RegisterBindings(pybind11::module_& host)
|
||||
{
|
||||
auto ui = host.def_submodule(
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace Slic3r {
|
||||
@@ -19,6 +20,8 @@ class PluginHostUi
|
||||
public:
|
||||
static void RegisterBindings(pybind11::module_& host);
|
||||
|
||||
static void run_on_ui_thread(std::function<void()> fn);
|
||||
|
||||
// Lifecycle hook: close and tear down every UI window owned by a plugin. PluginManager invokes
|
||||
// this after plugin teardown and also for bulk unload during application shutdown.
|
||||
static void close_windows_for_plugin(const std::string& plugin_key);
|
||||
|
||||
@@ -3,17 +3,25 @@
|
||||
|
||||
#include <catch2/catch_message.hpp>
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/ModelArrange.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/Preset.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
|
||||
#include "test_helpers.hpp"
|
||||
#include "test_utils.hpp"
|
||||
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include <fstream>
|
||||
#include <ios>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <catch2/matchers/catch_matchers.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
@@ -41,3 +49,39 @@ TEST_CASE("Klipper object labels name each copy without the characters Klipper c
|
||||
CHECK(gcode.find("EXCLUDE_OBJECT_START NAME=" + instance_label + "\n") != std::string::npos);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("An exported G-code's settings load back as the settings it was sliced with", "[GCode]")
|
||||
{
|
||||
// Two filaments, so the config block carries the flush matrix already multiplied by flush_multiplier.
|
||||
const DynamicPrintConfig config = Test::multifilament_config(2, {
|
||||
{ "flush_multiplier", "0.5" },
|
||||
{ "printable_area", "0x0,250x0,250x210,0x210" },
|
||||
{ "printable_height", "180" },
|
||||
});
|
||||
Print print;
|
||||
Model model;
|
||||
Test::init_print(std::vector<TriangleMesh>{ Test::cube(20.) }, print, model, config, nullptr, false);
|
||||
ScopedTemporaryFile file(".gcode");
|
||||
{
|
||||
std::ofstream os(file.string(), std::ios::binary);
|
||||
os << Test::gcode(print);
|
||||
REQUIRE(os.good());
|
||||
}
|
||||
|
||||
PresetBundle bundle;
|
||||
// The config block leaves prime_volume_mode out, so the receiver's choice has to survive the load.
|
||||
bundle.project_config.option<ConfigOptionEnum<PrimeVolumeMode>>("prime_volume_mode")->value = PrimeVolumeMode::pvmFast;
|
||||
ConfigSubstitutions substitutions;
|
||||
bundle.load_config_model(file.string(), bundle.config_from_gcode(file.string(), ForwardCompatibilitySubstitutionRule::Enable, substitutions));
|
||||
|
||||
const DynamicPrintConfig &printer = bundle.printers.get_edited_preset().config;
|
||||
CHECK(printer.opt_serialize("printable_area") == config.opt_serialize("printable_area"));
|
||||
CHECK_THAT(printer.opt_float("printable_height"), Catch::Matchers::WithinAbs(180., 1e-6));
|
||||
// The purge between the two filaments is what the slice used: 280 at a 0.5 multiplier.
|
||||
const std::vector<double> &matrix = bundle.project_config.option<ConfigOptionFloats>("flush_volumes_matrix")->values;
|
||||
const std::vector<double> &multiplier = bundle.project_config.option<ConfigOptionFloats>("flush_multiplier")->values;
|
||||
REQUIRE(matrix.size() == 4);
|
||||
REQUIRE(multiplier.size() == 1);
|
||||
CHECK_THAT(matrix[1] * multiplier[0], Catch::Matchers::WithinAbs(140., 1e-6));
|
||||
CHECK(bundle.project_config.option<ConfigOptionEnum<PrimeVolumeMode>>("prime_volume_mode")->value == PrimeVolumeMode::pvmFast);
|
||||
}
|
||||
|
||||
@@ -10,10 +10,17 @@
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include <cstddef>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
#include <new>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <shared_mutex>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
#include <catch2/matchers/catch_matchers_string.hpp>
|
||||
#include <catch2/catch_message.hpp>
|
||||
#include <catch2/generators/catch_generators.hpp>
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
@@ -38,6 +45,7 @@
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <regex>
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/PublishSettings.hpp"
|
||||
@@ -147,6 +155,124 @@ struct RenameTestCollection : public PresetCollection
|
||||
using PresetCollection::update_map_system_profile_renamed;
|
||||
};
|
||||
|
||||
struct ScopedPresetDataDir
|
||||
{
|
||||
ScopedTemporaryDir tmp{"orca-local-bundle-reload"};
|
||||
std::string previous{data_dir()};
|
||||
|
||||
ScopedPresetDataDir() { set_data_dir(tmp.path().string()); }
|
||||
~ScopedPresetDataDir() { set_data_dir(previous); }
|
||||
|
||||
fs::path bundle_dir(const std::string& id) const
|
||||
{
|
||||
return tmp.path() / PRESET_USER_DIR / DEFAULT_USER_FOLDER_NAME / PRESET_LOCAL_DIR / id;
|
||||
}
|
||||
};
|
||||
|
||||
void write_bundle_metadata(const fs::path& bundle_dir, const std::string& id)
|
||||
{
|
||||
fs::create_directories(bundle_dir);
|
||||
std::ofstream((bundle_dir / PRESET_BUNDLE_METADATA).string()) << "{\"id\":\"" << id << "\"}";
|
||||
}
|
||||
|
||||
void write_print_preset_with_layer_height(const DynamicPrintConfig& default_config, const fs::path& file,
|
||||
const std::string& name, double layer_height)
|
||||
{
|
||||
DynamicPrintConfig config(default_config);
|
||||
config.option<ConfigOptionString>("print_settings_id", true)->value = name;
|
||||
config.option<ConfigOptionString>(BBL_JSON_KEY_INHERITS, true)->value.clear();
|
||||
config.option<ConfigOptionFloat>("layer_height", true)->value = layer_height;
|
||||
fs::create_directories(file.parent_path());
|
||||
config.save_to_json(file.string(), name, "User", "1.0.0");
|
||||
}
|
||||
|
||||
double layer_height(const Preset& preset)
|
||||
{
|
||||
return preset.config.option<ConfigOptionFloat>("layer_height")->value;
|
||||
}
|
||||
|
||||
// The first copy snapshots the dirty edit; cloning that snapshot replays the edit after source loading.
|
||||
// This exercises late failures and concurrent metadata updates without a production test hook.
|
||||
struct ReloadEdit : ConfigOptionString
|
||||
{
|
||||
std::function<void()> on_restore;
|
||||
bool copied = false;
|
||||
|
||||
explicit ReloadEdit(std::function<void()> on_restore) : ConfigOptionString("dirty start gcode"), on_restore(std::move(on_restore)) {}
|
||||
|
||||
ConfigOption* clone() const override
|
||||
{
|
||||
if (copied)
|
||||
on_restore();
|
||||
auto* copy = new ReloadEdit(*this);
|
||||
copy->copied = true;
|
||||
return copy;
|
||||
}
|
||||
};
|
||||
|
||||
void check_reload_state(const PresetBundle& bundle, const PresetBundle& before)
|
||||
{
|
||||
const PresetCollection* actual[] = {&bundle.prints, &bundle.filaments, &bundle.printers};
|
||||
const PresetCollection* expected[] = {&before.prints, &before.filaments, &before.printers};
|
||||
for (size_t i = 0; i < 3; ++i) {
|
||||
CAPTURE(i);
|
||||
CHECK(actual[i]->get_selected_preset_name() == expected[i]->get_selected_preset_name());
|
||||
CHECK(actual[i]->get_edited_preset().config == expected[i]->get_edited_preset().config);
|
||||
CHECK(actual[i]->get_edited_preset().is_dirty == expected[i]->get_edited_preset().is_dirty);
|
||||
REQUIRE(actual[i]->size() == expected[i]->size());
|
||||
for (size_t j = 0; j < actual[i]->size(); ++j) {
|
||||
const Preset& preset = actual[i]->get_presets()[j];
|
||||
const Preset& old = expected[i]->get_presets()[j];
|
||||
CHECK(preset.name == old.name);
|
||||
CHECK(preset.config == old.config);
|
||||
CHECK(preset.bundle_id == old.bundle_id);
|
||||
CHECK(preset.is_compatible == old.is_compatible);
|
||||
CHECK(preset.is_visible == old.is_visible);
|
||||
CHECK(preset.is_dirty == old.is_dirty);
|
||||
CHECK(actual[i]->get_preset_name_by_alias(preset.alias) == expected[i]->get_preset_name_by_alias(old.alias));
|
||||
}
|
||||
}
|
||||
CHECK(bundle.filament_presets == before.filament_presets);
|
||||
CHECK(bundle.project_config == before.project_config);
|
||||
}
|
||||
|
||||
struct LocalReloadFixture
|
||||
{
|
||||
ScopedPresetDataDir data_dir;
|
||||
PresetBundle bundle;
|
||||
const std::string id = "reload-target";
|
||||
const fs::path directory = data_dir.bundle_dir(id);
|
||||
const std::string prefix = std::string(PRESET_LOCAL_DIR) + "/" + id + "/";
|
||||
std::string error;
|
||||
|
||||
LocalReloadFixture()
|
||||
{
|
||||
write_bundle_metadata(directory, id);
|
||||
write_print_preset_with_layer_height(bundle.prints.default_preset().config, directory / PRESET_PRINT_NAME / "Print.json", "Print",
|
||||
0.20);
|
||||
write_preset_with_inherits(bundle.filaments.default_preset().config, directory / PRESET_FILAMENT_NAME / "Filament.json", "Filament",
|
||||
"");
|
||||
write_preset_with_inherits(bundle.printers.default_preset().config, directory / PRESET_PRINTER_NAME / "Printer.json", "Printer", "");
|
||||
REQUIRE(bundle.reload_local_bundle("", id, &error));
|
||||
bundle.prints.select_preset_by_name(prefix + "Print", true);
|
||||
bundle.filaments.select_preset_by_name(prefix + "Filament", true);
|
||||
bundle.printers.select_preset_by_name(prefix + "Printer", true);
|
||||
bundle.filament_presets = {prefix + "Filament"};
|
||||
bundle.update_compatible(PresetSelectCompatibleType::Never);
|
||||
}
|
||||
|
||||
void change_sources()
|
||||
{
|
||||
write_print_preset_with_layer_height(bundle.prints.default_preset().config, directory / PRESET_PRINT_NAME / "Print.json", "Print",
|
||||
0.30);
|
||||
write_preset_with_inherits(bundle.filaments.default_preset().config, directory / PRESET_FILAMENT_NAME / "Added Filament.json",
|
||||
"Added Filament", "");
|
||||
write_preset_with_inherits(bundle.printers.default_preset().config, directory / PRESET_PRINTER_NAME / "Added Printer.json",
|
||||
"Added Printer", "");
|
||||
std::ofstream((directory / PRESET_BUNDLE_METADATA).string()) << "{\"id\":\"" << id << "\",\"version\":\"2.0.0\"}";
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Preset identity is canonicalized from load path", "[Preset][Identity]")
|
||||
@@ -266,6 +392,431 @@ TEST_CASE("Printer extruder count tolerates missing nozzle diameter", "[Preset][
|
||||
CHECK(bundle.get_printer_extruder_count() == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("Reloading one local bundle preserves unrelated and modified presets", "[Preset][Bundle]")
|
||||
{
|
||||
using Catch::Matchers::WithinAbs;
|
||||
|
||||
ScopedPresetDataDir data_dir;
|
||||
PresetBundle bundle;
|
||||
const std::string id = "reload-target";
|
||||
const fs::path bundle_dir = data_dir.bundle_dir(id);
|
||||
const fs::path process_file = bundle_dir / PRESET_PRINT_NAME / "Managed Print.json";
|
||||
const std::string target_name = std::string(PRESET_LOCAL_DIR) + "/" + id + "/Managed Print";
|
||||
std::string error;
|
||||
|
||||
write_bundle_metadata(bundle_dir, id);
|
||||
write_print_preset_with_layer_height(bundle.prints.default_preset().config, process_file, "Managed Print", 0.20);
|
||||
write_preset_with_inherits(bundle.filaments.default_preset().config,
|
||||
bundle_dir / PRESET_FILAMENT_NAME / "Managed Filament.json", "Managed Filament", "");
|
||||
write_preset_with_inherits(bundle.printers.default_preset().config,
|
||||
bundle_dir / PRESET_PRINTER_NAME / "Managed Printer.json", "Managed Printer", "");
|
||||
|
||||
REQUIRE(bundle.reload_local_bundle("", id, &error));
|
||||
const Preset* target = bundle.prints.find_preset(target_name, false, true);
|
||||
REQUIRE(target != nullptr);
|
||||
CHECK(target->bundle_id == id);
|
||||
CHECK_FALSE(target->is_user());
|
||||
REQUIRE(bundle.filaments.find_preset(std::string(PRESET_LOCAL_DIR) + "/" + id + "/Managed Filament") != nullptr);
|
||||
REQUIRE(bundle.printers.find_preset(std::string(PRESET_LOCAL_DIR) + "/" + id + "/Managed Printer") != nullptr);
|
||||
CHECK(bundle.prints.get_preset_name_by_alias("Managed Print") == target_name);
|
||||
CHECK(bundle.filaments.get_preset_name_by_alias("Managed Filament") ==
|
||||
std::string(PRESET_LOCAL_DIR) + "/" + id + "/Managed Filament");
|
||||
CHECK(bundle.printers.get_preset_name_by_alias("Managed Printer") ==
|
||||
std::string(PRESET_LOCAL_DIR) + "/" + id + "/Managed Printer");
|
||||
|
||||
const std::string unrelated_name = add_inmemory_preset(bundle.prints, "Unrelated Process").name;
|
||||
bundle.prints.select_preset_by_name(unrelated_name, true);
|
||||
write_print_preset_with_layer_height(bundle.prints.default_preset().config, process_file, "Managed Print", 0.25);
|
||||
REQUIRE(bundle.reload_local_bundle("", id, &error));
|
||||
CHECK(bundle.prints.get_selected_preset_name() == unrelated_name);
|
||||
target = bundle.prints.find_preset(target_name, false, true);
|
||||
REQUIRE(target != nullptr);
|
||||
CHECK_THAT(layer_height(*target), WithinAbs(0.25, 1e-6));
|
||||
|
||||
bundle.prints.select_preset_by_name(target_name, true);
|
||||
bundle.prints.get_edited_preset().config.option<ConfigOptionFloat>("layer_height", true)->value = 0.33;
|
||||
REQUIRE(bundle.prints.update_dirty());
|
||||
write_print_preset_with_layer_height(bundle.prints.default_preset().config, process_file, "Managed Print", 0.28);
|
||||
REQUIRE(bundle.reload_local_bundle("", id, &error));
|
||||
CHECK(bundle.prints.get_selected_preset_name() == target_name);
|
||||
CHECK_THAT(layer_height(bundle.prints.get_selected_preset()), WithinAbs(0.28, 1e-6));
|
||||
CHECK_THAT(layer_height(bundle.prints.get_edited_preset()), WithinAbs(0.33, 1e-6));
|
||||
CHECK(bundle.prints.current_is_dirty());
|
||||
|
||||
std::ofstream(process_file.string()) << "not json";
|
||||
CHECK_FALSE(bundle.reload_local_bundle("", id, &error));
|
||||
CHECK(fs::exists(process_file));
|
||||
CHECK_THAT(layer_height(bundle.prints.get_selected_preset()), WithinAbs(0.28, 1e-6));
|
||||
CHECK_THAT(layer_height(bundle.prints.get_edited_preset()), WithinAbs(0.33, 1e-6));
|
||||
|
||||
bundle.prints.discard_current_changes();
|
||||
REQUIRE(fs::remove_all(process_file.parent_path()) > 0);
|
||||
REQUIRE(bundle.reload_local_bundle("", id, &error));
|
||||
CHECK(bundle.prints.find_preset(target_name, false, true) == nullptr);
|
||||
CHECK(bundle.prints.get_selected_preset_name() != target_name);
|
||||
CHECK(bundle.filaments.find_preset(std::string(PRESET_LOCAL_DIR) + "/" + id + "/Managed Filament") != nullptr);
|
||||
CHECK(bundle.printers.find_preset(std::string(PRESET_LOCAL_DIR) + "/" + id + "/Managed Printer") != nullptr);
|
||||
|
||||
const std::string filament_name = std::string(PRESET_LOCAL_DIR) + "/" + id + "/Managed Filament";
|
||||
const std::string printer_name = std::string(PRESET_LOCAL_DIR) + "/" + id + "/Managed Printer";
|
||||
bundle.filaments.select_preset_by_name(filament_name, true);
|
||||
bundle.printers.select_preset_by_name(printer_name, true);
|
||||
bundle.filament_presets = { filament_name };
|
||||
REQUIRE(fs::remove_all(bundle_dir / PRESET_FILAMENT_NAME) > 0);
|
||||
REQUIRE(fs::remove_all(bundle_dir / PRESET_PRINTER_NAME) > 0);
|
||||
REQUIRE(bundle.reload_local_bundle("", id, &error));
|
||||
CHECK(bundle.filaments.get_selected_preset_name() != filament_name);
|
||||
CHECK(bundle.printers.get_selected_preset_name() != printer_name);
|
||||
REQUIRE(bundle.filament_presets.size() == 1);
|
||||
CHECK(bundle.filament_presets.front() != filament_name);
|
||||
CHECK(bundle.filaments.find_preset(bundle.filament_presets.front(), false) != nullptr);
|
||||
}
|
||||
|
||||
TEST_CASE("Reloading a local bundle resolves a loaded parent preset", "[Preset][Bundle]")
|
||||
{
|
||||
ScopedPresetDataDir data_dir;
|
||||
PresetBundle bundle;
|
||||
const std::string id = "inherited-target";
|
||||
const fs::path bundle_dir = data_dir.bundle_dir(id);
|
||||
const std::string parent_name = "Generic ABS @System";
|
||||
const std::string target_name = std::string(PRESET_LOCAL_DIR) + "/" + id + "/Managed Filament";
|
||||
std::string error;
|
||||
|
||||
add_inmemory_preset(bundle.filaments, parent_name);
|
||||
write_bundle_metadata(bundle_dir, id);
|
||||
write_preset_with_inherits(bundle.filaments.default_preset().config,
|
||||
bundle_dir / PRESET_FILAMENT_NAME / "Managed Filament.json", "Managed Filament", parent_name);
|
||||
|
||||
REQUIRE(bundle.reload_local_bundle("", id, &error));
|
||||
const Preset* target = bundle.filaments.find_preset(target_name, false, true);
|
||||
REQUIRE(target != nullptr);
|
||||
CHECK(target->inherits() == parent_name);
|
||||
}
|
||||
|
||||
TEST_CASE("Reloading a local bundle rejects unsafe source and inheritance changes", "[Preset][Bundle]")
|
||||
{
|
||||
ScopedPresetDataDir data_dir;
|
||||
PresetBundle bundle;
|
||||
const std::string id = "guarded-target";
|
||||
const fs::path bundle_dir = data_dir.bundle_dir(id);
|
||||
const std::string target_name = std::string(PRESET_LOCAL_DIR) + "/" + id + "/Managed Print";
|
||||
std::string error;
|
||||
|
||||
write_bundle_metadata(bundle_dir, id);
|
||||
write_print_preset_with_layer_height(bundle.prints.default_preset().config,
|
||||
bundle_dir / PRESET_PRINT_NAME / "Managed Print.json", "Managed Print", 0.20);
|
||||
REQUIRE(bundle.reload_local_bundle("", id, &error));
|
||||
|
||||
add_inmemory_preset(bundle.prints, "External Child", target_name);
|
||||
CHECK_FALSE(bundle.reload_local_bundle("", id, &error));
|
||||
CHECK(error.find("External Child") != std::string::npos);
|
||||
CHECK(bundle.prints.find_preset(target_name, false, true) != nullptr);
|
||||
|
||||
for (auto it = bundle.prints.begin(); it != bundle.prints.end(); ++it) {
|
||||
if (it->name == "External Child") {
|
||||
bundle.prints.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
fs::remove_all(bundle_dir);
|
||||
CHECK_FALSE(bundle.reload_local_bundle("", id, &error));
|
||||
CHECK(bundle.prints.find_preset(target_name, false, true) != nullptr);
|
||||
CHECK_FALSE(bundle.reload_local_bundle("", "../outside", &error));
|
||||
CHECK(error == "Local bundle id must be a single path component");
|
||||
CHECK_FALSE(bundle.reload_local_bundle("../outside", id, &error));
|
||||
CHECK(error == "Preset folder must be a single path component");
|
||||
#ifdef _WIN32
|
||||
CHECK_FALSE(bundle.reload_local_bundle("", "C:", &error));
|
||||
CHECK(error == "Local bundle id must be a single path component");
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_CASE("A failure while restoring dirty edits leaves the whole local bundle unchanged", "[Preset][Bundle][Regression]")
|
||||
{
|
||||
LocalReloadFixture f;
|
||||
bool restored = false;
|
||||
f.bundle.printers.get_edited_preset().config.set_key_value("machine_start_gcode", new ReloadEdit([&] {
|
||||
restored = true;
|
||||
throw std::bad_alloc();
|
||||
}));
|
||||
f.bundle.printers.update_dirty();
|
||||
const PresetBundle before(f.bundle);
|
||||
const BundleMetadata metadata = f.bundle.bundles.m_bundles.at(f.id);
|
||||
const fs::path local_dir = f.bundle.dir_user_presets_local;
|
||||
f.change_sources();
|
||||
|
||||
CHECK_FALSE(f.bundle.reload_local_bundle("", f.id, &f.error));
|
||||
REQUIRE(restored);
|
||||
CHECK_THAT(f.error, Catch::Matchers::ContainsSubstring("Failed to reload"));
|
||||
check_reload_state(f.bundle, before);
|
||||
CHECK(f.bundle.dir_user_presets_local == local_dir);
|
||||
const auto& after = f.bundle.bundles.m_bundles.at(f.id);
|
||||
CHECK(after.version == metadata.version);
|
||||
CHECK(after.path == metadata.path);
|
||||
CHECK(after.bundle_type == metadata.bundle_type);
|
||||
CHECK(after.print_presets == metadata.print_presets);
|
||||
CHECK(after.filament_presets == metadata.filament_presets);
|
||||
CHECK(after.printer_presets == metadata.printer_presets);
|
||||
CHECK(fs::exists(f.directory / PRESET_FILAMENT_NAME / "Added Filament.json"));
|
||||
|
||||
// The rejected staging state does not poison the next reload or leave locks held.
|
||||
f.bundle.printers.discard_current_changes();
|
||||
REQUIRE(f.bundle.reload_local_bundle("", f.id, &f.error));
|
||||
CHECK_THAT(layer_height(f.bundle.prints.get_selected_preset()), Catch::Matchers::WithinAbs(0.30, 1e-6));
|
||||
}
|
||||
|
||||
TEST_CASE("A subscribed bundle registered during staging wins over the local reload", "[Preset][Bundle][Regression]")
|
||||
{
|
||||
const bool replace_local_entry = GENERATE(false, true);
|
||||
LocalReloadFixture f;
|
||||
if (!replace_local_entry)
|
||||
f.bundle.bundles.m_bundles.erase(f.id);
|
||||
bool registered = false;
|
||||
f.bundle.printers.get_edited_preset().config.set_key_value("machine_start_gcode", new ReloadEdit([&] {
|
||||
// Finish a background metadata write after the initial local-source
|
||||
// check, before live commit.
|
||||
std::async(std::launch::async, [&] {
|
||||
BundleMetadata subscribed;
|
||||
subscribed.id = f.id;
|
||||
subscribed.bundle_type = BundleType::Subscribed;
|
||||
subscribed.is_subscribed = true;
|
||||
subscribed.version = "9.0.0";
|
||||
std::unique_lock<std::shared_mutex> lock(f.bundle.bundles.RWMtx);
|
||||
f.bundle.bundles.m_bundles.insert_or_assign(f.id,
|
||||
std::move(subscribed));
|
||||
}).get();
|
||||
registered = true;
|
||||
}));
|
||||
f.bundle.printers.update_dirty();
|
||||
const PresetBundle before(f.bundle);
|
||||
f.change_sources();
|
||||
|
||||
CHECK_FALSE(f.bundle.reload_local_bundle("", f.id, &f.error));
|
||||
REQUIRE(registered);
|
||||
CHECK_THAT(f.error, Catch::Matchers::ContainsSubstring("subscribed"));
|
||||
check_reload_state(f.bundle, before);
|
||||
const auto& metadata = f.bundle.bundles.m_bundles.at(f.id);
|
||||
CHECK(metadata.bundle_type == BundleType::Subscribed);
|
||||
CHECK(metadata.is_subscribed);
|
||||
CHECK(metadata.version == "9.0.0");
|
||||
}
|
||||
|
||||
TEST_CASE("External compatible process references prevent local bundle replacement and removal", "[Preset][Bundle][Regression]")
|
||||
{
|
||||
const bool edited_only = GENERATE(false, true);
|
||||
const bool remove = GENERATE(false, true);
|
||||
LocalReloadFixture f;
|
||||
const std::string external_name = "External Filament";
|
||||
add_inmemory_preset(f.bundle.filaments, external_name);
|
||||
f.bundle.filaments.select_preset_by_name(external_name, true);
|
||||
Preset& dependent = edited_only ? f.bundle.filaments.get_edited_preset() : *f.bundle.filaments.find_preset(external_name, false, true);
|
||||
dependent.config.set_key_value("compatible_prints", new ConfigOptionStrings({f.prefix + "Print"}));
|
||||
if (!edited_only)
|
||||
f.bundle.filaments.select_preset_by_name(external_name, true);
|
||||
f.bundle.filaments.update_dirty();
|
||||
f.bundle.filament_presets = {external_name};
|
||||
f.bundle.update_compatible(PresetSelectCompatibleType::Never);
|
||||
REQUIRE(f.bundle.filaments.get_edited_preset().is_compatible);
|
||||
const PresetBundle before(f.bundle);
|
||||
if (remove)
|
||||
REQUIRE(fs::remove(f.directory / PRESET_PRINT_NAME / "Print.json"));
|
||||
else
|
||||
f.change_sources();
|
||||
|
||||
CHECK_FALSE(f.bundle.reload_local_bundle("", f.id, &f.error));
|
||||
CHECK_THAT(f.error, Catch::Matchers::ContainsSubstring(external_name));
|
||||
check_reload_state(f.bundle, before);
|
||||
CHECK(f.bundle.bundles.m_bundles.at(f.id).print_presets == std::vector<std::string>{f.prefix + "Print"});
|
||||
}
|
||||
|
||||
TEST_CASE("External compatible printer references prevent local bundle replacement and removal", "[Preset][Bundle][Regression]")
|
||||
{
|
||||
const bool filament = GENERATE(false, true);
|
||||
const bool edited_only = GENERATE(false, true);
|
||||
const bool remove = GENERATE(false, true);
|
||||
LocalReloadFixture f;
|
||||
PresetCollection& collection = filament ? f.bundle.filaments : f.bundle.prints;
|
||||
const std::string external_name = filament ? "External Filament" : "External Print";
|
||||
add_inmemory_preset(collection, external_name);
|
||||
collection.select_preset_by_name(external_name, true);
|
||||
Preset& dependent = edited_only ? collection.get_edited_preset() : *collection.find_preset(external_name, false, true);
|
||||
dependent.config.set_key_value("compatible_printers", new ConfigOptionStrings({f.prefix + "Printer"}));
|
||||
if (!edited_only)
|
||||
collection.select_preset_by_name(external_name, true);
|
||||
collection.update_dirty();
|
||||
if (filament)
|
||||
f.bundle.filament_presets = {external_name};
|
||||
f.bundle.update_compatible(PresetSelectCompatibleType::Never);
|
||||
REQUIRE(collection.get_edited_preset().is_compatible);
|
||||
const PresetBundle before(f.bundle);
|
||||
if (remove)
|
||||
REQUIRE(fs::remove(f.directory / PRESET_PRINTER_NAME / "Printer.json"));
|
||||
else
|
||||
f.change_sources();
|
||||
|
||||
CHECK_FALSE(f.bundle.reload_local_bundle("", f.id, &f.error));
|
||||
CHECK_THAT(f.error, Catch::Matchers::ContainsSubstring(external_name));
|
||||
check_reload_state(f.bundle, before);
|
||||
}
|
||||
|
||||
TEST_CASE("Local reload retains internal compatibility references and live vendor and calibration pointers", "[Preset][Bundle][Regression]")
|
||||
{
|
||||
LocalReloadFixture f;
|
||||
VendorProfile& vendor = f.bundle.vendors["TestVendor"];
|
||||
vendor.id = "TestVendor";
|
||||
Preset& external = add_inmemory_preset(f.bundle.filaments, "Vendor Filament");
|
||||
external.is_system = true;
|
||||
external.vendor = &vendor;
|
||||
f.bundle.set_calibrate_printer(f.prefix + "Printer");
|
||||
|
||||
DynamicPrintConfig config(f.bundle.filaments.default_preset().config);
|
||||
config.set_key_value("compatible_prints", new ConfigOptionStrings({f.prefix + "Print"}));
|
||||
config.set_key_value("compatible_printers", new ConfigOptionStrings({f.prefix + "Printer"}));
|
||||
write_preset_with_inherits(config, f.directory / PRESET_FILAMENT_NAME / "Filament.json", "Filament", "");
|
||||
f.change_sources();
|
||||
REQUIRE(f.bundle.reload_local_bundle("", f.id, &f.error));
|
||||
CHECK(f.bundle.filaments.get_selected_preset_name() == f.prefix + "Filament");
|
||||
CHECK(f.bundle.filaments.get_edited_preset().is_compatible);
|
||||
CHECK(f.bundle.vendors.at("TestVendor").id == vendor.id);
|
||||
CHECK(f.bundle.filaments.find_preset("Vendor Filament")->vendor == &vendor);
|
||||
CHECK(f.bundle.calibrate_printer == f.bundle.printers.find_preset(f.prefix + "Printer"));
|
||||
CHECK(f.bundle.calibrate_filaments.count(f.bundle.filaments.find_preset("Vendor Filament")) == 1);
|
||||
CHECK(f.bundle.calibrate_filaments.count(f.bundle.filaments.find_preset(f.prefix + "Filament", false, true)) == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("Local reload preserves dirty filament and printer options over new source values", "[Preset][Bundle][Regression]")
|
||||
{
|
||||
LocalReloadFixture f;
|
||||
f.bundle.filaments.get_edited_preset().config.option<ConfigOptionFloats>("filament_flow_ratio")->values = {1.13};
|
||||
f.bundle.printers.get_edited_preset().config.option<ConfigOptionFloats>("nozzle_diameter")->values = {0.6};
|
||||
f.bundle.filaments.update_dirty();
|
||||
f.bundle.printers.update_dirty();
|
||||
DynamicPrintConfig filament_config(f.bundle.filaments.get_selected_preset().config);
|
||||
filament_config.option<ConfigOptionFloats>("filament_flow_ratio")->values = {0.98};
|
||||
write_preset_with_inherits(filament_config, f.directory / PRESET_FILAMENT_NAME / "Filament.json", "Filament", "");
|
||||
DynamicPrintConfig printer_config(f.bundle.printers.get_selected_preset().config);
|
||||
printer_config.option<ConfigOptionFloats>("nozzle_diameter")->values = {0.8};
|
||||
write_preset_with_inherits(printer_config, f.directory / PRESET_PRINTER_NAME / "Printer.json", "Printer", "");
|
||||
|
||||
REQUIRE(f.bundle.reload_local_bundle("", f.id, &f.error));
|
||||
check_double_vector(f.bundle.filaments.get_selected_preset().config.option<ConfigOptionFloats>("filament_flow_ratio")->values, {0.98});
|
||||
check_double_vector(f.bundle.filaments.get_edited_preset().config.option<ConfigOptionFloats>("filament_flow_ratio")->values, {1.13});
|
||||
check_double_vector(f.bundle.printers.get_selected_preset().config.option<ConfigOptionFloats>("nozzle_diameter")->values, {0.8});
|
||||
check_double_vector(f.bundle.printers.get_edited_preset().config.option<ConfigOptionFloats>("nozzle_diameter")->values, {0.6});
|
||||
CHECK(f.bundle.filaments.current_is_dirty());
|
||||
CHECK(f.bundle.printers.current_is_dirty());
|
||||
}
|
||||
|
||||
TEST_CASE("Local reload rejects removal or rename of a dirty selected preset in every collection", "[Preset][Bundle][Regression]")
|
||||
{
|
||||
const auto type = GENERATE(Preset::TYPE_PRINT, Preset::TYPE_FILAMENT, Preset::TYPE_PRINTER);
|
||||
const bool rename = GENERATE(false, true);
|
||||
LocalReloadFixture f;
|
||||
PresetCollection& collection = type == Preset::TYPE_PRINT ? f.bundle.prints :
|
||||
type == Preset::TYPE_FILAMENT ? f.bundle.filaments :
|
||||
f.bundle.printers;
|
||||
const char* directory = type == Preset::TYPE_PRINT ? PRESET_PRINT_NAME :
|
||||
type == Preset::TYPE_FILAMENT ? PRESET_FILAMENT_NAME :
|
||||
PRESET_PRINTER_NAME;
|
||||
const std::string name = type == Preset::TYPE_PRINT ? "Print" : type == Preset::TYPE_FILAMENT ? "Filament" : "Printer";
|
||||
const char* key = type == Preset::TYPE_PRINT ? "layer_height" :
|
||||
type == Preset::TYPE_FILAMENT ? "filament_flow_ratio" :
|
||||
"nozzle_diameter";
|
||||
collection.get_edited_preset().config.set_deserialize_strict(key, "0.37");
|
||||
collection.update_dirty();
|
||||
REQUIRE(collection.current_is_dirty());
|
||||
const PresetBundle before(f.bundle);
|
||||
REQUIRE(fs::remove(f.directory / directory / (name + ".json")));
|
||||
if (rename)
|
||||
write_preset_with_inherits(collection.default_preset().config, f.directory / directory / "Renamed.json", "Renamed", "");
|
||||
|
||||
CHECK_FALSE(f.bundle.reload_local_bundle("", f.id, &f.error));
|
||||
CHECK_THAT(f.error, Catch::Matchers::ContainsSubstring("modified selected preset"));
|
||||
check_reload_state(f.bundle, before);
|
||||
}
|
||||
|
||||
TEST_CASE("Local reload reconciles filament slots and purge volumes while retaining unrelated project options",
|
||||
"[Preset][Bundle][Regression]")
|
||||
{
|
||||
LocalReloadFixture f;
|
||||
DynamicPrintConfig printer_config(f.bundle.printers.get_selected_preset().config);
|
||||
printer_config.option<ConfigOptionFloats>("nozzle_diameter")->values = {0.4, 0.4};
|
||||
write_preset_with_inherits(printer_config, f.directory / PRESET_PRINTER_NAME / "Printer.json", "Printer", "");
|
||||
f.bundle.project_config.option<ConfigOptionFloats>("flush_volumes_matrix")->values = {0.};
|
||||
f.bundle.project_config.option<ConfigOptionFloats>("flush_volumes_vector")->values = {111., 222.};
|
||||
f.bundle.project_config.option<ConfigOptionFloats>("flush_multiplier")->values = {0.75};
|
||||
f.bundle.project_config.option<ConfigOptionStrings>("filament_colour")->values = {"#123456"};
|
||||
const ConfigOption* colour = f.bundle.project_config.option("filament_colour");
|
||||
|
||||
REQUIRE(f.bundle.reload_local_bundle("", f.id, &f.error));
|
||||
CHECK(f.bundle.filament_presets == std::vector<std::string>{f.prefix + "Filament", f.prefix + "Filament"});
|
||||
check_double_vector(f.bundle.project_config.option<ConfigOptionFloats>("flush_multiplier")->values, {0.75, 1.});
|
||||
check_double_vector(f.bundle.project_config.option<ConfigOptionFloats>("flush_volumes_vector")->values, {111., 222., 111., 222.});
|
||||
check_double_vector(f.bundle.project_config.option<ConfigOptionFloats>("flush_volumes_matrix")->values,
|
||||
{0., 333., 333., 0., 0., 333., 333., 0.});
|
||||
CHECK(f.bundle.project_config.option("filament_colour") == colour);
|
||||
CHECK(f.bundle.project_config.option<ConfigOptionStrings>("filament_colour")->values == std::vector<std::string>{"#123456"});
|
||||
}
|
||||
|
||||
TEST_CASE("Local reload rejects a non-FFF printer selection without changing live presets", "[Preset][Bundle][Regression]")
|
||||
{
|
||||
const bool from_source = GENERATE(false, true);
|
||||
LocalReloadFixture f;
|
||||
DynamicPrintConfig printer_config(f.bundle.printers.get_selected_preset().config);
|
||||
printer_config.set_key_value("printer_technology", new ConfigOptionEnum<PrinterTechnology>(ptSLA));
|
||||
if (from_source)
|
||||
write_preset_with_inherits(printer_config, f.directory / PRESET_PRINTER_NAME / "Printer.json", "Printer", "");
|
||||
else
|
||||
f.bundle.printers.load_preset("", "Non-FFF Printer", printer_config, true);
|
||||
const PresetBundle before(f.bundle);
|
||||
|
||||
CHECK_FALSE(f.bundle.reload_local_bundle("", f.id, &f.error));
|
||||
CHECK_THAT(f.error, Catch::Matchers::ContainsSubstring(from_source ? "invalid preset" : "FFF printer selection"));
|
||||
check_reload_state(f.bundle, before);
|
||||
CHECK(f.bundle.bundles.m_bundles.at(f.id).printer_presets == std::vector<std::string>{f.prefix + "Printer"});
|
||||
}
|
||||
|
||||
TEST_CASE("Listing local bundles returns only ids that reload accepts", "[Preset][Bundle]")
|
||||
{
|
||||
ScopedPresetDataDir data_dir;
|
||||
PresetBundle bundle;
|
||||
const fs::path local_dir = data_dir.bundle_dir("unused").parent_path();
|
||||
std::string error;
|
||||
|
||||
CHECK(bundle.list_local_bundle_ids("").empty());
|
||||
CHECK_FALSE(fs::exists(local_dir));
|
||||
CHECK(bundle.list_local_bundle_ids("../outside").empty());
|
||||
|
||||
write_bundle_metadata(data_dir.bundle_dir("valid-b"), "valid-b");
|
||||
write_print_preset_with_layer_height(bundle.prints.default_preset().config,
|
||||
data_dir.bundle_dir("valid-b") / PRESET_PRINT_NAME / "Listed Print.json",
|
||||
"Listed Print", 0.20);
|
||||
write_bundle_metadata(data_dir.bundle_dir("valid-a"), "valid-a");
|
||||
fs::create_directories(data_dir.bundle_dir("no-metadata"));
|
||||
write_bundle_metadata(data_dir.bundle_dir("wrong-id"), "other-id");
|
||||
write_bundle_metadata(data_dir.bundle_dir("subscribed-folder"), "subscribed-folder");
|
||||
fs::create_directories(data_dir.tmp.path() / PRESET_USER_DIR / DEFAULT_USER_FOLDER_NAME / PRESET_SUBSCRIBED_DIR /
|
||||
"subscribed-folder");
|
||||
write_bundle_metadata(data_dir.bundle_dir("subscribed-registry"), "subscribed-registry");
|
||||
BundleMetadata subscribed;
|
||||
subscribed.id = "subscribed-registry";
|
||||
subscribed.bundle_type = BundleType::Subscribed;
|
||||
bundle.bundles.m_bundles.emplace(subscribed.id, subscribed);
|
||||
std::ofstream((local_dir / "stray-file.json").string()) << "{}";
|
||||
|
||||
const size_t print_count = bundle.prints.size();
|
||||
const std::vector<std::string> expected{"valid-a", "valid-b"};
|
||||
CHECK(bundle.list_local_bundle_ids("") == expected);
|
||||
CHECK(bundle.prints.size() == print_count);
|
||||
CHECK_FALSE(fs::exists(data_dir.bundle_dir("no-metadata") / PRESET_BUNDLE_METADATA));
|
||||
|
||||
for (const std::string& rejected : {"no-metadata", "wrong-id", "subscribed-folder", "subscribed-registry"})
|
||||
CHECK_FALSE(bundle.reload_local_bundle("", rejected, &error));
|
||||
|
||||
REQUIRE(bundle.reload_local_bundle("", "valid-b", &error));
|
||||
CHECK(bundle.prints.find_preset(std::string(PRESET_LOCAL_DIR) + "/valid-b/Listed Print", false, true) != nullptr);
|
||||
}
|
||||
|
||||
TEST_CASE("Selected printer uses its default or saved bed type", "[Preset][Bundle]")
|
||||
{
|
||||
PresetBundle bundle;
|
||||
@@ -5957,6 +6508,176 @@ TEST_CASE("Config import confines zip entries, preset names and bundle ids to th
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
void change_json_value(const fs::path& path, const std::string& key, const std::string& value)
|
||||
{
|
||||
nlohmann::json document;
|
||||
{
|
||||
std::ifstream input(path.string());
|
||||
input >> document;
|
||||
}
|
||||
document[key] = value;
|
||||
std::ofstream(path.string()) << document.dump(2);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Shrinking an edited vector survives local reload", "[Preset][Bundle][Regression]")
|
||||
{
|
||||
LocalReloadFixture f;
|
||||
DynamicPrintConfig config(f.bundle.printers.get_selected_preset().config);
|
||||
config.option<ConfigOptionFloats>("nozzle_diameter")->values = {0.4, 0.4};
|
||||
write_preset_with_inherits(config, f.directory / PRESET_PRINTER_NAME / "Printer.json", "Printer", "");
|
||||
REQUIRE(f.bundle.reload_local_bundle("", f.id, &f.error));
|
||||
f.bundle.printers.get_edited_preset().config.option<ConfigOptionFloats>("nozzle_diameter")->values = {0.4};
|
||||
f.bundle.printers.update_dirty();
|
||||
REQUIRE(f.bundle.printers.current_is_dirty());
|
||||
REQUIRE(f.bundle.reload_local_bundle("", f.id, &f.error));
|
||||
CHECK(f.bundle.printers.get_edited_preset().config.option<ConfigOptionFloats>("nozzle_diameter")->values.size() == 1);
|
||||
CHECK(f.bundle.printers.current_is_dirty());
|
||||
}
|
||||
|
||||
TEST_CASE("Removing a selected printer preserves external dirty process edits", "[Preset][Bundle][Regression]")
|
||||
{
|
||||
LocalReloadFixture f;
|
||||
DynamicPrintConfig printer_config(f.bundle.printers.get_selected_preset().config);
|
||||
printer_config.option<ConfigOptionFloats>("nozzle_diameter")->values = {0.6};
|
||||
write_preset_with_inherits(printer_config, f.directory / PRESET_PRINTER_NAME / "Printer.json", "Printer", "");
|
||||
REQUIRE(f.bundle.reload_local_bundle("", f.id, &f.error));
|
||||
Preset& external = add_inmemory_preset(f.bundle.prints, "External Process");
|
||||
external.config.set_key_value("compatible_printers_condition", new ConfigOptionString("nozzle_diameter[0] == 0.6"));
|
||||
f.bundle.prints.select_preset_by_name("External Process", true);
|
||||
f.bundle.update_compatible(PresetSelectCompatibleType::Never);
|
||||
REQUIRE(f.bundle.prints.get_edited_preset().is_compatible);
|
||||
f.bundle.prints.get_edited_preset().config.option<ConfigOptionFloat>("layer_height")->value = 0.27;
|
||||
f.bundle.prints.update_dirty();
|
||||
REQUIRE(f.bundle.prints.current_is_dirty());
|
||||
REQUIRE(fs::remove(f.directory / PRESET_PRINTER_NAME / "Printer.json"));
|
||||
const PresetBundle before(f.bundle);
|
||||
const bool reloaded = f.bundle.reload_local_bundle("", f.id, &f.error);
|
||||
CAPTURE(f.error);
|
||||
CHECK_FALSE(reloaded);
|
||||
check_reload_state(f.bundle, before);
|
||||
CHECK(f.bundle.prints.get_selected_preset_name() == "External Process");
|
||||
CHECK_THAT(layer_height(f.bundle.prints.get_edited_preset()), Catch::Matchers::WithinAbs(0.27, 1e-6));
|
||||
CHECK(f.bundle.prints.current_is_dirty());
|
||||
}
|
||||
|
||||
TEST_CASE("A preset with an invalid version does not silently disappear on reload", "[Preset][Bundle][Regression]")
|
||||
{
|
||||
const auto type = GENERATE(Preset::TYPE_PRINT, Preset::TYPE_FILAMENT, Preset::TYPE_PRINTER);
|
||||
LocalReloadFixture f;
|
||||
const char* directory = type == Preset::TYPE_PRINT ? PRESET_PRINT_NAME :
|
||||
type == Preset::TYPE_FILAMENT ? PRESET_FILAMENT_NAME :
|
||||
PRESET_PRINTER_NAME;
|
||||
const char* name = type == Preset::TYPE_PRINT ? "Print.json" : type == Preset::TYPE_FILAMENT ? "Filament.json" : "Printer.json";
|
||||
change_json_value(f.directory / directory / name, "version", "broken-version");
|
||||
const PresetBundle before(f.bundle);
|
||||
const bool reloaded = f.bundle.reload_local_bundle("", f.id, &f.error);
|
||||
CAPTURE(f.error);
|
||||
CHECK_FALSE(reloaded);
|
||||
check_reload_state(f.bundle, before);
|
||||
CHECK(f.bundle.prints.find_preset(f.prefix + "Print", false, true) != nullptr);
|
||||
}
|
||||
|
||||
TEST_CASE("Hiding a selected source preset does not replay its edits onto another preset", "[Preset][Bundle][Regression]")
|
||||
{
|
||||
LocalReloadFixture f;
|
||||
add_inmemory_preset(f.bundle.prints, "External Process");
|
||||
f.bundle.prints.get_edited_preset().config.option<ConfigOptionFloat>("layer_height")->value = 0.27;
|
||||
f.bundle.prints.update_dirty();
|
||||
REQUIRE(f.bundle.prints.current_is_dirty());
|
||||
change_json_value(f.directory / PRESET_PRINT_NAME / "Print.json", "instantiation", "false");
|
||||
const PresetBundle before(f.bundle);
|
||||
const bool reloaded = f.bundle.reload_local_bundle("", f.id, &f.error);
|
||||
CAPTURE(f.error);
|
||||
CHECK_FALSE(reloaded);
|
||||
check_reload_state(f.bundle, before);
|
||||
CHECK(f.bundle.prints.get_selected_preset_name() == f.prefix + "Print");
|
||||
CHECK_THAT(layer_height(f.bundle.prints.get_edited_preset()), Catch::Matchers::WithinAbs(0.27, 1e-6));
|
||||
CHECK(f.bundle.prints.current_is_dirty());
|
||||
}
|
||||
|
||||
TEST_CASE("Background subscription flags are retained at the reload commit", "[Preset][Bundle][Regression]")
|
||||
{
|
||||
LocalReloadFixture f;
|
||||
bool registered = false;
|
||||
f.bundle.printers.get_edited_preset().config.set_key_value("machine_start_gcode", new ReloadEdit([&] {
|
||||
std::async(std::launch::async, [&] {
|
||||
std::unique_lock<std::shared_mutex> lock(f.bundle.bundles.RWMtx);
|
||||
auto& metadata = f.bundle.bundles.m_bundles.at(f.id);
|
||||
metadata.is_subscribed = true;
|
||||
metadata.update_available = true;
|
||||
}).get();
|
||||
registered = true;
|
||||
}));
|
||||
f.bundle.printers.update_dirty();
|
||||
const PresetBundle before(f.bundle);
|
||||
const bool reloaded = f.bundle.reload_local_bundle("", f.id, &f.error);
|
||||
REQUIRE(registered);
|
||||
CAPTURE(f.error);
|
||||
CHECK_FALSE(reloaded);
|
||||
check_reload_state(f.bundle, before);
|
||||
CHECK(f.bundle.bundles.m_bundles.at(f.id).is_subscribed);
|
||||
CHECK(f.bundle.bundles.m_bundles.at(f.id).update_available);
|
||||
}
|
||||
|
||||
TEST_CASE("An unrelated stored preset remains at the address exposed to consumers", "[Preset][Bundle][Regression]")
|
||||
{
|
||||
LocalReloadFixture f;
|
||||
const std::string name = "External Process";
|
||||
const Preset* external = &add_inmemory_preset(f.bundle.prints, name);
|
||||
const Preset* selected = &f.bundle.prints.get_selected_preset();
|
||||
const Preset* edited = &f.bundle.prints.get_edited_preset();
|
||||
REQUIRE(f.bundle.prints.get_selected_preset_name() != name);
|
||||
write_print_preset_with_layer_height(f.bundle.prints.default_preset().config, f.directory / PRESET_PRINT_NAME / "Print.json", "Print",
|
||||
0.30);
|
||||
REQUIRE(f.bundle.reload_local_bundle("", f.id, &f.error));
|
||||
REQUIRE(f.bundle.prints.find_preset(name, false, true) == external);
|
||||
REQUIRE(&f.bundle.prints.get_selected_preset() == selected);
|
||||
REQUIRE(&f.bundle.prints.get_edited_preset() == edited);
|
||||
CHECK(external->name == name);
|
||||
CHECK_THAT(layer_height(*selected), Catch::Matchers::WithinAbs(0.30, 1e-6));
|
||||
CHECK_THAT(layer_height(*edited), Catch::Matchers::WithinAbs(0.30, 1e-6));
|
||||
}
|
||||
TEST_CASE("Calibration compatibility reflects the reloaded printer settings", "[Preset][Bundle][Regression]")
|
||||
{
|
||||
LocalReloadFixture f;
|
||||
Preset& external = add_inmemory_preset(f.bundle.filaments, "Calibration Filament");
|
||||
external.config.set_key_value("compatible_printers_condition", new ConfigOptionString("nozzle_diameter[0] == 0.4"));
|
||||
f.bundle.set_calibrate_printer(f.prefix + "Printer");
|
||||
REQUIRE(f.bundle.calibrate_filaments.count(&external) == 1);
|
||||
DynamicPrintConfig config(f.bundle.printers.get_selected_preset().config);
|
||||
config.option<ConfigOptionFloats>("nozzle_diameter")->values = {0.6};
|
||||
write_preset_with_inherits(config, f.directory / PRESET_PRINTER_NAME / "Printer.json", "Printer", "");
|
||||
REQUIRE(f.bundle.reload_local_bundle("", f.id, &f.error));
|
||||
const Preset* filament = f.bundle.filaments.find_preset("Calibration Filament", false, true);
|
||||
REQUIRE(filament != nullptr);
|
||||
REQUIRE_FALSE(is_compatible_with_printer(f.bundle.filaments.get_preset_with_vendor_profile(*filament),
|
||||
f.bundle.printers.get_preset_with_vendor_profile(*f.bundle.calibrate_printer)));
|
||||
CHECK(f.bundle.calibrate_filaments.count(filament) == 0);
|
||||
}
|
||||
TEST_CASE("Local reload rolls back prepared collection growth on a late allocation failure", "[Preset][Bundle][Regression]")
|
||||
{
|
||||
LocalReloadFixture f;
|
||||
const size_t original_print_count = f.bundle.prints.size();
|
||||
bool failed = false;
|
||||
Preset& external = add_inmemory_preset(f.bundle.printers, "zzzz External Printer");
|
||||
external.config.set_key_value("machine_start_gcode", new ReloadEdit([&] {
|
||||
if (f.bundle.prints.size() > original_print_count) {
|
||||
failed = true;
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
}));
|
||||
const PresetBundle before(f.bundle);
|
||||
f.change_sources();
|
||||
write_print_preset_with_layer_height(f.bundle.prints.default_preset().config, f.directory / PRESET_PRINT_NAME / "Added Print.json",
|
||||
"Added Print", 0.25);
|
||||
CHECK_FALSE(f.bundle.reload_local_bundle("", f.id, &f.error));
|
||||
REQUIRE(failed);
|
||||
check_reload_state(f.bundle, before);
|
||||
CHECK(f.bundle.bundles.m_bundles.at(f.id).bundle_type == BundleType::Local);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
const std::string kTextureBytes("\x89PNG\x00tex", 8);
|
||||
@@ -7492,3 +8213,158 @@ TEST_CASE("Migrating the selected user filament keeps a printer list edit pendin
|
||||
// What marks the project as having unsaved preset changes.
|
||||
CHECK(filaments.saved_is_dirty() == expected_dirty);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// The settings a file sliced on another machine carries: print, printer and filament presets this
|
||||
// bundle does not have installed.
|
||||
DynamicPrintConfig sender_config(size_t filament_count)
|
||||
{
|
||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||
config.opt<ConfigOptionFloats>("filament_diameter")->values.assign(filament_count, 1.75);
|
||||
config.opt<ConfigOptionStrings>("filament_colour")->values.assign(filament_count, "#FF0000");
|
||||
Preset::normalize(config);
|
||||
config.opt_string("print_settings_id", true) = "Sender Process";
|
||||
config.opt_string("printer_settings_id", true) = "Sender Printer";
|
||||
std::vector<std::string> &filaments = config.option<ConfigOptionStrings>("filament_settings_id", true)->values;
|
||||
for (size_t i = 0; i < filament_count; ++i)
|
||||
filaments.push_back("Sender Filament " + std::to_string(i + 1));
|
||||
return config;
|
||||
}
|
||||
|
||||
size_t project_preset_count(const PresetBundle &bundle)
|
||||
{
|
||||
size_t count = 0;
|
||||
for (const PresetCollection *presets : std::initializer_list<const PresetCollection *>{ &bundle.prints, &bundle.filaments, &bundle.printers })
|
||||
count += std::count_if(presets->begin(), presets->end(), [](const Preset &preset) { return preset.is_project_embedded; });
|
||||
return count;
|
||||
}
|
||||
|
||||
size_t files_under(const fs::path &dir)
|
||||
{
|
||||
size_t count = 0;
|
||||
for (const fs::directory_entry &entry : fs::recursive_directory_iterator(dir))
|
||||
count += fs::is_regular_file(entry.path());
|
||||
return count;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Presets a G-code names but the receiver lacks load as temporary project presets", "[Preset][Bundle]")
|
||||
{
|
||||
// A G-code's settings select presets the way a .gcode.3mf's do, without adding to the user's library.
|
||||
const std::string source = GENERATE("part.gcode", "part.gcode.3mf");
|
||||
CAPTURE(source);
|
||||
ScopedTemporaryDir dir;
|
||||
ScopedDataDir data(dir.path());
|
||||
PresetBundle bundle;
|
||||
bundle.update_user_presets_directory(DEFAULT_USER_FOLDER_NAME);
|
||||
|
||||
bundle.load_config_model(source, sender_config(1));
|
||||
|
||||
for (const PresetCollection *presets : std::initializer_list<const PresetCollection *>{ &bundle.prints, &bundle.filaments, &bundle.printers }) {
|
||||
CAPTURE(presets->get_selected_preset_name());
|
||||
CHECK(presets->get_selected_preset().is_project_embedded);
|
||||
}
|
||||
CHECK(files_under(dir.path()) == 0);
|
||||
|
||||
bundle.reset_project_embedded_presets();
|
||||
CHECK(project_preset_count(bundle) == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Restoring a selection undoes an import that failed part-way", "[Preset][Bundle]")
|
||||
{
|
||||
ScopedTemporaryDir dir;
|
||||
ScopedDataDir data(dir.path());
|
||||
PresetBundle bundle;
|
||||
bundle.update_user_presets_directory(DEFAULT_USER_FOLDER_NAME);
|
||||
add_inmemory_preset(bundle.prints, "My Process");
|
||||
add_inmemory_preset(bundle.prints, "Sender Process");
|
||||
// The file names an installed but hidden process, which the import makes visible.
|
||||
bundle.prints.find_preset("Sender Process", false, true)->is_visible = false;
|
||||
REQUIRE(bundle.prints.select_preset_by_name("My Process", true));
|
||||
// An unsaved edit that the saved baseline already holds, as new_project leaves it.
|
||||
bundle.prints.get_edited_preset().config.opt_float("layer_height") = 0.12;
|
||||
bundle.prints.update_dirty();
|
||||
bundle.prints.update_saved_preset_from_current_preset();
|
||||
REQUIRE(bundle.prints.get_edited_preset().is_dirty);
|
||||
REQUIRE_FALSE(bundle.prints.saved_is_dirty());
|
||||
|
||||
const std::string printer_before = bundle.printers.get_selected_preset_name();
|
||||
const std::vector<std::string> filaments_before = bundle.filament_presets;
|
||||
const DynamicPrintConfig project_before = bundle.project_config;
|
||||
const std::optional<PresetBundle::Selection> previous = bundle.selection();
|
||||
REQUIRE(previous);
|
||||
|
||||
// Two filaments, one with a per-filament list the filament split cannot read: the load throws after
|
||||
// the print and printer presets are selected.
|
||||
DynamicPrintConfig config = sender_config(2);
|
||||
config.option<ConfigOptionStrings>("filament_dev_ams_drying_ams_limitations", true)->values.clear();
|
||||
REQUIRE_THROWS_AS(bundle.load_config_model("part.gcode", std::move(config)), ConfigurationError);
|
||||
REQUIRE(bundle.prints.get_selected_preset_name() == "Sender Process");
|
||||
|
||||
bundle.restore_selection(*previous);
|
||||
|
||||
CHECK(bundle.prints.get_selected_preset_name() == "My Process");
|
||||
CHECK_THAT(bundle.prints.get_edited_preset().config.opt_float("layer_height"), Catch::Matchers::WithinAbs(0.12, 1e-9));
|
||||
CHECK(bundle.prints.get_edited_preset().is_dirty);
|
||||
CHECK_FALSE(bundle.prints.saved_is_dirty());
|
||||
CHECK_FALSE(bundle.prints.find_preset("Sender Process", false, true)->is_visible);
|
||||
CHECK(bundle.printers.get_selected_preset_name() == printer_before);
|
||||
CHECK_FALSE(bundle.printers.saved_is_dirty());
|
||||
CHECK(bundle.filament_presets == filaments_before);
|
||||
CHECK(bundle.project_config == project_before);
|
||||
CHECK(project_preset_count(bundle) == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("A selection is not taken while project presets are loaded", "[Preset][Bundle]")
|
||||
{
|
||||
// Restoring drops every project-embedded preset, which would lose those of the project loaded now.
|
||||
ScopedTemporaryDir dir;
|
||||
ScopedDataDir data(dir.path());
|
||||
PresetBundle bundle;
|
||||
bundle.update_user_presets_directory(DEFAULT_USER_FOLDER_NAME);
|
||||
bundle.load_config_model("project.3mf", sender_config(1));
|
||||
REQUIRE(project_preset_count(bundle) > 0);
|
||||
|
||||
CHECK_FALSE(bundle.selection());
|
||||
}
|
||||
|
||||
TEST_CASE("Selections reach the app config only while no export blocker lives", "[Preset][Bundle]")
|
||||
{
|
||||
PresetBundle bundle;
|
||||
AppConfig app_config;
|
||||
{
|
||||
PresetBundle::SelectionExportBlocker blocker(bundle);
|
||||
bundle.export_selections(app_config);
|
||||
CHECK(app_config.get("presets", PRESET_PRINTER_NAME).empty());
|
||||
}
|
||||
bundle.export_selections(app_config);
|
||||
CHECK(app_config.get("presets", PRESET_PRINTER_NAME) == bundle.printers.get_selected_preset_name());
|
||||
}
|
||||
|
||||
TEST_CASE("An empty choice list in a G-code's settings keeps the value it had before the file was read", "[Preset][Bundle]")
|
||||
{
|
||||
// A G-code written by a tool rather than from presets can leave nozzle_volume_type empty, and the settings
|
||||
// tabs index it per extruder. A blank text list is a value of its own and stays blank.
|
||||
const DynamicPrintConfig block = DynamicPrintConfig::full_print_config();
|
||||
ScopedTemporaryFile file(".gcode");
|
||||
{
|
||||
std::ofstream os(file.string());
|
||||
os << "; generated by OrcaSlicer\n; CONFIG_BLOCK_START\n";
|
||||
for (const std::string &key : block.keys())
|
||||
if (key != "nozzle_volume_type" && key != "filament_multi_colour")
|
||||
os << "; " << key << " = " << block.opt_serialize(key) << '\n';
|
||||
os << "; nozzle_volume_type = \n; filament_multi_colour = \n; CONFIG_BLOCK_END\n";
|
||||
REQUIRE(os.good());
|
||||
}
|
||||
|
||||
PresetBundle bundle;
|
||||
bundle.project_config.set_deserialize_strict("nozzle_volume_type", "High Flow");
|
||||
bundle.project_config.option<ConfigOptionStrings>("filament_multi_colour", true)->values = { "#FF0000 #0000FF" };
|
||||
ConfigSubstitutions substitutions;
|
||||
const DynamicPrintConfig config = bundle.config_from_gcode(file.string(), ForwardCompatibilitySubstitutionRule::Enable, substitutions);
|
||||
|
||||
CHECK(config.opt_serialize("nozzle_volume_type") == "High Flow");
|
||||
CHECK(config.option<ConfigOptionStrings>("filament_multi_colour")->values.empty());
|
||||
}
|
||||
|
||||
@@ -56,6 +56,14 @@ TEST_CASE("Plugin host API exposes host-owned bundle and preset surface to Pytho
|
||||
REQUIRE(has_attr(host, "Model"));
|
||||
REQUIRE(has_attr(host, "ModelObject"));
|
||||
REQUIRE(has_attr(host, "Plater"));
|
||||
REQUIRE(has_attr(host, "preset"));
|
||||
py::object preset = host.attr("preset");
|
||||
REQUIRE(has_attr(preset, "bundles"));
|
||||
py::object bundles = preset.attr("bundles");
|
||||
CHECK(has_attr(bundles, "list_ids"));
|
||||
CHECK(has_attr(bundles, "reload_local"));
|
||||
CHECK_FALSE(has_attr(host, "reload_local_bundle"));
|
||||
CHECK_FALSE(has_attr(host, "list_bundle_ids"));
|
||||
|
||||
py::object preset_bundle_type = host.attr("PresetBundle");
|
||||
CHECK(has_attr(preset_bundle_type, "prints"));
|
||||
@@ -132,6 +140,7 @@ TEST_CASE("Plugin host API exposes host-owned bundle and preset surface to Pytho
|
||||
TEST_CASE("Plugin host API reports unavailable GUI objects before Orca app initialization", "[PluginHost][Python]")
|
||||
{
|
||||
py::object host = import_orca_module().attr("host");
|
||||
py::object bundles = host.attr("preset").attr("bundles");
|
||||
|
||||
for (const char* function_name : { "preset_bundle", "plater", "model", "app_info", "selected_printer" }) {
|
||||
CAPTURE(function_name);
|
||||
@@ -143,6 +152,22 @@ TEST_CASE("Plugin host API reports unavailable GUI objects before Orca app initi
|
||||
CHECK(std::string(error.what()).find("OrcaSlicer application is not initialized") != std::string::npos);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
bundles.attr("reload_local")("test-bundle");
|
||||
FAIL("host.preset.bundles.reload_local unexpectedly succeeded without a wx application");
|
||||
} catch (const py::error_already_set& error) {
|
||||
CHECK(error.matches(PyExc_RuntimeError));
|
||||
CHECK(std::string(error.what()).find("OrcaSlicer application is not initialized") != std::string::npos);
|
||||
}
|
||||
|
||||
try {
|
||||
bundles.attr("list_ids")();
|
||||
FAIL("host.preset.bundles.list_ids unexpectedly succeeded without a wx application");
|
||||
} catch (const py::error_already_set& error) {
|
||||
CHECK(error.matches(PyExc_RuntimeError));
|
||||
CHECK(std::string(error.what()).find("OrcaSlicer application is not initialized") != std::string::npos);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Plugin host API reports no GL details before the 3D view exists", "[PluginHost][Python]")
|
||||
|
||||
Reference in New Issue
Block a user