Merge branch 'OrcaSlicer:main' into harktech/k2-discovery

This commit is contained in:
Grant Harkness
2026-05-19 21:05:55 +01:00
committed by GitHub
87 changed files with 1381 additions and 694 deletions
-8
View File
@@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
-2
View File
@@ -1,2 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module classpath="CMake" type="CPP_MODULE" version="4" />
-7
View File
@@ -1,7 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<clangFormatSettings>
<option name="ENABLED" value="true" />
</clangFormatSettings>
</code_scheme>
</component>
-5
View File
@@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>
-4
View File
@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
</project>
-8
View File
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/OrcaSlicer.iml" filepath="$PROJECT_DIR$/.idea/OrcaSlicer.iml" />
</modules>
</component>
</project>
Generated
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -94,8 +94,8 @@ text = Switch workspaces\nYou can switch between <b>Prepare</b> and <b>Preview</
[hint:How to use keyboard shortcuts] [hint:How to use keyboard shortcuts]
text = How to use keyboard shortcuts\nDid you know that Orca Slicer offers a wide range of keyboard shortcuts and 3D scene operations? text = How to use keyboard shortcuts\nDid you know that Orca Slicer offers a wide range of keyboard shortcuts and 3D scene operations?
[hint:Reverse on odd] [hint:Reverse on even]
text = Reverse on odd\nDid you know that <b>Reverse on odd</b> feature can significantly improve the surface quality of your overhangs? text = Reverse on even\nDid you know that <b>Reverse on even</b> feature can significantly improve the surface quality of your overhangs? However, it can cause wall inconsistencies so use carefully!
[hint:Cut Tool] [hint:Cut Tool]
text = Cut Tool\nDid you know that you can cut a model at any angle and position with the cutting tool? text = Cut Tool\nDid you know that you can cut a model at any angle and position with the cutting tool?
+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><polygon points=".5 .5 5.5 7.5 5.5 13.5 9.5 15.5 9.5 7.5 14.5 .5 .5 .5" style="fill:none; stroke:#949494; stroke-linecap:round; stroke-linejoin:round;"/><line x1="8.64" y1="4.5" x2="10.19" y2="2.34" style="fill:none; stroke:#949494; stroke-linecap:round; stroke-linejoin:round;"/></svg>

After

Width:  |  Height:  |  Size: 414 B

@@ -61,7 +61,7 @@
"nil" "nil"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

@@ -1,237 +0,0 @@
{
"type": "machine",
"name": "Anet A8 Plus 0.4 nozzle",
"inherits": "",
"from": "User",
"adaptive_bed_mesh_margin": "0",
"auxiliary_fan": "0",
"bbl_use_printhost": "0",
"bed_custom_model": "",
"bed_custom_texture": "",
"bed_exclude_area": [
"0x0"
],
"bed_mesh_max": "99999,99999",
"bed_mesh_min": "-99999,-99999",
"bed_mesh_probe_distance": "50,50",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n",
"best_object_pos": "0.5,0.5",
"change_extrusion_role_gcode": "",
"change_filament_gcode": "M600",
"cooling_tube_length": "5",
"cooling_tube_retraction": "91.5",
"default_bed_type": "",
"default_filament_profile": [
"Anycubic Generic PLA"
],
"default_print_profile": "0.20mm Standard @Anycubic 4MaxPro2",
"deretraction_speed": [
"25"
],
"disable_m73": "0",
"emit_machine_limits_to_gcode": "1",
"enable_filament_ramming": "1",
"enable_long_retraction_when_cut": "0",
"extra_loading_move": "-2",
"extruder_clearance_height_to_lid": "140",
"extruder_clearance_height_to_rod": "36",
"extruder_clearance_radius": "65",
"extruder_colour": [
"#018001"
],
"extruder_offset": [
"0x0"
],
"fan_kickstart": "0",
"fan_speedup_overhangs": "1",
"fan_speedup_time": "0",
"gcode_flavor": "marlin",
"head_wrap_detect_zone": [],
"high_current_on_filament_swap": "0",
"host_type": "octoprint",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"long_retractions_when_cut": [
"0"
],
"machine_end_gcode": "M104 S0 ; turn off extruder heating\nM140 S0 ; turn off bed heating\nM107 ; turn off fans\nG91 ; relative positioning\nG0 Z+0.5 ; move Z up a tiny bit\nG90 ; absolute positioning\nG0 X135 Y105 F{machine_max_speed_x[0]*60} ; move extruder to center position\nG0 Z190.5 F{machine_max_speed_z[0]*60} ; lower the plattform to Z min\nM84 ; steppers off\nG90 ; absolute positioning\n",
"machine_load_filament_time": "0",
"machine_max_acceleration_e": [
"5000",
"5000"
],
"machine_max_acceleration_extruding": [
"1250",
"1250"
],
"machine_max_acceleration_retracting": [
"1250",
"1250"
],
"machine_max_acceleration_travel": [
"1500",
"1500"
],
"machine_max_acceleration_x": [
"1000",
"900"
],
"machine_max_acceleration_y": [
"1000",
"900"
],
"machine_max_acceleration_z": [
"1000",
"100"
],
"machine_max_jerk_e": [
"5",
"5"
],
"machine_max_jerk_x": [
"6",
"6"
],
"machine_max_jerk_y": [
"6",
"6"
],
"machine_max_jerk_z": [
"0.2",
"0.2"
],
"machine_max_junction_deviation": [
"0.10",
"0"
],
"machine_max_speed_e": [
"120",
"120"
],
"machine_max_speed_x": [
"200",
"200"
],
"machine_max_speed_y": [
"200",
"200"
],
"machine_max_speed_z": [
"64",
"16"
],
"machine_min_extruding_rate": [
"0",
"0"
],
"machine_min_travel_rate": [
"0",
"0"
],
"machine_pause_gcode": "M601",
"machine_start_gcode": "G21 ; metric values\nG90 ; absolute positioning\nM82 ; set extruder to absolute mode\nM140 S[first_layer_bed_temperature] ; set bed temp\nG28 X0 Y0 ; home X and Y\nG28 Z0 ; home Z\nG1 Z30 F{machine_max_speed_z[0]*60} ; move Z a bit down to not blow on the bed edge while heating\nG1 X10 F3900 ; let some space on x to prevent the filament cooling exhaust from beeing blocked by the servo motor\nM190 S[bed_temperature_initial_layer_single] ; wait for bed temp\nM104 S[nozzle_temperature_initial_layer] ; set extruder temp\nM106 S80 ; turn on fan to prevent air nozzle melt while heating up\nM109 S[nozzle_temperature_initial_layer] ; wait for extruder temp\nM107 ; start with the fan off\nG28 X0 ; goto X home again\nG92 E0 ; zero the extruded length\nG1 Z0.2 F360 ; move plattform upwards\n; extrude material next to the plattform (comment or remove following lines to disable)\nG1 F180 E20 ; extrude some material next to the plattform\nG92 E0 ; zero the extruded length\nG1 E-[retraction_length] F{retraction_speed[0]*60} ; do a filament retract\nG92 E0 ; zero the extruded length again\nG1 X5 F3900 ; move sideways to get rid of that string\nG1 E[retraction_length] F{retraction_speed[0]*60} ; do a filament deretract with retract parameters\nG92 E0 ; zero the extruded length again\n; draw intro line (comment or remove following lines to disable)\nG1 X30 E5 F700 ; draw intro line\nG92 E0 ; zero the extruded length\nG1 E-[retraction_length] F{retraction_speed[0]*60} ; do a filament retract\nG1 X40 Z2.0 ; move away from the introline\nG92 E0 ; zero the extruded length again\nG1 E[retraction_length] F{retraction_speed[0]*60} ; do a filament deretract with retract parameters\n; end of intro line code\nM117 Printing...\nG5",
"machine_tool_change_time": "0",
"machine_unload_filament_time": "0",
"manual_filament_change": "0",
"max_layer_height": [
"0.3"
],
"max_resonance_avoidance_speed": "120",
"min_layer_height": [
"0.07"
],
"min_resonance_avoidance_speed": "70",
"nozzle_diameter": [
"0.4"
],
"nozzle_height": "2.5",
"nozzle_hrc": "0",
"nozzle_type": "undefine",
"nozzle_volume": "0",
"parking_pos_retraction": "92",
"pellet_modded_printer": "0",
"preferred_orientation": "0",
"printable_area": [
"0x0",
"300x0",
"300x300",
"0x300"
],
"printable_height": "350",
"printer_model": "Anet A8 Plus",
"printer_notes": "",
"printer_settings_id": "Anet A8 Plus 0.4 nozzle",
"printer_structure": "undefine",
"printer_technology": "FFF",
"printer_variant": "0.4",
"printhost_authorization_type": "key",
"printhost_ssl_ignore_revoke": "0",
"printing_by_object_gcode": "",
"purge_in_prime_tower": "1",
"resonance_avoidance": "0",
"retract_before_wipe": [
"0%"
],
"retract_length_toolchange": [
"10"
],
"retract_lift_above": [
"0"
],
"retract_lift_below": [
"0"
],
"retract_lift_enforce": [
"All Surfaces"
],
"retract_restart_extra": [
"0"
],
"retract_restart_extra_toolchange": [
"0"
],
"retract_when_changing_layer": [
"1"
],
"retraction_distances_when_cut": [
"18"
],
"retraction_length": [
"2.5"
],
"retraction_minimum_travel": [
"2"
],
"retraction_speed": [
"35"
],
"scan_first_layer": "0",
"silent_mode": "0",
"single_extruder_multi_material": "1",
"support_air_filtration": "1",
"support_chamber_temp_control": "1",
"support_multi_bed_types": "0",
"template_custom_gcode": "",
"thumbnails": "48x48/PNG,300x300/PNG",
"thumbnails_format": "PNG",
"time_cost": "0",
"time_lapse_gcode": "",
"travel_slope": [
"3"
],
"upward_compatible_machine": [],
"use_firmware_retraction": "0",
"use_relative_e_distances": "1",
"wipe": [
"1"
],
"wipe_distance": [
"1"
],
"z_hop": [
"0.4"
],
"z_hop_types": [
"Normal Lift"
],
"z_offset": "0"
}
@@ -58,7 +58,7 @@
"1.75" "1.75"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
File diff suppressed because one or more lines are too long
@@ -58,7 +58,7 @@
"1.75" "1.75"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
@@ -97,7 +97,7 @@
"nil" "nil"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_ramming_volumetric_speed": [ "filament_ramming_volumetric_speed": [
"-1" "-1"
@@ -64,7 +64,7 @@
"1.75" "1.75"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
@@ -64,7 +64,7 @@
"1.75" "1.75"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
@@ -67,7 +67,7 @@
"1.75" "1.75"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
@@ -64,7 +64,7 @@
"1.75" "1.75"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
@@ -64,7 +64,7 @@
"1.75" "1.75"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
@@ -64,7 +64,7 @@
"1.75" "1.75"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
@@ -14,7 +14,7 @@
"15" "15"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "8"
], ],
"filament_type": [ "filament_type": [
"PETG" "PETG"
@@ -6,7 +6,7 @@
"instantiation": "false", "instantiation": "false",
"fan_cooling_layer_time": "100", "fan_cooling_layer_time": "100",
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_type": [ "filament_type": [
"PLA" "PLA"
@@ -14,7 +14,7 @@
"30" "30"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "3.6"
], ],
"filament_type": [ "filament_type": [
"TPU" "TPU"
@@ -40,7 +40,7 @@
"1.75" "1.75"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
@@ -64,7 +64,7 @@
"1.75" "1.75"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
@@ -58,7 +58,7 @@
"1.75" "1.75"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
@@ -58,7 +58,7 @@
"1.75" "1.75"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
@@ -64,7 +64,7 @@
"1.75" "1.75"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
@@ -38,7 +38,7 @@
"15" "15"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "8"
], ],
"filament_type": [ "filament_type": [
"PETG" "PETG"
@@ -8,7 +8,7 @@
"100" "100"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_type": [ "filament_type": [
"PLA" "PLA"
@@ -64,7 +64,7 @@
"1.75" "1.75"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
@@ -64,7 +64,7 @@
"1.75" "1.75"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
@@ -64,7 +64,7 @@
"1.75" "1.75"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
@@ -64,7 +64,7 @@
"1.75" "1.75"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
@@ -64,7 +64,7 @@
"1.75" "1.75"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
@@ -64,7 +64,7 @@
"1.75" "1.75"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
@@ -64,7 +64,7 @@
"1.75" "1.75"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
@@ -64,7 +64,7 @@
"1.75" "1.75"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
@@ -64,7 +64,7 @@
"1.75" "1.75"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
@@ -70,7 +70,7 @@
"1.75" "1.75"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
@@ -61,7 +61,7 @@
"nil" "nil"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
@@ -58,7 +58,7 @@
"1.75" "1.75"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
@@ -58,7 +58,7 @@
"1.75" "1.75"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
@@ -58,7 +58,7 @@
"1.75" "1.75"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
@@ -61,7 +61,7 @@
"1.75" "1.75"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
@@ -64,7 +64,7 @@
"1.75" "1.75"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
@@ -64,7 +64,7 @@
"1.75" "1.75"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
@@ -67,7 +67,7 @@
"1.75" "1.75"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"0" "0"
@@ -61,7 +61,7 @@
"nil" "nil"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
@@ -64,7 +64,7 @@
"1.75" "1.75"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
@@ -64,7 +64,7 @@
"1.75" "1.75"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
@@ -61,7 +61,7 @@
"nil" "nil"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
@@ -64,7 +64,7 @@
"2.85" "2.85"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"0" "12"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15" "15"
+16 -13
View File
@@ -719,6 +719,9 @@ std::string CoolingBuffer::apply_layer_cooldown(
// Second generate the adjusted G-code. // Second generate the adjusted G-code.
std::string new_gcode; std::string new_gcode;
new_gcode.reserve(gcode.size() * 2); new_gcode.reserve(gcode.size() * 2);
// ORCA: per-printer minimum non-zero fan PWM. Applied at every part-cooling fan emission below so any
// non-zero command is raised to at least this percent (0 disables the clamp).
const unsigned int part_cooling_fan_min_pwm = static_cast<unsigned int>(std::max(0, m_config.part_cooling_fan_min_pwm.value));
bool overhang_fan_control= false; bool overhang_fan_control= false;
int overhang_fan_speed = 0; int overhang_fan_speed = 0;
bool internal_bridge_fan_control= false; // ORCA: Add support for separate internal bridge fan speed control bool internal_bridge_fan_control= false; // ORCA: Add support for separate internal bridge fan speed control
@@ -727,7 +730,7 @@ std::string CoolingBuffer::apply_layer_cooldown(
int supp_interface_fan_speed = 0; int supp_interface_fan_speed = 0;
bool ironing_fan_control= false; // ORCA: Add support for ironing fan speed control bool ironing_fan_control= false; // ORCA: Add support for ironing fan speed control
int ironing_fan_speed = 0; // ORCA: Add support for ironing fan speed control int ironing_fan_speed = 0; // ORCA: Add support for ironing fan speed control
auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode, auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode, part_cooling_fan_min_pwm,
&overhang_fan_control, &overhang_fan_speed, &overhang_fan_control, &overhang_fan_speed,
&internal_bridge_fan_control, &internal_bridge_fan_speed, &internal_bridge_fan_control, &internal_bridge_fan_speed,
&supp_interface_fan_control, &supp_interface_fan_speed, &supp_interface_fan_control, &supp_interface_fan_speed,
@@ -743,11 +746,11 @@ std::string CoolingBuffer::apply_layer_cooldown(
int full_fan_speed_layer = EXTRUDER_CONFIG(full_fan_speed_layer); int full_fan_speed_layer = EXTRUDER_CONFIG(full_fan_speed_layer);
supp_interface_fan_speed = EXTRUDER_CONFIG(support_material_interface_fan_speed); supp_interface_fan_speed = EXTRUDER_CONFIG(support_material_interface_fan_speed);
if (close_fan_the_first_x_layers <= 0 && full_fan_speed_layer > 0) { // ORCA: previously a silent override forced `close_fan_the_first_x_layers` from 0 up to 1 whenever a ramp
// When ramping up fan speed from close_fan_the_first_x_layers to full_fan_speed_layer, force close_fan_the_first_x_layers above zero, // was configured (`full_fan_speed_layer > 0`), so the first printed layer always had the fan disabled.
// so there will be a zero fan speed at least at the 1st layer. // That hid the user's literal "no cooling for the first 0 layers" setting and produced a non-zero starting
close_fan_the_first_x_layers = 1; // factor on the ramp denominator. The override has been removed: with N=0 and M>0 the ramp now genuinely
} // starts on layer 0 at a factor of 1/M and reaches 100% at layer M-1, matching the intent of the option.
if (int(layer_id) >= close_fan_the_first_x_layers) { if (int(layer_id) >= close_fan_the_first_x_layers) {
float fan_max_speed = EXTRUDER_CONFIG(fan_max_speed); float fan_max_speed = EXTRUDER_CONFIG(fan_max_speed);
float slow_down_layer_time = float(EXTRUDER_CONFIG(slow_down_layer_time)); float slow_down_layer_time = float(EXTRUDER_CONFIG(slow_down_layer_time));
@@ -806,7 +809,7 @@ std::string CoolingBuffer::apply_layer_cooldown(
m_fan_speed = fan_speed_new; m_fan_speed = fan_speed_new;
m_current_fan_speed = fan_speed_new; m_current_fan_speed = fan_speed_new;
if (immediately_apply) if (immediately_apply)
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_fan_speed); new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_fan_speed, part_cooling_fan_min_pwm);
} }
//BBS //BBS
if (additional_fan_speed_new != m_additional_fan_speed) { if (additional_fan_speed_new != m_additional_fan_speed) {
@@ -980,26 +983,26 @@ std::string CoolingBuffer::apply_layer_cooldown(
if (need_set_fan) { if (need_set_fan) {
if (fan_speed_change_requests[CoolingLine::TYPE_OVERHANG_FAN_START]){ if (fan_speed_change_requests[CoolingLine::TYPE_OVERHANG_FAN_START]){
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, overhang_fan_speed); new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, overhang_fan_speed, part_cooling_fan_min_pwm);
m_current_fan_speed = overhang_fan_speed; m_current_fan_speed = overhang_fan_speed;
} else if (fan_speed_change_requests[CoolingLine::TYPE_INTERNAL_BRIDGE_FAN_START]){ // ORCA: Add support for separate internal bridge fan speed control } else if (fan_speed_change_requests[CoolingLine::TYPE_INTERNAL_BRIDGE_FAN_START]){ // ORCA: Add support for separate internal bridge fan speed control
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, internal_bridge_fan_speed); new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, internal_bridge_fan_speed, part_cooling_fan_min_pwm);
m_current_fan_speed = internal_bridge_fan_speed; m_current_fan_speed = internal_bridge_fan_speed;
} }
else if (fan_speed_change_requests[CoolingLine::TYPE_SUPPORT_INTERFACE_FAN_START]){ else if (fan_speed_change_requests[CoolingLine::TYPE_SUPPORT_INTERFACE_FAN_START]){
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, supp_interface_fan_speed); new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, supp_interface_fan_speed, part_cooling_fan_min_pwm);
m_current_fan_speed = supp_interface_fan_speed; m_current_fan_speed = supp_interface_fan_speed;
} }
else if (fan_speed_change_requests[CoolingLine::TYPE_IRONING_FAN_START]){ else if (fan_speed_change_requests[CoolingLine::TYPE_IRONING_FAN_START]){
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, ironing_fan_speed); new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, ironing_fan_speed, part_cooling_fan_min_pwm);
m_current_fan_speed = ironing_fan_speed; m_current_fan_speed = ironing_fan_speed;
} }
else if(fan_speed_change_requests[CoolingLine::TYPE_FORCE_RESUME_FAN] && m_current_fan_speed != -1){ else if(fan_speed_change_requests[CoolingLine::TYPE_FORCE_RESUME_FAN] && m_current_fan_speed != -1){
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_current_fan_speed); new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_current_fan_speed, part_cooling_fan_min_pwm);
fan_speed_change_requests[CoolingLine::TYPE_FORCE_RESUME_FAN] = false; fan_speed_change_requests[CoolingLine::TYPE_FORCE_RESUME_FAN] = false;
} }
else else
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_fan_speed); new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_fan_speed, part_cooling_fan_min_pwm);
need_set_fan = false; need_set_fan = false;
} }
pos = line_end; pos = line_end;
+4 -1
View File
@@ -230,7 +230,10 @@ void FanMover::_remove_slow_fan(int16_t min_speed, float past_sec) {
std::string FanMover::_set_fan(int16_t speed) { std::string FanMover::_set_fan(int16_t speed) {
//const Tool* tool = m_writer.get_tool(m_currrent_extruder < 20 ? m_currrent_extruder : 0); //const Tool* tool = m_writer.get_tool(m_currrent_extruder < 20 ? m_currrent_extruder : 0);
return GCodeWriter::set_fan(m_writer.config.gcode_flavor.value, speed); // ORCA: apply the per-printer non-zero fan PWM floor so reposted fan commands respect the clamp too.
const int floor_pct = m_writer.config.part_cooling_fan_min_pwm.value;
const unsigned int part_cooling_fan_min_pwm = floor_pct > 0 ? static_cast<unsigned int>(floor_pct) : 0u;
return GCodeWriter::set_fan(m_writer.config.gcode_flavor.value, speed, part_cooling_fan_min_pwm);
} }
+28 -3
View File
@@ -1230,8 +1230,15 @@ void GCodeProcessor::run_post_process()
} }
}; };
// Orca: track the current layer during the post-processing pass so that preheat M104s emitted
// for tool changes on the first layer use the correct first-layer temperature. The member
// m_layer_id is populated during the analysis pass and ends at the total layer count, so it
// cannot be used here — it would always select the "other layers" temperature for multi-layer
// prints.
unsigned int current_layer_id = 0;
// add lines M104 to exported gcode // add lines M104 to exported gcode
auto process_line_T = [this, &export_line](const std::string& gcode_line, const size_t g1_lines_counter, const ExportLines::Backtrace& backtrace) { auto process_line_T = [this, &export_line, &current_layer_id](const std::string& gcode_line, const size_t g1_lines_counter, const ExportLines::Backtrace& backtrace) {
const std::string cmd = GCodeReader::GCodeLine::extract_cmd(gcode_line); const std::string cmd = GCodeReader::GCodeLine::extract_cmd(gcode_line);
int tool_number = -1; int tool_number = -1;
@@ -1257,8 +1264,13 @@ void GCodeProcessor::run_post_process()
export_line.insert_lines( export_line.insert_lines(
backtrace, cmd, backtrace, cmd,
// line inserter // line inserter
[tool_number, this](unsigned int id, const std::vector<float>& time_diffs) { [tool_number, this, &current_layer_id](unsigned int id, const std::vector<float>& time_diffs) {
const int temperature = int(m_layer_id != 1 ? m_filament_nozzle_temp[tool_number] : // Orca: use the locally-tracked layer index (current_layer_id) rather than
// the stale m_layer_id from the analysis pass. current_layer_id == 0 means
// we haven't reached the first ;LAYER_CHANGE marker yet (e.g. tool change
// inside start gcode); == 1 means we are inside the first printed layer.
// Both cases should use the first-layer nozzle temperature.
const int temperature = int(current_layer_id > 1 ? m_filament_nozzle_temp[tool_number] :
m_filament_nozzle_temp_first_layer[tool_number]); m_filament_nozzle_temp_first_layer[tool_number]);
// Orca: M104.1 for XL printers, I can't find the documentation for this so I copied the C++ comments from // Orca: M104.1 for XL printers, I can't find the documentation for this so I copied the C++ comments from
// Prusa-Firmware-Buddy here // Prusa-Firmware-Buddy here
@@ -1351,6 +1363,19 @@ void GCodeProcessor::run_post_process()
if (eol) { if (eol) {
++line_id; ++line_id;
const unsigned int internal_g1_lines_counter = export_line.update(gcode_line, line_id, g1_lines_counter); const unsigned int internal_g1_lines_counter = export_line.update(gcode_line, line_id, g1_lines_counter);
// Orca: track the current layer for preheat temperature selection.
// The line is ";" + reserved_tag(Layer_Change) + EOL; match it independent of
// BBL vs. compatible flavor (which differ in the tag text).
if (gcode_line.size() > 1 && gcode_line.front() == ';') {
std::string_view tag_line(gcode_line);
// strip trailing CR/LF
while (!tag_line.empty() && (tag_line.back() == '\n' || tag_line.back() == '\r'))
tag_line.remove_suffix(1);
// strip leading ';'
tag_line.remove_prefix(1);
if (tag_line == reserved_tag(ETags::Layer_Change))
++current_layer_id;
}
// replace placeholder lines // replace placeholder lines
bool processed = process_placeholders(gcode_line); bool processed = process_placeholders(gcode_line);
if (processed) if (processed)
+8 -2
View File
@@ -1092,9 +1092,13 @@ std::string GCodeWriter::unlift()
return gcode; return gcode;
} }
std::string GCodeWriter::set_fan(const GCodeFlavor gcode_flavor, unsigned int speed) std::string GCodeWriter::set_fan(const GCodeFlavor gcode_flavor, unsigned int speed, unsigned int part_cooling_fan_min_pwm)
{ {
std::ostringstream gcode; std::ostringstream gcode;
// ORCA: clamp non-zero fan commands up to the configured PWM floor so fans that can't spool at low duty
// cycles still start reliably. Zero (fan off) is preserved exactly so disable-fan commands are never altered.
if (speed > 0 && part_cooling_fan_min_pwm > 0 && speed < part_cooling_fan_min_pwm)
speed = part_cooling_fan_min_pwm;
if (speed == 0) { if (speed == 0) {
switch (gcode_flavor) { switch (gcode_flavor) {
case gcfTeacup: case gcfTeacup:
@@ -1129,7 +1133,9 @@ std::string GCodeWriter::set_fan(const GCodeFlavor gcode_flavor, unsigned int sp
std::string GCodeWriter::set_fan(unsigned int speed) const std::string GCodeWriter::set_fan(unsigned int speed) const
{ {
//BBS //BBS
return GCodeWriter::set_fan(this->config.gcode_flavor, speed); // ORCA: pick up the per-printer PWM floor from the active config.
return GCodeWriter::set_fan(this->config.gcode_flavor, speed,
static_cast<unsigned int>(std::max(0, this->config.part_cooling_fan_min_pwm.value)));
} }
//BBS: set additional fan speed for BBS machine only //BBS: set additional fan speed for BBS machine only
+3 -1
View File
@@ -98,7 +98,9 @@ public:
void set_xy_offset(double x, double y) { m_x_offset = x; m_y_offset = y; } void set_xy_offset(double x, double y) { m_x_offset = x; m_y_offset = y; }
Vec2f get_xy_offset() { return Vec2f{m_x_offset, m_y_offset}; }; Vec2f get_xy_offset() { return Vec2f{m_x_offset, m_y_offset}; };
// To be called by the CoolingBuffer from another thread. // To be called by the CoolingBuffer from another thread.
static std::string set_fan(const GCodeFlavor gcode_flavor, unsigned int speed); // ORCA: `part_cooling_fan_min_pwm` (0-100, default 0) is a floor applied only when `speed` is non-zero, used to overcome
// PWM start-up thresholds on fans that won't spool below a certain duty cycle. A `speed` of 0 is always honoured.
static std::string set_fan(const GCodeFlavor gcode_flavor, unsigned int speed, unsigned int part_cooling_fan_min_pwm = 0);
// To be called by the main thread. It always emits the G-code, it does not remember the previous state. // To be called by the main thread. It always emits the G-code, it does not remember the previous state.
// Keeping the state is left to the CoolingBuffer, which runs asynchronously on another thread. // Keeping the state is left to the CoolingBuffer, which runs asynchronously on another thread.
std::string set_fan(unsigned int speed) const; std::string set_fan(unsigned int speed) const;
+6
View File
@@ -159,6 +159,12 @@ bool Layer::is_perimeter_compatible(const PrintRegion& a, const PrintRegion& b)
&& config.detect_thin_wall == other_config.detect_thin_wall && config.detect_thin_wall == other_config.detect_thin_wall
&& config.infill_wall_overlap == other_config.infill_wall_overlap && config.infill_wall_overlap == other_config.infill_wall_overlap
&& config.top_bottom_infill_wall_overlap == other_config.top_bottom_infill_wall_overlap && config.top_bottom_infill_wall_overlap == other_config.top_bottom_infill_wall_overlap
// Orca: these flags directly change the effective wall count produced by the perimeter
// generator. If two regions disagree on any of them, merging their slices into one shared make_perimeters
// call would silently use the first region's flag for both.
&& config.only_one_wall_first_layer == other_config.only_one_wall_first_layer
&& config.only_one_wall_top == other_config.only_one_wall_top
&& config.min_width_top_surface == other_config.min_width_top_surface
&& config.seam_slope_type == other_config.seam_slope_type && config.seam_slope_type == other_config.seam_slope_type
&& config.seam_slope_conditional == other_config.seam_slope_conditional && config.seam_slope_conditional == other_config.seam_slope_conditional
&& config.scarf_angle_threshold == other_config.scarf_angle_threshold && config.scarf_angle_threshold == other_config.scarf_angle_threshold
+1 -1
View File
@@ -1326,7 +1326,7 @@ static std::vector<std::string> s_Preset_machine_limits_options {
static std::vector<std::string> s_Preset_printer_options { static std::vector<std::string> s_Preset_printer_options {
"printer_technology", "printer_technology",
"printable_area", "extruder_printable_area", "bed_exclude_area","bed_custom_texture", "bed_custom_model", "gcode_flavor", "printable_area", "extruder_printable_area", "bed_exclude_area","bed_custom_texture", "bed_custom_model", "gcode_flavor",
"fan_kickstart", "fan_speedup_time", "fan_speedup_overhangs", "fan_kickstart", "part_cooling_fan_min_pwm", "fan_speedup_time", "fan_speedup_overhangs",
"single_extruder_multi_material", "manual_filament_change", "file_start_gcode", "machine_start_gcode", "machine_end_gcode", "before_layer_change_gcode", "printing_by_object_gcode", "layer_change_gcode", "time_lapse_gcode", "wrapping_detection_gcode", "change_filament_gcode", "change_extrusion_role_gcode", "single_extruder_multi_material", "manual_filament_change", "file_start_gcode", "machine_start_gcode", "machine_end_gcode", "before_layer_change_gcode", "printing_by_object_gcode", "layer_change_gcode", "time_lapse_gcode", "wrapping_detection_gcode", "change_filament_gcode", "change_extrusion_role_gcode",
"printer_model", "printer_variant", "printer_extruder_id", "printer_extruder_variant", "extruder_variant_list", "default_nozzle_volume_type", "printer_model", "printer_variant", "printer_extruder_id", "printer_extruder_variant", "extruder_variant_list", "default_nozzle_volume_type",
"printable_height", "extruder_printable_height", "extruder_clearance_radius", "extruder_clearance_height_to_lid", "extruder_clearance_height_to_rod", "printable_height", "extruder_printable_height", "extruder_clearance_radius", "extruder_clearance_height_to_lid", "extruder_clearance_height_to_rod",
+33 -31
View File
@@ -133,6 +133,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
"fan_cooling_layer_time", "fan_cooling_layer_time",
"full_fan_speed_layer", "full_fan_speed_layer",
"fan_kickstart", "fan_kickstart",
"part_cooling_fan_min_pwm",
"fan_speedup_overhangs", "fan_speedup_overhangs",
"fan_speedup_time", "fan_speedup_time",
"filament_colour", "filament_colour",
@@ -1753,46 +1754,47 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
}; };
std::string warning_key; std::string warning_key;
const auto max_junction_deviation = m_config.machine_max_junction_deviation.values[0];
const bool ignore_jerk_validation = m_config.gcode_flavor == gcfMarlinFirmware && max_junction_deviation > 0;
// check jerk // check jerk
if (m_default_object_config.default_jerk == 1 || m_default_object_config.outer_wall_jerk == 1 || if (!ignore_jerk_validation) {
m_default_object_config.inner_wall_jerk == 1) { if (m_default_object_config.default_jerk == 1 || m_default_object_config.outer_wall_jerk == 1 ||
warning->string = L("Setting the jerk speed too low could lead to artifacts on curved surfaces"); m_default_object_config.inner_wall_jerk == 1) {
if (m_default_object_config.outer_wall_jerk == 1) warning->string = L("Setting the jerk speed too low could lead to artifacts on curved surfaces");
warning_key = "outer_wall_jerk"; if (m_default_object_config.outer_wall_jerk == 1)
else if (m_default_object_config.inner_wall_jerk == 1) warning_key = "outer_wall_jerk";
warning_key = "inner_wall_jerk"; else if (m_default_object_config.inner_wall_jerk == 1)
else warning_key = "inner_wall_jerk";
warning_key = "default_jerk"; else
warning_key = "default_jerk";
warning->opt_key = warning_key; warning->opt_key = warning_key;
} }
if (warning_key.empty() && m_default_object_config.default_jerk > 0) { if (warning_key.empty() && m_default_object_config.default_jerk > 0) {
std::vector<std::string> jerk_to_check = {"default_jerk", "outer_wall_jerk", "inner_wall_jerk", "infill_jerk", std::vector<std::string> jerk_to_check = {"default_jerk", "outer_wall_jerk", "inner_wall_jerk", "infill_jerk",
"top_surface_jerk", "initial_layer_jerk", "travel_jerk"}; "top_surface_jerk", "initial_layer_jerk", "travel_jerk"};
const auto max_jerk = std::min(m_config.machine_max_jerk_x.values[0], m_config.machine_max_jerk_y.values[0]); const auto max_jerk = std::min(m_config.machine_max_jerk_x.values[0], m_config.machine_max_jerk_y.values[0]);
warning_key.clear(); warning_key.clear();
if (m_default_object_config.default_jerk > 0) warning_key = check_motion_ability_object_setting(jerk_to_check, max_jerk);
warning_key = check_motion_ability_object_setting(jerk_to_check, max_jerk); if (!warning_key.empty()) {
if (!warning_key.empty()) { warning->string = L(
warning->string = L( "The jerk setting exceeds the printer's maximum jerk (machine_max_jerk_x/machine_max_jerk_y).\n"
"The jerk setting exceeds the printer's maximum jerk (machine_max_jerk_x/machine_max_jerk_y).\nOrca will " "Orca will automatically cap the jerk speed to ensure it doesn't surpass the printer's capabilities.\n"
"automatically cap the jerk speed to ensure it doesn't surpass the printer's capabilities.\nYou can adjust the " "You can adjust the maximum jerk setting in your printer's configuration to get higher speeds.");
"maximum jerk setting in your printer's configuration to get higher speeds."); warning->opt_key = warning_key;
warning->opt_key = warning_key; }
} }
} }
// Check junction deviation // Check junction deviation
const auto max_junction_deviation = m_config.machine_max_junction_deviation.values[0];
// Orca: Only marlin FW supports max junction deviation. Dont display warning if firmware is not supporting it. // Orca: Only marlin FW supports max junction deviation. Dont display warning if firmware is not supporting it.
const bool support_max_junction_deviation = ( m_config.gcode_flavor == gcfMarlinFirmware); const bool support_max_junction_deviation = ( m_config.gcode_flavor == gcfMarlinFirmware);
if (warning_key.empty() && m_default_object_config.default_junction_deviation.value > max_junction_deviation && support_max_junction_deviation) { if (warning_key.empty() && m_default_object_config.default_junction_deviation.value > max_junction_deviation && support_max_junction_deviation) {
warning->string = L( "Junction deviation setting exceeds the printer's maximum value " warning->string = L( "Junction deviation setting exceeds the printer's maximum value (machine_max_junction_deviation).\n"
"(machine_max_junction_deviation).\nOrca will " "Orca will automatically cap the junction deviation to ensure it doesn't surpass the printer's capabilities.\n"
"automatically cap the junction deviation to ensure it doesn't surpass the printer's " "You can adjust the machine_max_junction_deviation value in your printer's configuration to get higher limits.");
"capabilities.\nYou can adjust the "
"machine_max_junction_deviation value in your printer's configuration to get higher limits.");
warning->opt_key = warning_key; warning->opt_key = warning_key;
} }
+22
View File
@@ -3724,6 +3724,28 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced; def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0)); def->set_default_value(new ConfigOptionFloat(0));
// ORCA: minimum non-zero part cooling fan speed.
def = this->add("part_cooling_fan_min_pwm", coInt);
def->label = L("Minimum non-zero part cooling fan speed");
def->tooltip = L("Some part-cooling fans cannot start spinning when commanded below a certain PWM duty cycle. "
"When set above 0, any non-zero part-cooling fan command will be raised to at least this percentage "
"so the fan reliably starts. A fan command of 0 (fan off) is always honoured exactly. "
"This clamp is applied after every other fan calculation (first-layer ramp, layer-time interpolation, "
"overhang/bridge/support-interface/ironing overrides), so scaling still operates within the range "
"[this value, 100%]."
"\nIf your firmware already disables the fan below a threshold (for example Klipper's "
"[fan] off_below: 0.10 shuts the fan off whenever the commanded duty cycle is below 10%), "
"this option and the firmware threshold should ideally be set to the same value. Matching them "
"(e.g. off_below: 0.10 in Klipper and 10% here) guarantees the slicer never emits a non-zero "
"value that the firmware would silently drop, and the fan never receives a value below the one "
"you know it can actually spool at."
"\nSet to 0 to deactivate.");
def->sidetext = L("%");
def->min = 0;
def->max = 100;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionInt(0));
def = this->add("time_cost", coFloat); def = this->add("time_cost", coFloat);
def->label = L("Time cost"); def->label = L("Time cost");
+4
View File
@@ -1309,6 +1309,10 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionFloat, fan_kickstart)) ((ConfigOptionFloat, fan_kickstart))
((ConfigOptionBool, fan_speedup_overhangs)) ((ConfigOptionBool, fan_speedup_overhangs))
((ConfigOptionFloat, fan_speedup_time)) ((ConfigOptionFloat, fan_speedup_time))
// ORCA: minimum PWM (as a percent 0-100) emitted when the part-cooling fan is asked for a non-zero speed.
// Used to overcome the PWM start-up threshold on fans that cannot spool below a certain duty cycle.
// A value of 0 (the default) leaves behaviour unchanged. A fan command of 0 (off) is always honoured.
((ConfigOptionInt, part_cooling_fan_min_pwm))
((ConfigOptionFloats, filament_diameter)) ((ConfigOptionFloats, filament_diameter))
((ConfigOptionBoolsNullable, filament_adaptive_volumetric_speed)) ((ConfigOptionBoolsNullable, filament_adaptive_volumetric_speed))
((ConfigOptionStrings, volumetric_speed_coefficients)) ((ConfigOptionStrings, volumetric_speed_coefficients))
+6
View File
@@ -331,6 +331,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Mouse3DController.hpp GUI/Mouse3DController.hpp
GUI/MsgDialog.cpp GUI/MsgDialog.cpp
GUI/MsgDialog.hpp GUI/MsgDialog.hpp
GUI/MultiChoiceDialog.hpp
GUI/MultiChoiceDialog.cpp
GUI/MultiMachine.cpp GUI/MultiMachine.cpp
GUI/MultiMachine.hpp GUI/MultiMachine.hpp
GUI/MultiMachineManagerPage.cpp GUI/MultiMachineManagerPage.cpp
@@ -498,6 +500,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Widgets/Button.hpp GUI/Widgets/Button.hpp
GUI/Widgets/CheckBox.cpp GUI/Widgets/CheckBox.cpp
GUI/Widgets/CheckBox.hpp GUI/Widgets/CheckBox.hpp
GUI/Widgets/CheckList.cpp
GUI/Widgets/CheckList.hpp
GUI/Widgets/ComboBox.cpp GUI/Widgets/ComboBox.cpp
GUI/Widgets/ComboBox.hpp GUI/Widgets/ComboBox.hpp
GUI/Widgets/DialogButtons.cpp GUI/Widgets/DialogButtons.cpp
@@ -693,6 +697,8 @@ if (APPLE)
GUI/Mouse3DHandlerMac.mm GUI/Mouse3DHandlerMac.mm
GUI/InstanceCheckMac.mm GUI/InstanceCheckMac.mm
GUI/InstanceCheckMac.h GUI/InstanceCheckMac.h
GUI/DeepLinkHandlerMac.mm
GUI/DeepLinkHandlerMac.h
GUI/GUI_UtilsMac.mm GUI/GUI_UtilsMac.mm
GUI/wxMediaCtrl2.mm GUI/wxMediaCtrl2.mm
GUI/wxMediaCtrl2.h GUI/wxMediaCtrl2.h
+16
View File
@@ -0,0 +1,16 @@
#ifndef slic3r_GUI_DeepLinkHandlerMac_h_
#define slic3r_GUI_DeepLinkHandlerMac_h_
namespace Slic3r {
namespace GUI {
// Re-registers a Cocoa Apple Event handler for kInternetEventClass/kAEGetURL.
// Works around a regression observed after upgrading to wxWidgets 3.3.2 on
// macOS Tahoe (#13119) where wxWidgets' built-in handler is registered but
// never fires for orcaslicer:// deep links.
void register_mac_deep_link_handler();
} // namespace GUI
} // namespace Slic3r
#endif
+41
View File
@@ -0,0 +1,41 @@
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
#include <boost/log/trivial.hpp>
#include "DeepLinkHandlerMac.h"
#include "GUI_App.hpp"
@interface OrcaDeepLinkHandler : NSObject
- (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)reply;
@end
@implementation OrcaDeepLinkHandler
- (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)reply
{
NSString *url = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
if (url == nil || url.length == 0)
return;
BOOST_LOG_TRIVIAL(info) << "Deep link received: " << [url UTF8String];
Slic3r::GUI::wxGetApp().MacOpenURL(wxString::FromUTF8([url UTF8String]));
}
@end
namespace Slic3r {
namespace GUI {
void register_mac_deep_link_handler()
{
static OrcaDeepLinkHandler *handler = nil;
if (handler == nil)
handler = [[OrcaDeepLinkHandler alloc] init];
[[NSAppleEventManager sharedAppleEventManager]
setEventHandler:handler
andSelector:@selector(handleGetURLEvent:withReplyEvent:)
forEventClass:kInternetEventClass
andEventID:kAEGetURL];
}
} // namespace GUI
} // namespace Slic3r
+10 -2
View File
@@ -324,9 +324,18 @@ wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelR
else else
c_editor->SetSelection(atoi(data.GetText().c_str()) - 1); c_editor->SetSelection(atoi(data.GetText().c_str()) - 1);
// Open the dropdown immediately when the editor is focused.
c_editor->Bind(wxEVT_SET_FOCUS, [c_editor](wxFocusEvent& evt) { c_editor->Bind(wxEVT_SET_FOCUS, [c_editor](wxFocusEvent& evt) {
#ifdef __WXGTK__
// On wxGTK the data-view editor may receive focus before its native
// window is mapped. Opening the popup one event later avoids creating
// the GTK popup without a valid toplevel parent.
c_editor->CallAfter([c_editor]() {
if (c_editor->IsShownOnScreen())
c_editor->ForceDropdownOpen();
});
#else
c_editor->ForceDropdownOpen(); c_editor->ForceDropdownOpen();
#endif
evt.Skip(); evt.Skip();
}); });
@@ -392,4 +401,3 @@ wxSize TextRenderer::GetSize() const
return GetTextExtent(m_value); return GetTextExtent(m_value);
} }
+3 -1
View File
@@ -663,7 +663,9 @@ void GLTexture::render_texture(unsigned int tex_id, float left, float right, flo
void GLTexture::render_sub_texture(unsigned int tex_id, float left, float right, float bottom, float top, const GLTexture::Quad_UVs& uvs) void GLTexture::render_sub_texture(unsigned int tex_id, float left, float right, float bottom, float top, const GLTexture::Quad_UVs& uvs)
{ {
glsafe(::glEnable(GL_BLEND)); glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); // Orca: fix washed-out toolbar icons on Wayland: keep destination alpha at 1.0 so the compositor
// does not treat anti-aliased icon edges as window transparency.
glsafe(::glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA));
glsafe(::glEnable(GL_TEXTURE_2D)); glsafe(::glEnable(GL_TEXTURE_2D));
glsafe(::glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE)); glsafe(::glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE));
+76 -19
View File
@@ -11,6 +11,7 @@
#include "Downloader.hpp" #include "Downloader.hpp"
#include <boost/chrono/duration.hpp> #include <boost/chrono/duration.hpp>
#include <boost/log/detail/native_typeof.hpp> #include <boost/log/detail/native_typeof.hpp>
#include <libslic3r/Config.hpp>
#include <wx/event.h> #include <wx/event.h>
// Localization headers: include libslic3r version first so everything in this file // Localization headers: include libslic3r version first so everything in this file
@@ -100,6 +101,9 @@
#include "Mouse3DController.hpp" #include "Mouse3DController.hpp"
#include "RemovableDriveManager.hpp" #include "RemovableDriveManager.hpp"
#include "InstanceCheck.hpp" #include "InstanceCheck.hpp"
#ifdef __APPLE__
#include "DeepLinkHandlerMac.h"
#endif
#include "NotificationManager.hpp" #include "NotificationManager.hpp"
#include "UnsavedChangesDialog.hpp" #include "UnsavedChangesDialog.hpp"
#include "SavePresetDialog.hpp" #include "SavePresetDialog.hpp"
@@ -783,9 +787,20 @@ void GUI_App::post_init()
mainframe->select_tab(size_t(MainFrame::tp3DEditor)); mainframe->select_tab(size_t(MainFrame::tp3DEditor));
plater_->select_view_3D("3D"); plater_->select_view_3D("3D");
//BBS init the opengl resource here //BBS init the opengl resource here
//#ifdef __linux__ if (!plater_->canvas3D()->get_wxglcanvas()->IsShownOnScreen() ||
if (plater_->canvas3D()->get_wxglcanvas()->IsShownOnScreen()&&plater_->canvas3D()->make_current_for_postinit()) { !plater_->canvas3D()->make_current_for_postinit()) {
//#endif BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": glcontext not ready, postpone init";
plater_->canvas3D()->enable_render(true);
plater_->canvas3D()->set_as_dirty();
#ifdef __linux__
// Wayland/EGL may not have committed the GL surface yet; ask the
// idle loop to retry post_init when the canvas is actually mapped.
// Without this, GL function pointers stay null and the first
// Preview focus crashes in Camera::apply_viewport.
m_post_initialized = false;
return;
#endif
} else {
Size canvas_size = plater_->canvas3D()->get_canvas_size(); Size canvas_size = plater_->canvas3D()->get_canvas_size();
wxGetApp().imgui()->set_display_size(static_cast<float>(canvas_size.get_width()), static_cast<float>(canvas_size.get_height())); wxGetApp().imgui()->set_display_size(static_cast<float>(canvas_size.get_width()), static_cast<float>(canvas_size.get_height()));
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", start to init opengl"; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", start to init opengl";
@@ -805,14 +820,7 @@ void GUI_App::post_init()
plater_->canvas3D()->render(false); plater_->canvas3D()->render(false);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished rendering a first frame for test"; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished rendering a first frame for test";
} }
//#ifdef __linux__
} }
else {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << "Found glcontext not ready, postpone the init";
plater_->canvas3D()->enable_render(true);
plater_->canvas3D()->set_as_dirty();
}
//#endif
if (is_editor()) if (is_editor())
mainframe->select_tab(size_t(0)); mainframe->select_tab(size_t(0));
if (app_config->get("default_page") == "1") if (app_config->get("default_page") == "1")
@@ -1900,6 +1908,7 @@ void GUI_App::init_networking_callbacks()
} }
if (return_code == 5) { if (return_code == 5) {
GUI::wxGetApp().CallAfter([this, provider = event.provider] { GUI::wxGetApp().CallAfter([this, provider = event.provider] {
BOOST_LOG_TRIVIAL(info) << "logout: login expired";
this->request_user_logout(provider); this->request_user_logout(provider);
MessageDialog msg_dlg(nullptr, _L("Login information expired. Please login again."), "", wxAPPLY | wxOK); MessageDialog msg_dlg(nullptr, _L("Login information expired. Please login again."), "", wxAPPLY | wxOK);
if (msg_dlg.ShowModal() == wxOK) { if (msg_dlg.ShowModal() == wxOK) {
@@ -2546,6 +2555,12 @@ std::string get_system_info()
bool GUI_App::on_init_inner() bool GUI_App::on_init_inner()
{ {
wxLog::SetActiveTarget(new wxBoostLog()); wxLog::SetActiveTarget(new wxBoostLog());
#ifdef __APPLE__
// Override wxWidgets' kAEGetURL handler so orcaslicer:// deep links keep
// working after the wxWidgets 3.3.2 upgrade on macOS (#13119).
register_mac_deep_link_handler();
#endif
#if BBL_RELEASE_TO_PUBLIC #if BBL_RELEASE_TO_PUBLIC
wxLog::SetLogLevel(wxLOG_Message); wxLog::SetLogLevel(wxLOG_Message);
#endif #endif
@@ -4526,7 +4541,8 @@ std::string GUI_App::handle_web_request(std::string cmd)
} }
else if (command_str.compare("homepage_logout") == 0) { else if (command_str.compare("homepage_logout") == 0) {
CallAfter([this] { CallAfter([this] {
wxGetApp().request_user_logout(); BOOST_LOG_TRIVIAL(info) << "logout: homepage_logout";
request_user_logout();
}); });
} }
else if (command_str.compare("get_orca_login_info") == 0) { else if (command_str.compare("get_orca_login_info") == 0) {
@@ -4539,18 +4555,22 @@ std::string GUI_App::handle_web_request(std::string cmd)
CallAfter([this] { request_login(true, BBL_CLOUD_PROVIDER); }); CallAfter([this] { request_login(true, BBL_CLOUD_PROVIDER); });
} }
else if (command_str.compare("homepage_bambu_logout") == 0) { else if (command_str.compare("homepage_bambu_logout") == 0) {
CallAfter([this] { request_user_logout(BBL_CLOUD_PROVIDER); }); CallAfter([this] {
BOOST_LOG_TRIVIAL(info) << "logout: homepage_bambu_logout";
request_user_logout(BBL_CLOUD_PROVIDER);
});
} }
else if (command_str.compare("homepage_orca_login_or_register") == 0) { else if (command_str.compare("homepage_orca_login_or_register") == 0) {
CallAfter([this] { request_login(true, ORCA_CLOUD_PROVIDER); }); CallAfter([this] { request_login(true, ORCA_CLOUD_PROVIDER); });
} }
else if (command_str.compare("homepage_orca_logout") == 0) { else if (command_str.compare("homepage_orca_logout") == 0) {
CallAfter([this] { request_user_logout(ORCA_CLOUD_PROVIDER); }); CallAfter([this] {
BOOST_LOG_TRIVIAL(info) << "logout: homepage_orca_logout";
request_user_logout(ORCA_CLOUD_PROVIDER);
});
} }
else if (command_str.compare("homepage_modeldepot") == 0) { else if (command_str.compare("homepage_modeldepot") == 0) {
CallAfter([this] { CallAfter([this] { open_mall_page_dialog(); });
wxGetApp().open_mall_page_dialog();
});
} }
else if (command_str.compare("homepage_newproject") == 0) { else if (command_str.compare("homepage_newproject") == 0) {
this->request_open_project("<new>"); this->request_open_project("<new>");
@@ -4826,14 +4846,15 @@ void GUI_App::on_http_error(wxCommandEvent &evt)
if (status == 401) { if (status == 401) {
if (m_agent) { if (m_agent) {
if (m_agent->is_user_login(provider)) { if (m_agent->is_user_login(provider)) {
BOOST_LOG_TRIVIAL(warning) << "logout: http error 401.";
this->request_user_logout(provider); this->request_user_logout(provider);
if (!m_show_http_errpr_msgdlg) { if (!m_show_http_error_msgdlg) {
MessageDialog msg_dlg(nullptr, _L("Login information expired. Please login again."), "", wxAPPLY | wxOK); MessageDialog msg_dlg(nullptr, _L("Login information expired. Please login again."), "", wxAPPLY | wxOK);
m_show_http_errpr_msgdlg = true; m_show_http_error_msgdlg = true;
auto modal_result = msg_dlg.ShowModal(); auto modal_result = msg_dlg.ShowModal();
if (modal_result == wxOK || modal_result == wxCLOSE) { if (modal_result == wxOK || modal_result == wxCLOSE) {
m_show_http_errpr_msgdlg = false; m_show_http_error_msgdlg = false;
return; return;
} }
} }
@@ -4841,6 +4862,40 @@ void GUI_App::on_http_error(wxCommandEvent &evt)
} }
return; return;
} }
// No need to show dialog for 410: 410 means resource has been deleted from the server.
if (status == 410) {
BOOST_LOG_TRIVIAL(info) << "Http error 410.";
return;
}
static bool m_is_error_shown = false;
// Show general error notification for Orca Cloud API failures (not Bambu)
if (provider == ORCA_CLOUD_PROVIDER && status >= 400 && code != HttpErrorVersionLimited) {
wxString msg;
if (!error.empty()) {
msg = wxString::Format(_L("API error (HTTP %u): %s"), status, wxString::FromUTF8(error));
} else {
msg = wxString::Format(_L("API error (HTTP %u)"), status);
}
if (app_config->get_bool("developer_mode")) {
// Use notification manager if ImGui is ready; fall back to wxMessageBox on Linux
// where ImGui may not be initialized until the user switches to the Prepare tab.
if (wxGetApp().plater() != nullptr && wxGetApp().imgui()->display_initialized()) {
wxGetApp()
.plater()
->get_notification_manager()
->push_notification(NotificationType::PlaterError, NotificationManager::NotificationLevel::WarningNotificationLevel,
msg.ToUTF8().data());
}
}
if (!m_is_error_shown) {
m_is_error_shown = true;
wxMessageBox(msg, _L("Orca Cloud API Error"), wxOK | wxICON_ERROR, wxGetApp().mainframe);
}
}
} }
void GUI_App::enable_user_preset_folder(bool enable) void GUI_App::enable_user_preset_folder(bool enable)
@@ -5599,6 +5654,7 @@ void GUI_App::show_check_privacy_dlg(wxCommandEvent& evt)
privacy_dlg.Bind(EVT_PRIVACY_UPDATE_CANCEL, [this, provider](wxCommandEvent &e) { privacy_dlg.Bind(EVT_PRIVACY_UPDATE_CANCEL, [this, provider](wxCommandEvent &e) {
app_config->set_bool("privacy_update_checked", false); app_config->set_bool("privacy_update_checked", false);
if (m_agent) { if (m_agent) {
BOOST_LOG_TRIVIAL(info) << "logout: Privacy update dialog cancelled.";
m_agent->user_logout(false, provider); m_agent->user_logout(false, provider);
post_logout_to_webview(provider); post_logout_to_webview(provider);
} }
@@ -6766,6 +6822,7 @@ void GUI_App::stop_sync_user_preset()
void GUI_App::on_stealth_mode_enter() void GUI_App::on_stealth_mode_enter()
{ {
stop_sync_user_preset(); stop_sync_user_preset();
BOOST_LOG_TRIVIAL(info) << "logout: on_stealth_mode_enter";
request_user_logout(ORCA_CLOUD_PROVIDER); request_user_logout(ORCA_CLOUD_PROVIDER);
request_user_logout(BBL_CLOUD_PROVIDER); request_user_logout(BBL_CLOUD_PROVIDER);
if (mainframe && mainframe->m_webview) { if (mainframe && mainframe->m_webview) {
+1 -1
View File
@@ -324,7 +324,7 @@ private:
bool m_is_dark_mode{ false }; bool m_is_dark_mode{ false };
bool m_adding_script_handler { false }; bool m_adding_script_handler { false };
bool m_side_popup_status{false}; bool m_side_popup_status{false};
bool m_show_http_errpr_msgdlg{false}; bool m_show_http_error_msgdlg{false};
bool m_show_error_msgdlg{false}; bool m_show_error_msgdlg{false};
wxString m_info_dialog_content; wxString m_info_dialog_content;
HttpServer m_http_server; HttpServer m_http_server;
+14 -5
View File
@@ -561,13 +561,22 @@ wxString GridCellSupportEditor::ms_stringValues[2] = { wxT(""), wxT("") };
void GridCellSupportEditor::DoActivate(int row, int col, wxGrid* grid) void GridCellSupportEditor::DoActivate(int row, int col, wxGrid* grid)
{ {
ObjectGrid* local_table = dynamic_cast<ObjectGrid*>(grid);
wxGridBlocks cell_array = grid->GetSelectedBlocks(); wxGridBlocks cell_array = grid->GetSelectedBlocks();
auto iter = cell_array.begin();
auto left_col = cell_array.begin()->GetLeftCol(); int left_col, right_col, top_row, bottom_row;
auto right_col = cell_array.begin()->GetRightCol(); if (iter == cell_array.end()) {
auto top_row = cell_array.begin()->GetTopRow(); // wxWidgets 3.3.x returns an empty range when nothing is selected;
auto bottom_row = cell_array.begin()->GetBottomRow(); // fall back to the cell that triggered activation so the single-cell
// branch below handles it.
left_col = right_col = col;
top_row = bottom_row = row;
} else {
left_col = iter->GetLeftCol();
right_col = iter->GetRightCol();
top_row = iter->GetTopRow();
bottom_row = iter->GetBottomRow();
}
if ((left_col == right_col) && if ((left_col == right_col) &&
(top_row == bottom_row)) { (top_row == bottom_row)) {
+2 -1
View File
@@ -373,12 +373,13 @@ public:
//BBS //BBS
static int TOOLBAR_WINDOW_FLAGS; static int TOOLBAR_WINDOW_FLAGS;
bool display_initialized() const;
private: private:
void init_font(bool compress); void init_font(bool compress);
void init_input(); void init_input();
void init_style(); void init_style();
void render_draw_data(ImDrawData *draw_data); void render_draw_data(ImDrawData *draw_data);
bool display_initialized() const;
void destroy_font(); void destroy_font();
std::vector<unsigned char> load_svg(const std::string& bitmap_name, unsigned target_width, unsigned target_height, unsigned *outwidth, unsigned *outheight); std::vector<unsigned char> load_svg(const std::string& bitmap_name, unsigned target_width, unsigned target_height, unsigned *outwidth, unsigned *outheight);
+58
View File
@@ -0,0 +1,58 @@
#include "MultiChoiceDialog.hpp"
#include "GUI_App.hpp"
#include "MainFrame.hpp"
namespace Slic3r { namespace GUI {
MultiChoiceDialog::MultiChoiceDialog(
wxWindow* parent,
const wxString& message,
const wxString& caption,
const wxArrayString& choices
)
: DPIDialog(parent ? parent : static_cast<wxWindow *>(wxGetApp().mainframe), wxID_ANY, caption, wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX)
{
SetBackgroundColour(*wxWHITE);
wxBoxSizer* w_sizer = new wxBoxSizer(wxVERTICAL);
if(!message.IsEmpty()){
wxStaticText *msg = new wxStaticText(this, wxID_ANY, message);
msg->SetFont(Label::Body_13);
msg->Wrap(-1);
w_sizer->Add(msg, 0, wxRIGHT | wxLEFT | wxTOP, FromDIP(10));
}
m_check_list = new CheckList(this, choices);
w_sizer->Add(m_check_list, 1, wxRIGHT | wxLEFT | wxTOP | wxEXPAND, FromDIP(10));
auto dlg_btns = new DialogButtons(this, {"OK", "Cancel"});
dlg_btns->GetOK()->Bind( wxEVT_BUTTON, [this](wxCommandEvent &e) {EndModal(wxID_OK);});
dlg_btns->GetCANCEL()->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) {EndModal(wxID_CANCEL);});
w_sizer->Add(dlg_btns, 0, wxEXPAND);
SetSizer(w_sizer);
Layout();
w_sizer->Fit(this);
wxGetApp().UpdateDlgDarkUI(this);
}
wxArrayInt MultiChoiceDialog::GetSelections() const
{
return m_check_list->GetSelections();
}
void MultiChoiceDialog::SetSelections(wxArrayInt sel_array)
{
m_check_list->SetSelections(sel_array);
}
MultiChoiceDialog::~MultiChoiceDialog() {}
void MultiChoiceDialog::on_dpi_changed(const wxRect &suggested_rect) {}
}} // namespace Slic3r::GUI
+37
View File
@@ -0,0 +1,37 @@
#ifndef slic3r_GUI_MultiChoiceDialog_hpp_
#define slic3r_GUI_MultiChoiceDialog_hpp_
#include "Widgets/CheckList.hpp"
#include "Widgets/DialogButtons.hpp"
#include <wx/wx.h>
#include <vector>
#include <map>
namespace Slic3r { namespace GUI {
class MultiChoiceDialog : public DPIDialog
{
public:
MultiChoiceDialog(
wxWindow* parent = nullptr,
const wxString& message = wxEmptyString,
const wxString& caption = wxEmptyString,
const wxArrayString& choices = wxArrayString()
);
~MultiChoiceDialog();
wxArrayInt GetSelections() const;
void SetSelections(wxArrayInt sel_array);
protected:
CheckList* m_check_list;
wxArrayInt m_selected_indices;
void on_dpi_changed(const wxRect &suggested_rect) override;
};
}} // namespace Slic3r::GUI
#endif
+81
View File
@@ -23,6 +23,81 @@
namespace Slic3r { namespace Slic3r {
namespace GUI { namespace GUI {
#ifdef __linux__
// Workaround for #7210: WebKitGTK crashes on vue-resize's hidden <object> probe used by
// older Fluidd/Mainsail pages. Swap that <object> for a <div> shim at appendChild time
// and bridge resize events through a fake contentDocument.defaultView so vue-resize keeps
// working. Workaround proposed by @VittC.
static void inject_vue_resize_workaround(wxWebView *webView)
{
webView->AddUserScript(
"(function() {"
" 'use strict';"
" function isVueResizeObject(el) {"
" return el && el.tagName === 'OBJECT'"
" && el.type === 'text/html'"
" && el.getAttribute('aria-hidden') === 'true'"
" && el.getAttribute('tabindex') === '-1';"
" }"
" function isResizeObserverParent(p) {"
" return p && p.classList && p.classList.contains('resize-observer');"
" }"
" function makeShim(orig, parentForRO) {"
" var shim = document.createElement('div');"
" shim.setAttribute('aria-hidden', 'true');"
" shim.setAttribute('tabindex', '-1');"
" shim.style.display = 'none';"
" var fakeWin = document.createElement('div');"
" var ro = null;"
" var origRemoveEL = fakeWin.removeEventListener.bind(fakeWin);"
" fakeWin.removeEventListener = function(type, fn, opts) {"
" origRemoveEL(type, fn, opts);"
" if (type === 'resize' && ro) { ro.disconnect(); ro = null; }"
" };"
" Object.defineProperty(shim, 'contentDocument', {"
" configurable: true,"
" get: function() { return { defaultView: fakeWin }; }"
" });"
" Object.defineProperty(shim, 'contentWindow', {"
" configurable: true,"
" get: function() { return fakeWin; }"
" });"
" if (typeof orig.onload === 'function') { shim.onload = orig.onload; }"
" queueMicrotask(function() {"
" if (parentForRO && typeof ResizeObserver !== 'undefined') {"
" ro = new ResizeObserver(function() {"
" fakeWin.dispatchEvent(new Event('resize'));"
" });"
" ro.observe(parentForRO);"
" }"
" if (typeof shim.onload === 'function') {"
" try { shim.onload(new Event('load')); } catch (e) {}"
" }"
" shim.dispatchEvent(new Event('load'));"
" });"
" return shim;"
" }"
" var origAppend = Node.prototype.appendChild;"
" Node.prototype.appendChild = function(child) {"
" if (isResizeObserverParent(this) && isVueResizeObject(child)) {"
" return origAppend.call(this, makeShim(child, this));"
" }"
" return origAppend.call(this, child);"
" };"
" var origInsertBefore = Node.prototype.insertBefore;"
" Node.prototype.insertBefore = function(child, ref) {"
" if (isResizeObserverParent(this) && isVueResizeObject(child)) {"
" return origInsertBefore.call(this, makeShim(child, this), ref);"
" }"
" return origInsertBefore.call(this, child, ref);"
" };"
" console.log('[vr-fix] vue-resize WebKitGTK patch active');"
"})();",
wxWEBVIEW_INJECT_AT_DOCUMENT_START
);
}
#endif
PrinterWebView::PrinterWebView(wxWindow *parent) PrinterWebView::PrinterWebView(wxWindow *parent)
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize) : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
, m_browser(nullptr) , m_browser(nullptr)
@@ -43,6 +118,8 @@ PrinterWebView::PrinterWebView(wxWindow *parent)
} }
#ifdef __linux__ #ifdef __linux__
inject_vue_resize_workaround(m_browser);
auto cookiesPath = boost::filesystem::path(data_dir() + "/cache/cookies.db"); auto cookiesPath = boost::filesystem::path(data_dir() + "/cache/cookies.db");
auto wv = static_cast<WebKitWebView*>(m_browser->GetNativeBackend()); auto wv = static_cast<WebKitWebView*>(m_browser->GetNativeBackend());
auto wv_ctx = webkit_web_view_get_context(wv); auto wv_ctx = webkit_web_view_get_context(wv);
@@ -165,6 +242,10 @@ void PrinterWebView::SendAPIKey()
)", )",
m_apikey); m_apikey);
m_browser->RemoveAllUserScripts(); m_browser->RemoveAllUserScripts();
#ifdef __linux__
// Re-inject the vue-resize/WebKitGTK workaround that RemoveAllUserScripts just cleared.
inject_vue_resize_workaround(m_browser);
#endif
m_browser->AddUserScript(script); m_browser->AddUserScript(script);
m_browser->Reload(); m_browser->Reload();
+17 -4
View File
@@ -40,7 +40,7 @@
#include "UnsavedChangesDialog.hpp" #include "UnsavedChangesDialog.hpp"
#include "SavePresetDialog.hpp" #include "SavePresetDialog.hpp"
#include "EditGCodeDialog.hpp" #include "EditGCodeDialog.hpp"
#include "MultiChoiceDialog.hpp"
#include "MsgDialog.hpp" #include "MsgDialog.hpp"
#include "Notebook.hpp" #include "Notebook.hpp"
@@ -4500,6 +4500,8 @@ void TabPrinter::build_fff()
line.append_option(optgroup->get_option("fan_speedup_overhangs")); line.append_option(optgroup->get_option("fan_speedup_overhangs"));
optgroup->append_line(line); optgroup->append_line(line);
optgroup->append_single_option_line("fan_kickstart", "printer_basic_information_cooling_fan#fan-kick-start-time"); optgroup->append_single_option_line("fan_kickstart", "printer_basic_information_cooling_fan#fan-kick-start-time");
// ORCA: PWM floor for fans that won't spool at low duty cycles.
optgroup->append_single_option_line("part_cooling_fan_min_pwm", "printer_basic_information_cooling_fan#minimum-non-zero-part-cooling-fan-speed");
optgroup = page->new_optgroup(L("Extruder Clearance"), "param_extruder_clearance"); optgroup = page->new_optgroup(L("Extruder Clearance"), "param_extruder_clearance");
optgroup->append_single_option_line("extruder_clearance_radius", "printer_basic_information_extruder_clearance#radius"); optgroup->append_single_option_line("extruder_clearance_radius", "printer_basic_information_extruder_clearance#radius");
@@ -7002,7 +7004,15 @@ wxSizer* Tab::compatible_widget_create(wxWindow* parent, PresetDependencies &dep
presets.Add(from_u8(preset.name)); presets.Add(from_u8(preset.name));
} }
wxMultiChoiceDialog dlg(parent, deps.dialog_title, deps.dialog_label, presets); if(deps.type == Preset::TYPE_PRINTER){
deps.dialog_title = "Compatible printers";
deps.dialog_label = "Select printers";
}else{
deps.dialog_title = "Compatible process profiles";
deps.dialog_label = "Select profiles";
}
MultiChoiceDialog dlg(parent, deps.dialog_label, deps.dialog_title, presets);
wxGetApp().UpdateDlgDarkUI(&dlg); wxGetApp().UpdateDlgDarkUI(&dlg);
// Collect and set indices of depending_presets marked as compatible. // Collect and set indices of depending_presets marked as compatible.
wxArrayInt selections; wxArrayInt selections;
@@ -7015,13 +7025,16 @@ wxSizer* Tab::compatible_widget_create(wxWindow* parent, PresetDependencies &dep
break; break;
} }
dlg.SetSelections(selections); dlg.SetSelections(selections);
dlg.SetSize(FromDIP(wxSize(360, 480)));
std::vector<std::string> value; std::vector<std::string> value;
// Show the dialog. // Show the dialog.
if (dlg.ShowModal() == wxID_OK) { if (dlg.ShowModal() == wxID_OK) {
selections.Clear(); selections.Clear();
selections = dlg.GetSelections(); selections = dlg.GetSelections();
for (auto idx : selections) // leave list empty if all items checked. this will check "All" checkbox automatically. also fixes unnecessary config change
value.push_back(presets[idx].ToUTF8().data()); if(selections.GetCount() != presets.GetCount())
for (auto idx : selections)
value.push_back(presets[idx].ToUTF8().data());
if (value.empty()) { if (value.empty()) {
deps.checkbox->SetValue(1); deps.checkbox->SetValue(1);
deps.btn->Disable(); deps.btn->Disable();
+4 -1
View File
@@ -4,6 +4,7 @@
#include <boost/filesystem/operations.hpp> #include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp> #include <boost/filesystem/path.hpp>
#include <boost/iostreams/detail/select.hpp> #include <boost/iostreams/detail/select.hpp>
#include <boost/log/trivial.hpp>
#include <string.h> #include <string.h>
#include "I18N.hpp" #include "I18N.hpp"
#include "libslic3r/AppConfig.hpp" #include "libslic3r/AppConfig.hpp"
@@ -516,7 +517,9 @@ void GuideFrame::OnScriptMessage(wxWebViewEvent &evt)
if (agent) { if (agent) {
agent->set_country_code(country_code); agent->set_country_code(country_code);
if (wxGetApp().is_user_login()) { if (wxGetApp().is_user_login()) {
agent->user_logout(); BOOST_LOG_TRIVIAL(info) << "logout: user_logout on user_guide_finish";
// agent->user_logout();
wxGetApp().request_user_logout();
} }
} }
} }
+225
View File
@@ -0,0 +1,225 @@
#include "CheckList.hpp"
#include "slic3r/GUI/GUI_App.hpp"
CheckList::CheckList(
wxWindow* parent,
const wxArrayString& choices,
long scroll_style
)
: wxWindow(parent, wxID_ANY)
, m_search(this, "search", 16)
, m_menu(this, "filter", 16)
, m_first_load(true)
{
Freeze();
w_sizer = new wxBoxSizer(wxVERTICAL);
f_sizer = new wxBoxSizer(wxHORIZONTAL);
f_bar = new wxPanel(this, wxID_ANY);
f_bar->SetBackgroundColour(parent->GetBackgroundColour());
m_filter_box = new TextInput(f_bar, "", "", "", wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER);
m_filter_box->SetIcon(m_search.bmp());
m_filter_box->SetMinSize(FromDIP(wxSize(200,24)));
m_filter_box->SetSize(FromDIP(wxSize(-1,24)));
m_filter_box->SetFocus();
m_filter_ctrl = m_filter_box->GetTextCtrl();
m_filter_ctrl->SetFont(Label::Body_13);
m_filter_ctrl->SetSize(wxSize(-1, FromDIP(16))); // Centers text vertically
m_filter_ctrl->SetHint(_L("Type to filter..."));
m_filter_ctrl->Bind(wxEVT_TEXT, [this](auto &e) {Filter(m_filter_ctrl->GetValue());});
m_filter_ctrl->Bind(wxEVT_TEXT_ENTER, [this](auto &e) {Filter(m_filter_ctrl->GetValue());});
m_filter_ctrl->Bind(wxEVT_SET_FOCUS, [this](auto &e) {Filter(m_filter_ctrl->GetValue());e.Skip();});
m_filter_ctrl->Bind(wxEVT_KILL_FOCUS, [this](auto &e) {Filter(m_filter_ctrl->GetValue());e.Skip();});
f_sizer->Add(m_filter_box, 1, wxEXPAND);
Bind(wxEVT_SET_FOCUS, [this](auto &e) {m_filter_box->SetFocus();});
fb_sizer = new wxBoxSizer(wxHORIZONTAL);
auto create_btn = [this] (wxString title, bool select){
auto btn = new wxStaticText(f_bar, wxID_ANY, title);
btn->SetForegroundColour("#009687");
btn->SetCursor(wxCURSOR_HAND);
btn->SetFont(Label::Body_13);
btn->Bind(wxEVT_LEFT_DOWN, [this, select](wxMouseEvent &e) {SelectAll(select);});
fb_sizer->Add(btn, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(10));
};
f_sizer->Add(fb_sizer,0 ,wxALIGN_CENTER_VERTICAL);
create_btn(_L("All") , true);
create_btn(_L("None"), false);
m_menu_button = new wxStaticBitmap(f_bar, wxID_ANY, m_menu.bmp());
m_menu_button->SetCursor(wxCURSOR_HAND);
m_menu_button->Bind(wxEVT_LEFT_DOWN, &CheckList::ShowMenu, this);
f_sizer->Add(m_menu_button,0 ,wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(10));
f_bar->SetSizerAndFit(f_sizer);
w_sizer->Add(f_bar, 0, wxEXPAND);
auto spacer = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, FromDIP(3)));
spacer->SetBackgroundColour(parent->GetBackgroundColour());
w_sizer->Add(spacer, 0, wxEXPAND);
s_sizer = new wxBoxSizer(wxVERTICAL);
m_scroll_area = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, scroll_style);
m_scroll_area->SetScrollRate(0, 10);
m_scroll_area->SetSizer(s_sizer);
m_scroll_area->SetBackgroundColour(parent->GetBackgroundColour());
m_scroll_area->Bind(wxEVT_RIGHT_DOWN, &CheckList::ShowMenu, this);
m_scroll_area->DisableFocusFromKeyboard();
m_info = new wxStaticText(m_scroll_area, wxID_ANY, "");
m_info->SetFont(Label::Body_13);
s_sizer->Add(m_info, 1, wxALIGN_CENTER_HORIZONTAL | wxALL, FromDIP(10));
m_info->Hide();
m_info_nonsel = _L("No selected items...");
m_info_allsel = _L("All items selected...");
m_info_empty = _L("No matching items...");
SetBackgroundColour(StateColor::darkModeColorFor("#DBDBDB")); // draws border on wxScrolledWindow
w_sizer->Add(m_scroll_area, 1, wxEXPAND | wxALL, FromDIP(1)); // 1 for border
s_sizer->Layout();
m_list_size = choices.size();
m_checks.reserve(m_list_size);
auto margin = FromDIP(2);
wxCheckBox* cb;
for (size_t i = 0; i < m_list_size; ++i){
cb = new wxCheckBox(m_scroll_area, wxID_ANY, choices[i]);
m_checks.emplace_back(cb);
s_sizer->Add(cb, 0, wxALL, margin);
}
m_scroll_area->FitInside();
s_sizer->Layout();
SetSizer(w_sizer);
Layout();
Thaw();
}
void CheckList::SetSelections(wxArrayInt sel_array){
if(!m_first_load){
for (size_t i = 0; i < m_list_size; ++i)
Check(i, false);
m_first_load = false;
}
for (int i : sel_array)
Check(i, true);
}
wxArrayInt CheckList::GetSelections()
{
wxArrayInt checks;
for (size_t i = 0; i < m_list_size; ++i)
if (m_checks[i]->GetValue())
checks.push_back(i);
return checks;
}
void CheckList::Check(int i, bool checked)
{
if (i > -1 && i < m_list_size)
m_checks[i]->SetValue(checked);
}
void CheckList::SelectAll(bool value)
{
for (size_t i = 0; i < m_list_size; ++i)
Check(i, value);
}
void CheckList::SelectVisible(bool value)
{
auto filter = m_filter_ctrl->GetValue().Lower();
if((!value && filter == "::unsel") || (value && filter == "::sel"))
m_filter_ctrl->SetValue("");
for (size_t i = 0; i < m_list_size; ++i)
if(m_checks[i]->IsShown())
Check(i, value);
}
bool CheckList::IsChecked(int i)
{
if (i > -1 && i < m_list_size)
return false;
return m_checks[i]->GetValue();
}
void CheckList::Filter(const wxString& filterText)
{
Freeze();
auto filter = filterText.Lower();
auto c_text = m_filter_ctrl->GetValue().Lower();
if(filter == "::sel" || filter == "::nonsel"){
if(c_text != filter){ // not text input
m_filter_ctrl->SetValue(filter);
m_filter_ctrl->SetSelection(0,-1);
}
fb_sizer->Show(false);
if (filter == "::sel")
for (auto& cb : m_checks) cb->Show(cb->GetValue());
else
for (auto& cb : m_checks) cb->Show(!cb->GetValue());
}
else{
bool clear = filterText.IsEmpty();
fb_sizer->Show(clear);
for (auto& cb : m_checks)
cb->Show(clear || cb->GetLabel().Lower().Contains(filter));
}
m_info->Show();
for (size_t i = 0; i < m_list_size; ++i) {
if (m_checks[i]->IsShown()){
m_info->Hide();
break;
}
}
if (m_info->IsShown())
m_info->SetLabel(filter == "::sel" ? m_info_nonsel : filter == "::nonsel" ? m_info_allsel : m_info_empty);
m_scroll_area->FitInside();
f_sizer->Layout();
Thaw();
}
void CheckList::ShowMenu(wxMouseEvent &evt)
{
bool filtering = !m_filter_ctrl->GetValue().IsEmpty();
bool list_empty = m_info->IsShown();
wxMenu m;
m.Append(wxID_FILE1, _L("Select All" ))->Enable(!filtering);
m.Append(wxID_FILE2, _L("Deselect All"))->Enable(!filtering);
m.AppendSeparator();
m.Append(wxID_FILE3, _L("Select visible" ))->Enable(!list_empty && filtering);
m.Append(wxID_FILE4, _L("Deselect visible"))->Enable(!list_empty && filtering);
m.AppendSeparator();
m.Append(wxID_FILE5, _L("Filter selected" ));
m.Append(wxID_FILE6, _L("Filter nonSelected"));
m.Bind(wxEVT_MENU, [this](wxCommandEvent& e) {
switch (e.GetId()){
case wxID_FILE1: SelectAll(true) ; break;
case wxID_FILE2: SelectAll(false) ; break;
case wxID_FILE3: SelectVisible(true) ; break;
case wxID_FILE4: SelectVisible(false); break;
case wxID_FILE5: Filter("::sel") ; break;
case wxID_FILE6: Filter("::nonsel") ; break;
default: break;
}
},wxID_FILE1, wxID_FILE6);
wxWindow* src = dynamic_cast<wxWindow*>(evt.GetEventObject());
if (!src) return;
wxPoint screen_pos = src->ClientToScreen(evt.GetPosition());
wxPoint local_pos = ScreenToClient(screen_pos);
PopupMenu(&m, local_pos);
}
+59
View File
@@ -0,0 +1,59 @@
#ifndef slic3r_GUI_CHECKLIST_hpp_
#define slic3r_GUI_CHECKLIST_hpp_
#include "../wxExtensions.hpp"
#include "Label.hpp"
#include "TextInput.hpp"
#include <wx/wx.h>
#include <vector>
#include <wx/scrolwin.h>
#include <wx/menu.h>
class CheckList : public wxWindow
{
public:
CheckList(
wxWindow* parent,
const wxArrayString& choices = wxArrayString(),
long scroll_style = wxVSCROLL
);
wxArrayInt GetSelections();
void SetSelections(wxArrayInt sel_array);
void Check(int i, bool checked);
bool IsChecked(int i);
void Filter(const wxString& filterText);
void SelectAll(bool value);
void SelectVisible(bool value);
private:
void ShowMenu(wxMouseEvent &e);
std::vector<wxCheckBox*> m_checks;
int m_list_size;
bool m_first_load;
wxBoxSizer* w_sizer;
wxPanel* f_bar;
wxBoxSizer* f_sizer;
TextInput* m_filter_box;
wxTextCtrl* m_filter_ctrl;
wxBoxSizer* fb_sizer;
wxStaticBitmap* m_menu_button;
wxScrolledWindow* m_scroll_area;
wxBoxSizer* s_sizer;
wxStaticText* m_info;
wxString m_info_nonsel;
wxString m_info_allsel;
wxString m_info_empty;
ScalableBitmap m_search;
ScalableBitmap m_menu;
};
#endif // !slic3r_GUI_CheckList_hpp_
+37 -7
View File
@@ -167,6 +167,30 @@ bool DropDown::HasDismissLongTime()
(now - dismissTime).total_milliseconds() >= 20; (now - dismissTime).total_milliseconds() >= 20;
} }
void DropDown::Popup(wxWindow *focus)
{
#ifdef __WXGTK__
if (!mainDropDown && m_widget) {
// Data-view cell editors can receive focus before wxGTK infers a
// native popup parent, so provide the current toplevel explicitly.
GtkWindow *transient_parent = nullptr;
for (wxWindow *win = GetParent(); win; win = win->GetParent()) {
GtkWidget *widget = static_cast<GtkWidget *>(win->GetHandle());
if (!widget)
continue;
GtkWidget *top = gtk_widget_get_toplevel(widget);
if (GTK_IS_WINDOW(top)) {
transient_parent = GTK_WINDOW(top);
break;
}
}
if (transient_parent)
gtk_window_set_transient_for(GTK_WINDOW(m_widget), transient_parent);
}
#endif
PopupWindow::Popup(focus);
}
void DropDown::paintEvent(wxPaintEvent& evt) void DropDown::paintEvent(wxPaintEvent& evt)
{ {
// depending on your system you may need to look at double-buffered dcs // depending on your system you may need to look at double-buffered dcs
@@ -554,7 +578,8 @@ void DropDown::messureSize()
wxWindow::SetSize(szContent); wxWindow::SetSize(szContent);
#ifdef __WXGTK__ #ifdef __WXGTK__
// Gtk has a wrapper window for popup widget // Gtk has a wrapper window for popup widget
gtk_window_resize (GTK_WINDOW (m_widget), szContent.x, szContent.y); if (szContent.x > 0 && szContent.y > 0)
gtk_window_resize (GTK_WINDOW (m_widget), szContent.x, szContent.y);
#endif #endif
if (!groups.empty() && subDropDown == nullptr) { if (!groups.empty() && subDropDown == nullptr) {
subDropDown = new DropDown(items); subDropDown = new DropDown(items);
@@ -564,11 +589,6 @@ void DropDown::messureSize()
subDropDown->use_content_width = true; subDropDown->use_content_width = true;
subDropDown->Create(GetParent()); subDropDown->Create(GetParent());
#ifdef __WXGTK__ #ifdef __WXGTK__
// Orca: Keep the wx parent as the combobox so wxPopupTransientWindow installs
// its capture handlers on the main dropdown, but make the native GTK
// popup transient for the currently open popup to satisfy Wayland's
// xdg-shell rule that a popup's parent must be the topmost mapped popup.
gtk_window_set_transient_for(GTK_WINDOW(subDropDown->GetHandle()), GTK_WINDOW(GetHandle()));
// Orca: On Wayland, while the sub holds an xdg_popup grab, motion events for // Orca: On Wayland, while the sub holds an xdg_popup grab, motion events for
// the cursor over main may not be delivered (Mutter drops motion // the cursor over main may not be delivered (Mutter drops motion
// outside the grabbing surface). Poll on idle and synthesize a // outside the grabbing surface). Poll on idle and synthesize a
@@ -636,8 +656,11 @@ void DropDown::autoPosition()
// may exceed // may exceed
auto drect = wxDisplay(GetParent()).GetGeometry(); auto drect = wxDisplay(GetParent()).GetGeometry();
if (GetPosition().y + size.y + 10 > drect.GetBottom()) { if (GetPosition().y + size.y + 10 > drect.GetBottom()) {
int available_height = drect.GetBottom() - GetPosition().y - 10;
if (available_height < rowSize.y * 2)
return;
if (use_content_width && count <= 15) size.x += 6; if (use_content_width && count <= 15) size.x += 6;
size.y = drect.GetBottom() - GetPosition().y - 10; size.y = available_height;
wxWindow::SetSize(size); wxWindow::SetSize(size);
if (selection >= 0) { if (selection >= 0) {
if (offset.y + rowSize.y * (selection + 1) > size.y) if (offset.y + rowSize.y * (selection + 1) > size.y)
@@ -727,6 +750,13 @@ void DropDown::mouseMove(wxMouseEvent &event)
drop.group = items[-index - 2].group_key; drop.group = items[-index - 2].group_key;
drop.need_sync = true; drop.need_sync = true;
drop.messureSize(); drop.messureSize();
#ifdef __WXGTK__
// wxGTK wraps popup contents in a native GtkWindow. Make the submenu
// transient for the currently mapped parent popup window before
// positioning/showing it, so wlroots/Hyprland sees the topmost parent.
if (m_widget && drop.m_widget)
gtk_window_set_transient_for(GTK_WINDOW(drop.m_widget), GTK_WINDOW(m_widget));
#endif
drop.autoPosition(); drop.autoPosition();
drop.paintNow(); drop.paintNow();
if (!drop.IsShown()) if (!drop.IsShown())
+2
View File
@@ -105,6 +105,8 @@ public:
bool HasDismissLongTime(); bool HasDismissLongTime();
void Popup(wxWindow *focus = nullptr) override;
protected: protected:
void Dismiss() override; void Dismiss() override;
+17 -11
View File
@@ -1465,6 +1465,7 @@ bool OrcaCloudServiceAgent::load_refresh_token(std::string& out_token)
if (payload.rfind("v2:", 0) == 0) { if (payload.rfind("v2:", 0) == 0) {
auto delim = payload.find(':', 3); auto delim = payload.find(':', 3);
if (delim == std::string::npos) { if (delim == std::string::npos) {
BOOST_LOG_TRIVIAL(warning) << "payload missing delim ':'.";
integrity_ok = false; integrity_ok = false;
} else { } else {
std::string stored_hmac = payload.substr(3, delim - 3); std::string stored_hmac = payload.substr(3, delim - 3);
@@ -1783,7 +1784,8 @@ int OrcaCloudServiceAgent::http_get(const std::string& path, std::string* respon
std::string url = api_base_url + path; std::string url = api_base_url + path;
BOOST_LOG_TRIVIAL(trace) << "OrcaCloudServiceAgent: GET " << url; BOOST_LOG_TRIVIAL(trace) << "OrcaCloudServiceAgent: GET " << url;
ensure_token_fresh("http_get_" + path); if (!ensure_token_fresh("http_get_" + path))
BOOST_LOG_TRIVIAL(warning) << "ensure_token_fresh returned false";
struct HttpResult { struct HttpResult {
bool success{false}; bool success{false};
@@ -1815,8 +1817,9 @@ int OrcaCloudServiceAgent::http_get(const std::string& path, std::string* respon
}) })
.on_error([&](std::string body, std::string error, unsigned resp_status) { .on_error([&](std::string body, std::string error, unsigned resp_status) {
result.success = false; result.success = false;
result.status = resp_status; result.status = resp_status == 0 ? 404 : resp_status;
result.body = body; result.body = body;
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: HTTP error - " << error;
}) })
.timeout_max(30) .timeout_max(30)
.perform_sync(); .perform_sync();
@@ -1884,8 +1887,9 @@ int OrcaCloudServiceAgent::http_post(const std::string& path, const std::string&
}) })
.on_error([&](std::string resp_body, std::string error, unsigned resp_status) { .on_error([&](std::string resp_body, std::string error, unsigned resp_status) {
result.success = false; result.success = false;
result.status = resp_status; result.status = resp_status == 0 ? 404 : resp_status;
result.body = resp_body; result.body = body;
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: HTTP error - " << error;
}) })
.timeout_max(30) .timeout_max(30)
.perform_sync(); .perform_sync();
@@ -1953,8 +1957,9 @@ int OrcaCloudServiceAgent::http_put(const std::string& path, const std::string&
}) })
.on_error([&](std::string resp_body, std::string error, unsigned resp_status) { .on_error([&](std::string resp_body, std::string error, unsigned resp_status) {
result.success = false; result.success = false;
result.status = resp_status; result.status = resp_status == 0 ? 404 : resp_status;
result.body = resp_body; result.body = body;
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: HTTP error - " << error;
}) })
.timeout_max(30) .timeout_max(30)
.perform_sync(); .perform_sync();
@@ -2019,8 +2024,9 @@ int OrcaCloudServiceAgent::http_delete(const std::string& path, std::string* res
}) })
.on_error([&](std::string resp_body, std::string error, unsigned resp_status) { .on_error([&](std::string resp_body, std::string error, unsigned resp_status) {
result.success = false; result.success = false;
result.status = resp_status; result.status = resp_status == 0 ? 404 : resp_status;
result.body = resp_body; result.body = resp_body;
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: HTTP error - " << error;
}) })
.timeout_max(30) .timeout_max(30)
.perform_sync(); .perform_sync();
@@ -2101,7 +2107,7 @@ bool OrcaCloudServiceAgent::http_post_token(const std::string& body, std::string
}) })
.on_error([&](std::string body, std::string error, unsigned resp_status) { .on_error([&](std::string body, std::string error, unsigned resp_status) {
success = false; success = false;
status = resp_status; status = resp_status == 0 ? 404 : resp_status;
resp_body = body; resp_body = body;
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: HTTP error - " << error; BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: HTTP error - " << error;
}) })
@@ -2171,7 +2177,7 @@ bool OrcaCloudServiceAgent::http_post_auth(const std::string& path, const std::s
}) })
.on_error([&](std::string body, std::string error, unsigned resp_status) { .on_error([&](std::string body, std::string error, unsigned resp_status) {
success = false; success = false;
status = resp_status; status = resp_status == 0 ? 404 : resp_status;
resp_body = body; resp_body = body;
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: HTTP (auth) error - " << error; BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: HTTP (auth) error - " << error;
}) })