Process a .gcode/txt file to extract data into a .csv file with Applescript

Hello,

I’m new here, and also a AppleScript rookie, and I’m looking for help with my little project.

This is a project that I’ll share on my Github later.

I’ve already received a lot of help on StackOverflow from @Mockman , whom I thank very much for the precious time he devoted to my project.

As a beginner, I’ve tried to thoroughly study and understand @Mockman code, and I’ve made a few unsuccessful attempts at adding and modifying certain functions.

I’m stuck on some things.

Let’s get to the heart of the matter first.

Here’s what I want to do: process a .gcode file to extract certain data, then create a .csv file containing all this data.

For your information, this .gcode file is used on a 3D printer or a laser/CNC machine. It contains all the information needed by the 3D printer, the various settings, as well as all the movements to be performed.

For movements, the file is formatted with one line per movement, without a delimiter. For settings, the file is formatted with one line per setting, but with a “;” as a delimiter. Each setting has a “name” and a “value”. As you might expect, the “name” doesn’t change, but the “value” can change.

For example, let’s take the line “sparse_infill_density = 100%”
The “name” is “sparse_infill_density” and its “value” is “100%”

Here’s a small example of a gcode file (this is a small example, as a gcode file can be much larger and contain many more lines of movements):

; HEADER_BLOCK_START
; generated by OrcaSlicer 2.2.0 on 2024-12-12 at 08:50:10
; total layer number: 504
; filament_density: 1.24
; filament_diameter: 1.75
; max_z_height: 121.30
; HEADER_BLOCK_END

; THUMBNAIL_BLOCK_START

;
; thumbnail begin 140x110 1308
; iVBORw0KGgoAAAANSUhEUgAAAIwAAABuCAYAAAD1TPu3AAADm0lEQVR4Ae3dzU4TURTA8VO+HkPjO7
; hTQB/ClTFGVybu+ZJI/di4UDAujWEBqOjGPZryAi58onpPuHdye7kz9AyFTmf+/+REwNIA88uZKW2C
; CBERzUjPZL4vROOkWP7J0lDntvTuCVGuGMpPWRyGt9k2dK4Yy5EsDHdkfrjr/j31cL7IwoBtQ6IIYi
; hbDopOAKPzi21DWrpVFMrf3tLwXQJGZ59t0+30oOvBP3EI9qLNsu/fTsGwbTpabqvkpgxM2DbhPhSe
; UDsbF8tFYMJwimppFigWMJyiWlgdLBYwXBC3JD1o8YXtuFDqgGHbzHh1t8plweS2jVCzi7Hs1cTyti
; aWsm3DKaqBTWKr6CxLr/9c5geXBZM+/OYU1aAmgeWhzA1u+E1QBWY/Gt0i8ZxmJnxdXBA3oLLngeps
; lfh+tx2YFEJ84HPzx90mnmP39aTzw88DmesLXW+T2Cp6jaOfq/cVHlGl89sd/G/uNjrf3Xzys+MvjP
; tuXmXmdcm8cXM/AUpXXMCSPg900Rx5ICeZjRHD+Ojmpbt9OjuZAUyDs26VACTFoWC+uo/rvHC3284M
; YGa8cbCUAQkf3/VA0gFMi7oISg7JoXv/0APZdLeJBzAtrgxLFZJ19/86GwkUwLS43PNAOSQH7v33bt
; Y8kngA05HSrZLbJAceSTyA6WAxlqrTzVpmANOhqqCsZzAApsPlsJRBAUyHiy9sc6cdwFBRvFUClA1/
; kAFDIwUsRwkUwNBI6cPlzQQKYKgo3irxwQMMjRRD2cocPMBQkWJJX68CGMBk04fLuWeWAQOYkXSrVL
; 2wCTCAKbrpfhG3LdWvgAMMYIpW3A9Bf9Af3HzuLQAGMNUFMGwYwIwVYABjCjCAMQUYwJgCDGBMAQYw
; pgADGFOAAYwpwADGFGAAYwowgDEFGMCYAgxgTAEGMKYAAxhTgAGMKcAAxhRgAGMKMIAxBRjAmAIMYE
; wBBjCmAAMYU4ABjCnAAMYUYABjCjCAMQUYwJgCDGBMAeZsnsjcoGye+rnFHwptN5jH7iCvuu9vlc0w
; udoA5pGDsQKK62nWwCwDY7o1GYz+EfO7AGlWTQMDkIY3bTAAmbGmAeYOSGa36wLDJmlJVw0GKC3rKs
; CApMVNEgxQOtAkwAh1p8uAEepedcAIdTcLGCEaBwwXs1RUBYZnhulcMZjj3mKBRYhypRuGrUKVxWCE
; iIiIiIiIiIiIiIiIiIiIiIiIiIioe/0HOzs0hzTYNJwAAAAASUVORK5CYII=
; thumbnail end
; THUMBNAIL_BLOCK_END

; external perimeters extrusion width = 0.42mm
; perimeters extrusion width = 0.45mm
; infill extrusion width = 0.45mm
; solid infill extrusion width = 0.42mm
; top infill extrusion width = 0.42mm
; support material extrusion width = 0.42mm
; first layer extrusion width = 0.50mm

; EXECUTABLE_BLOCK_START
M73 P0 R102
M106 S0
M106 P2 S0
;TYPE:Custom
M190 S55
M104 S220
G90
M83
G1 Z5 F6000
G1 E-0.2 F800
G1 X110 Y-110 F6000
M73 P0 R101
G1 E2 F800
M73 P0 R101
G1 Y-110 X55 Z0.25 F4800
G1 X-55 E8 F2400
G1 Y-109.6 F2400
G1 X55 E5 F2400
G1 Y-110 X55 Z0.45 F4800
G1 X-55 E8 F2400
G1 Y-109.6 F2400
G1 X55 E5 F2400
G92 E0
G90
G21
M83 ; use relative distances for extrusion
; filament start gcode
;right_extruder_material: PLA
SET_PRESSURE_ADVANCE ADVANCE=0.025; Override pressure advance value
M106 S0
M106 P2 S0
;LAYER_CHANGE
;Z:0.3
;HEIGHT:0.3
;BEFORE_LAYER_CHANGE
;0.3
G1 E-.8 F2100
;AFTER_LAYER_CHANGE
;0.3
;_SET_FAN_SPEED_CHANGING_LAYER
SET_VELOCITY_LIMIT ACCEL=500
G1 X-28.348 Y38.998 F30000
G1 Z.7
G1 Z.3
G1 E.8 F2100
;TYPE:Skirt
;WIDTH:0.5
G1 F3000
G1 E-.8 F2100
;WIPE_START
G1 F3000
G1 X-26.416 Y38.62
;WIPE_END
G1 X-19.273 Y35.931 Z.7 F30000
G1 X65.043 Y4.182 Z.7
G1 Z.3
G1 E.8 F2100
;TYPE:Support
G1 F3000
G1 X65.043 Y66.239 E3.30428
G1 X64.018 Y67.264 E.07719
G1 X-64.014 Y67.264 E6.81722
G1 X-65.039 Y66.239 E.07719
G1 X-65.039 Y58.683 E.40229
G1 X-64.014 Y57.658 E.07719
G1 X-50.697 Y57.658 E.7091
G1 X-50.697 Y55.815 E.09815
G1 X-49.672 Y54.79 E.07719
G1 X-30.617 Y54.79 E1.01457
G1 X-30.617 Y52.946 E.09815
G1 X-29.592 Y51.921 E.07719
G1 X-19.143 Y51.921 E.55636
G1 X-19.143 Y50.078 E.09815
G1 X-18.118 Y49.053 E.07719
G1 X-10.538 Y49.053 E.40363
G1 X-10.538 Y47.209 E.09815
G1 X-9.513 Y46.184 E.07719
G1 X-1.932 Y46.184 E.40363
G1 X-1.932 Y44.341 E.09815
G1 X-.907 Y43.316 E.07719
G1 X3.805 Y43.316 E.25089
G1 X3.805 Y41.472 E.09815
G1 X4.83 Y40.447 E.07719
G1 X9.542 Y40.447 E.25089
G1 X9.542 Y38.604 E.09815
G1 X10.567 Y37.579 E.07719
G1 X15.279 Y37.579 E.25089
G1 X15.279 Y35.736 E.09815
G1 X16.304 Y34.71 E.07719
G1 X21.016 Y34.71 E.25089
G1 X21.016 Y32.867 E.09815
G1 X22.041 Y31.842 E.07719
G1 X26.753 Y31.842 E.25089
G1 E-.8 F2100
;WIPE_START
G1 F3000
G1 X64.018 Y3.157
G1 X63.402 Y3.157
;WIPE_END
G1 X60.135 Y10.055 Z.7 F30000
G1 X50.701 Y29.973 Z.7
G1 Z.3
G1 E.8 F2100
G1 F3000
G1 E-.8 F2100
;WIPE_START
G1 F3000
G1 X52.569 Y29.973
G1 X52.569 Y30.17
;WIPE_END
G1 X45.262 Y32.376 Z.7 F30000
G1 X-30.274 Y55.182 Z.7
G1 Z.3
G1 E.8 F2100
G1 F3000
G1 X-30.225 Y55.182 E.00258
G1 X-30.225 Y53.109 E.11038
G1 E-.8 F2100
;WIPE_START
G1 F3000
G1 X-30.225 Y55.109
;WIPE_END
G1 X-23.488 Y51.522 Z.7 F30000
G1 X64.651 Y4.596 Z.7
G1 Z.3
G1 E.8 F2100
G1 F3000
G1 X60.021 Y3.549 E3.3717
G1 X59.536 Y3.549 E.02577
G1 X59.536 Y66.872 E3.3717
G1 X59.052 Y66.872 E.02577
G1 X59.052 Y3.99 E3.34821
G1 X58.698 Y4.344 E.02668
G1 X58.698 Y6.417 E.11038
G1 X58.568 Y6.417 E.0069
G1 X58.568 Y66.872 E3.21896
G1 X58.084 Y66.872 E.02577
G1 X58.084 Y6.417 E3.21896
G1 X57.6 Y6.417 E.02577
G1 X57.6 Y66.872 E3.21896
G1 X57.116 Y66.872 E.02577
G1 X57.116 Y6.417 E3.21896
G1 X56.632 Y6.417 E.02577
G1 X56.632 Y66.872 E3.21896
G1 X56.148 Y66.872 E.02577
G1 X56.148 Y6.894 E3.19358
G1 X55.83 Y7.213 E.024
G1 X55.83 Y7.494 E.01496
G1 E-.8 F2100
;WIPE_START
G1 F3000
G1 X55.83 Y7.213
G1 X56.148 Y6.894
G1 X56.148 Y8.163
;WIPE_END
G1 X55.664 Y9.286 Z.7 F30000
G1 Z.3
G1 E.8 F2100
G1 F3000
G1 X55.664 Y66.872 E3.06623
G1 X55.18 Y66.872 E.02577
G1 X55.18 Y9.286 E3.06623
G1 X54.696 Y9.286 E.02577
G1 X54.696 Y66.872 E3.06623
G1 X54.212 Y66.872 E.02577
G1 X54.212 Y9.286 E3.06623
G1 X53.757 Y9.286 E.02426
G1 X53.728 Y9.314 E.00214
G1 X53.728 Y66.872 E3.06472
G1 X53.244 Y66.872 E.02577
G1 X53.244 Y9.798 E3.03894
G1 X52.961 Y10.081 E.02132
G1 X52.961 Y10.336 E.01354
G1 E-.8 F2100
;WIPE_START
G1 F3000
G1 X52.961 Y10.081
G1 X53.244 Y9.798
G1 X53.244 Y11.144
;WIPE_END
G1 X53.16 Y18.776 Z.7 F30000
G1 X52.76 Y55.182 Z.7
G1 Z.3
G1 E.8 F2100
G1 F3000
; EXECUTABLE_BLOCK_END

; filament used [mm] = 33328.51
; filament used [cm3] = 80.16
; filament used [g] = 99.40
; filament cost = 1.99
; total filament used [g] = 99.40
; total filament cost = 1.99
; total layers count = 504
; estimated printing time (normal mode) = 1h 42m 0s

; CONFIG_BLOCK_START
; accel_to_decel_enable = 0
; accel_to_decel_factor = 50%
; activate_air_filtration = 0
; activate_chamber_temp_control = 0
; adaptive_bed_mesh_margin = 0
; adaptive_pressure_advance = 0
; adaptive_pressure_advance_bridges = 0
; adaptive_pressure_advance_model = "0,0,0\n0,0,0"
; adaptive_pressure_advance_overhangs = 0
; additional_cooling_fan_speed = 100
; alternate_extra_wall = 0
; auxiliary_fan = 1
; bbl_calib_mark_logo = 1
; 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]
; best_object_pos = 0.5,0.5
; bottom_shell_layers = 3
; bottom_shell_thickness = 0
; bottom_solid_infill_flow_ratio = 1
; bottom_surface_pattern = monotonic
; bridge_acceleration = 50%
; bridge_angle = 0
; bridge_density = 100%
; bridge_flow = 1
; bridge_no_support = 0
; bridge_speed = 25
; brim_ears_detection_length = 1
; brim_ears_max_angle = 125
; brim_object_gap = 0.1
; brim_type = no_brim
; brim_width = 5
; chamber_temperature = 0
; change_extrusion_role_gcode = 
; change_filament_gcode = 
; close_fan_the_first_x_layers = 1
; complete_print_exhaust_fan_speed = 80
; cool_plate_temp = 60
; cool_plate_temp_initial_layer = 55
; cooling_tube_length = 5
; cooling_tube_retraction = 91.5
; counterbore_hole_bridging = none
; curr_bed_type = Textured PEI Plate
; default_acceleration = 10000
; default_filament_colour = #000000
; default_filament_profile = "Flashforge Generic PLA"
; default_jerk = 0
; default_print_profile = 0.20mm Standard @Flashforge AD5M 0.4 Nozzle
; deretraction_speed = 35
; detect_narrow_internal_solid_infill = 1
; detect_overhang_wall = 1
; detect_thin_wall = 0
; different_settings_to_system = raft_layers;seam_position;sparse_infill_density;wall_loops;default_filament_colour;filament_notes;filament_retraction_length;filament_vendor;filament_z_hop;host_type;print_host;support_multi_bed_types
; disable_m73 = 0
; dont_filter_internal_bridges = disabled
; dont_slow_down_outer_wall = 0
; draft_shield = disabled
; during_print_exhaust_fan_speed = 60
; elefant_foot_compensation = 0.15
; elefant_foot_compensation_layers = 1
; emit_machine_limits_to_gcode = 1
; enable_arc_fitting = 0
; enable_filament_ramming = 1
; enable_long_retraction_when_cut = 0
; enable_overhang_bridge_fan = 1
; enable_overhang_speed = 1
; enable_pressure_advance = 1
; enable_prime_tower = 0
; enable_support = 0
; enforce_support_layers = 0
; eng_plate_temp = 60
; eng_plate_temp_initial_layer = 55
; ensure_vertical_shell_thickness = ensure_all
; exclude_object = 0
; extra_loading_move = -2
; extra_perimeters_on_overhangs = 0
; extruder_clearance_height_to_lid = 150
; extruder_clearance_height_to_rod = 27
; extruder_clearance_radius = 76
; extruder_colour = #E30006
; extruder_offset = 0x0
; fan_cooling_layer_time = 100
; fan_kickstart = 0
; fan_max_speed = 100
; fan_min_speed = 100
; fan_speedup_overhangs = 1
; fan_speedup_time = 0
; filament_colour = #E30006
; filament_cooling_final_speed = 3.4
; filament_cooling_initial_speed = 2.2
; filament_cooling_moves = 4
; filament_cost = 20
; filament_density = 1.24
; filament_diameter = 1.75
; filament_end_gcode = "; filament end gcode\n"
; filament_flow_ratio = 0.98
; filament_ids = GFL99
; filament_is_support = 0
; filament_loading_speed = 28
; filament_loading_speed_start = 3
; filament_max_volumetric_speed = 25
; filament_minimal_purge_on_wipe_tower = 15
; filament_multitool_ramming = 0
; filament_multitool_ramming_flow = 10
; filament_multitool_ramming_volume = 10
; filament_notes = "Custom settings for Flashforge PLA/PLA PRO - (Updated 01/10/24) - Not tested - Settings from Orca Flashforge"
; filament_ramming_parameters = "120 100 6.6 6.8 7.2 7.6 7.9 8.2 8.7 9.4 9.9 10.0| 0.05 6.6 0.45 6.8 0.95 7.8 1.45 8.3 1.95 9.7 2.45 10 2.95 7.6 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6"
; filament_retraction_length = 0.8
; filament_settings_id = "Flashforge PLA - BullzLabz"
; filament_shrink = 100%
; filament_shrinkage_compensation_z = 100%
; filament_soluble = 0
; filament_stamping_distance = 0
; filament_stamping_loading_speed = 0
; filament_start_gcode = "; filament start gcode\n;right_extruder_material: PLA\n"
; filament_toolchange_delay = 0
; filament_type = PLA
; filament_unloading_speed = 90
; filament_unloading_speed_start = 100
; filament_vendor = Flashforge
; filament_z_hop = 0.4
; filename_format = {input_filename_base}.gcode
; filter_out_gap_fill = 0.5
; first_layer_print_sequence = 0
; flush_into_infill = 0
; flush_into_objects = 0
; flush_into_support = 1
; flush_multiplier = 1
; flush_volumes_matrix = 0
; flush_volumes_vector = 140,140
; full_fan_speed_layer = 0
; fuzzy_skin = none
; fuzzy_skin_first_layer = 0
; fuzzy_skin_point_distance = 0.8
; fuzzy_skin_thickness = 0.3
; gap_fill_target = nowhere
; gap_infill_speed = 200
; gcode_add_line_number = 0
; gcode_comments = 0
; gcode_flavor = klipper
; gcode_label_objects = 0
; has_scarf_joint_seam = 0
; head_wrap_detect_zone = 
; high_current_on_filament_swap = 0
; hole_to_polyhole = 0
; hole_to_polyhole_threshold = 0.01
; hole_to_polyhole_twisted = 1
; host_type = flashforge
; hot_plate_temp = 50
; hot_plate_temp_initial_layer = 55
; idle_temperature = 0
; independent_support_layer_height = 1
; infill_anchor = 400%
; infill_anchor_max = 20
; infill_combination = 0
; infill_combination_max_layer_height = 100%
; infill_direction = 45
; infill_jerk = 9
; infill_wall_overlap = 25%
; inherits_group = ;"Flashforge Generic PLA";"Flashforge Adventurer 5M 0.4 Nozzle"
; initial_layer_acceleration = 500
; initial_layer_infill_speed = 80
; initial_layer_jerk = 9
; initial_layer_line_width = 0.5
; initial_layer_min_bead_width = 85%
; initial_layer_print_height = 0.3
; initial_layer_speed = 50
; initial_layer_travel_speed = 100%
; inner_wall_acceleration = 5000
; inner_wall_jerk = 9
; inner_wall_line_width = 0.45
; inner_wall_speed = 300
; interface_shells = 0
; interlocking_beam = 0
; interlocking_beam_layer_count = 2
; interlocking_beam_width = 0.8
; interlocking_boundary_avoidance = 2
; interlocking_depth = 2
; interlocking_orientation = 22.5
; internal_bridge_flow = 1
; internal_bridge_speed = 50
; internal_solid_infill_acceleration = 7000
; internal_solid_infill_line_width = 0.42
; internal_solid_infill_pattern = monotonic
; internal_solid_infill_speed = 250
; ironing_angle = -1
; ironing_flow = 15%
; ironing_pattern = zig-zag
; ironing_spacing = 0.1
; ironing_speed = 15
; ironing_type = no ironing
; is_infill_first = 0
; layer_change_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z]
; layer_height = 0.24
; line_width = 0.42
; long_retractions_when_cut = 0
; machine_end_gcode = G1 E-3 F3600\nG0 X50 Y50 F30000\nM104 S0 ; turn off temperature
; machine_load_filament_time = 0
; machine_max_acceleration_e = 5000,5000
; machine_max_acceleration_extruding = 20000,20000
; machine_max_acceleration_retracting = 5000,5000
; machine_max_acceleration_travel = 20000,20000
; machine_max_acceleration_x = 20000,20000
; machine_max_acceleration_y = 20000,20000
; machine_max_acceleration_z = 500,500
; machine_max_jerk_e = 2.5,2.5
; machine_max_jerk_x = 9,9
; machine_max_jerk_y = 9,9
; machine_max_jerk_z = 3,3
; machine_max_speed_e = 30,30
; machine_max_speed_x = 600,600
; machine_max_speed_y = 600,600
; machine_max_speed_z = 20,20
; machine_min_extruding_rate = 0,0
; machine_min_travel_rate = 0,0
; machine_pause_gcode = M25
; machine_start_gcode = M190 S[bed_temperature_initial_layer_single]\nM104 S[nozzle_temperature_initial_layer]\nG90\nM83\nG1 Z5 F6000\nG1 E-0.2 F800\nG1 X110 Y-110 F6000\nG1 E2 F800\nG1 Y-110 X55 Z0.25 F4800\nG1 X-55 E8 F2400\nG1 Y-109.6 F2400\nG1 X55 E5 F2400\nG1 Y-110 X55 Z0.45 F4800\nG1 X-55 E8 F2400\nG1 Y-109.6 F2400\nG1 X55 E5 F2400\nG92 E0
; machine_tool_change_time = 0
; machine_unload_filament_time = 0
; make_overhang_printable = 0
; make_overhang_printable_angle = 55
; make_overhang_printable_hole_size = 0
; manual_filament_change = 0
; max_bridge_length = 10
; max_layer_height = 0.28
; max_travel_detour_distance = 0
; max_volumetric_extrusion_rate_slope = 0
; max_volumetric_extrusion_rate_slope_segment_length = 3
; min_bead_width = 85%
; min_feature_size = 25%
; min_layer_height = 0.08
; min_length_factor = 0.5
; min_skirt_length = 0
; min_width_top_surface = 300%
; minimum_sparse_infill_area = 15
; mmu_segmented_region_interlocking_depth = 0
; mmu_segmented_region_max_width = 0
; notes = 
; nozzle_diameter = 0.4
; nozzle_height = 2.5
; nozzle_hrc = 0
; nozzle_temperature = 220
; nozzle_temperature_initial_layer = 220
; nozzle_temperature_range_high = 230
; nozzle_temperature_range_low = 190
; nozzle_type = stainless_steel
; nozzle_volume = 0
; only_one_wall_first_layer = 0
; only_one_wall_top = 0
; ooze_prevention = 0
; other_layers_print_sequence = 0
; other_layers_print_sequence_nums = 0
; outer_wall_acceleration = 5000
; outer_wall_jerk = 9
; outer_wall_line_width = 0.42
; outer_wall_speed = 200
; overhang_1_4_speed = 0
; overhang_2_4_speed = 40
; overhang_3_4_speed = 20
; overhang_4_4_speed = 10
; overhang_fan_speed = 100
; overhang_fan_threshold = 50%
; overhang_reverse = 0
; overhang_reverse_internal_only = 0
; overhang_reverse_threshold = 50%
; overhang_speed_classic = 0
; parking_pos_retraction = 92
; pellet_flow_coefficient = 0.4157
; pellet_modded_printer = 0
; post_process = 
; precise_outer_wall = 0
; precise_z_height = 0
; preferred_orientation = 0
; preheat_steps = 1
; preheat_time = 30
; pressure_advance = 0.025
; prime_tower_brim_width = 3
; prime_tower_width = 60
; prime_volume = 45
; print_compatible_printers = "Flashforge Adventurer 5M 0.4 Nozzle"
; print_flow_ratio = 1
; print_order = default
; print_sequence = by layer
; print_settings_id = 0.24mm Draft @Flashforge AD5M 0.4 Nozzle
; printable_area = -110x-110,110x-110,110x110,-110x110
; printable_height = 220
; printer_model = Flashforge Adventurer 5M
; printer_notes = 
; printer_settings_id = Flashforge Adventurer 5M 0.4 Nozzle - Orca
; 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
; raft_contact_distance = 0.1
; raft_expansion = 1.5
; raft_first_layer_density = 90%
; raft_first_layer_expansion = 2
; raft_layers = 4
; reduce_crossing_wall = 0
; reduce_fan_stop_start_freq = 1
; reduce_infill_retraction = 1
; required_nozzle_HRC = 0
; resolution = 0.012
; retract_before_wipe = 100%
; retract_length_toolchange = 2
; 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 = 0.8
; retraction_minimum_travel = 1
; retraction_speed = 35
; role_based_wipe_speed = 1
; rotate_solid_infill_direction = 1
; scan_first_layer = 0
; scarf_angle_threshold = 155
; scarf_joint_flow_ratio = 1
; scarf_joint_speed = 100%
; scarf_overhang_threshold = 40%
; seam_gap = 10%
; seam_position = back
; seam_slope_conditional = 0
; seam_slope_entire_loop = 0
; seam_slope_inner_walls = 0
; seam_slope_min_length = 20
; seam_slope_start_height = 0
; seam_slope_steps = 10
; seam_slope_type = none
; silent_mode = 0
; single_extruder_multi_material = 0
; single_extruder_multi_material_priming = 0
; skirt_distance = 2
; skirt_height = 1
; skirt_loops = 2
; skirt_speed = 50
; skirt_start_angle = -135
; skirt_type = combined
; slice_closing_radius = 0.049
; slicing_mode = regular
; slow_down_for_layer_cooling = 1
; slow_down_layer_time = 6
; slow_down_layers = 1
; slow_down_min_speed = 20
; slowdown_for_curled_perimeters = 1
; small_area_infill_flow_compensation = 0
; small_area_infill_flow_compensation_model = 0,0;"\n0.2,0.4444";"\n0.4,0.6145";"\n0.6,0.7059";"\n0.8,0.7619";"\n1.5,0.8571";"\n2,0.8889";"\n3,0.9231";"\n5,0.9520";"\n10,1"
; small_perimeter_speed = 50%
; small_perimeter_threshold = 0
; solid_infill_direction = 45
; solid_infill_filament = 1
; sparse_infill_acceleration = 100%
; sparse_infill_density = 100%
; sparse_infill_filament = 1
; sparse_infill_line_width = 0.45
; sparse_infill_pattern = grid
; sparse_infill_speed = 270
; spiral_mode = 0
; spiral_mode_max_xy_smoothing = 200%
; spiral_mode_smooth = 0
; staggered_inner_seams = 0
; standby_temperature_delta = -5
; start_end_points = 30x-3,54x245
; support_air_filtration = 1
; support_angle = 0
; support_base_pattern = rectilinear
; support_base_pattern_spacing = 2.5
; support_bottom_interface_spacing = 0.3
; support_bottom_z_distance = 0.15
; support_chamber_temp_control = 1
; support_critical_regions_only = 0
; support_expansion = 0
; support_filament = 0
; support_interface_bottom_layers = 0
; support_interface_filament = 0
; support_interface_loop_pattern = 0
; support_interface_not_for_body = 1
; support_interface_pattern = auto
; support_interface_spacing = 0.3
; support_interface_speed = 40
; support_interface_top_layers = 2
; support_line_width = 0.42
; support_material_interface_fan_speed = 100
; support_multi_bed_types = 1
; support_object_xy_distance = 0.3
; support_on_build_plate_only = 0
; support_remove_small_overhang = 1
; support_speed = 100
; support_style = default
; support_threshold_angle = 30
; support_top_z_distance = 0.15
; support_type = normal(auto)
; temperature_vitrification = 60
; template_custom_gcode = 
; textured_cool_plate_temp = 40
; textured_cool_plate_temp_initial_layer = 40
; textured_plate_temp = 60
; textured_plate_temp_initial_layer = 55
; thick_bridges = 0
; thick_internal_bridges = 1
; thumbnails = 140x110/PNG
; thumbnails_format = PNG
; time_cost = 0
; time_lapse_gcode = 
; timelapse_type = 0
; top_bottom_infill_wall_overlap = 25%
; top_shell_layers = 5
; top_shell_thickness = 1
; top_solid_infill_flow_ratio = 1
; top_surface_acceleration = 2000
; top_surface_jerk = 9
; top_surface_line_width = 0.42
; top_surface_pattern = monotonicline
; top_surface_speed = 200
; travel_acceleration = 10000
; travel_jerk = 12
; travel_slope = 3
; travel_speed = 500
; travel_speed_z = 0
; tree_support_adaptive_layer_height = 1
; tree_support_angle_slow = 25
; tree_support_auto_brim = 1
; tree_support_branch_angle = 45
; tree_support_branch_angle_organic = 40
; tree_support_branch_diameter = 5
; tree_support_branch_diameter_angle = 5
; tree_support_branch_diameter_double_wall = 3
; tree_support_branch_diameter_organic = 2
; tree_support_branch_distance = 5
; tree_support_branch_distance_organic = 1
; tree_support_brim_width = 3
; tree_support_tip_diameter = 0.8
; tree_support_top_rate = 30%
; tree_support_wall_count = 0
; upward_compatible_machine = 
; use_firmware_retraction = 0
; use_relative_e_distances = 1
; wall_direction = auto
; wall_distribution_count = 1
; wall_filament = 1
; wall_generator = classic
; wall_loops = 4
; wall_sequence = inner wall/outer wall
; wall_transition_angle = 10
; wall_transition_filter_deviation = 25%
; wall_transition_length = 100%
; wipe = 1
; wipe_before_external_loop = 0
; wipe_distance = 2
; wipe_on_loops = 0
; wipe_speed = 200
; wipe_tower_bridging = 10
; wipe_tower_cone_angle = 0
; wipe_tower_extra_flow = 100%
; wipe_tower_extra_spacing = 100%
; wipe_tower_filament = 0
; wipe_tower_max_purge_speed = 90
; wipe_tower_no_sparse_layers = 0
; wipe_tower_rotation_angle = 0
; wipe_tower_x = 165.000
; wipe_tower_x = 165
; wipe_tower_y = 250.000
; wipe_tower_y = 250
; wiping_volumes_extruders = 70,70,70,70,70,70,70,70,70,70
; xy_contour_compensation = 0
; xy_hole_compensation = 0
; z_hop = 0.4
; z_hop_types = Auto Lift
; z_offset = 0
; first_layer_bed_temperature = 55
; bed_shape = -110x-110,110x-110,110x110,-110x110
; first_layer_temperature = 220
; first_layer_height = 0.300
; CONFIG_BLOCK_END

Here’s @Mockman’s code:

use scripting additions

-- get original file and backup to desktop
tell application "Finder"
    set f to ((path to desktop as text) & "code.gcode")
    set dp to (path to desktop as text) -- where to deposit backup
    set d to duplicate file f to folder dp
end tell

-- use 'try' to halt script if file with backup name already exists
try
    tell application "Finder" to set name of d to "backup.cgode" -- rename backup

-- read original
set intake to read file f as «class utf8»
--> 722 paragraph text file

-- extract encoded thumbnail to field,value
set text item delimiters to {"; thumbnail begin", "; thumbnail end"}
set thumb to text item 2 of intake
set thup to paragraphs 2 thru -2 of thumb
set text item delimiters to linefeed
set thut to "thumbnail," & thup as text

-- specified entries to extract
set infillList to {"; raft_layers = 4", "; seam_position = back", "; sparse_infill_density = 100%", "; wall_loops = 4"}

-- extract specified entries to list
set lines22 to {}
repeat with setting in infillList
    set text item delimiters to {"; ", " = "} -- bits to exclude
    set loops to text items of setting -- split on bits
    --> "(, sparse_infill_density, 100%" -- NB list of 3 items, first item is "" before first comma
    set text item delimiters to "," -- csv separator
    set end of lines22 to items 2 thru 3 of loops as text -- join on csv separator
end repeat

-- write settings pairs to csv
set text item delimiters to linefeed
set tin to (lines22 as text) & linefeed -- default text input

-- name and location for csv ('dp' is currently the desktop)
set newf to (choose file name with prompt "Enter name for CSV file" default location alias dp default name "fields.csv")

set frn to open for access newf with write permission
set eof frn to 0
write (tin & linefeed) to frn starting at 0 as «class utf8»

close access frn

-- write base64 encode to file
set text item delimiters to ";" & space
set base to rest of text items of thut
set text item delimiters to ""
set base to base as text

set mewf to ((path to desktop as text) & "image") -- file reference for future base64-encoded text
set brn to open for access mewf with write permission
set eof brn to 0
write base to brn starting at 0 as «class utf8»
close access brn
delay 1

-- test that base64 file exists and then decode to png
my nowWhat(mewf)

on error number -48
    display dialog "Conflict: A file with the backup name already exists" buttons {"cancel"} default button "cancel" with icon stop with title "Chaos"
end try


--== handlers

-- does file exist yet
on existo(fil)
    tell application "Finder"
        try
            exists alias fil
        end try
    end tell
end existo

-- test that base64 file exists and then decode to png
on nowWhat(mewf)
    set tf to false
    repeat until tf is true
        try
            set tf to existo(mewf)
            set qewf to quoted form of POSIX path of mewf
            do shell script "/usr/bin/base64 --decode -i " & qewf & " -o ~/Desktop/base.png" -- can change resulting image name here
            exit repeat
        end try
        delay 0.5. -- depending upon computer performance could be reduced or possibly removed
    end repeat
end nowWhat

@Mockman’s code is perfect and works perfectly, but what I would like to modify or add to the code is simply to be able to add an “on run” statement at the beginning of the script to select the file to process and create a bundled application.

Then, modify the function that copies/pastes the selected lines by specifying that the “values” are not static and can therefore change.

The principle of this applescript would be as follows:

Step 1: Request the file to process (on run).
Step 2: Create a copy of the file as a backup. Step 3: Create a .csv file that will gather all the lines extracted from the .gcode file (name + value).
Step 5: Search for the “;thumbnail begin” and “;thumbnail end” tags and copy all the lines between these two tags into the .csv file.
Step 6: Search for approximately 22 lines of specific parameters, then copy them into the .csv file (example: search for the line “;sparse_infill_density = 100%” and copy/paste it into the .csv file, removing the delimiter and placing “sparse_infill_density” in a line with the value “100%”.)

My limited knowledge prevents me from going any further, despite my extensive research, experimentation, and learning.

I hope to find a kind soul here who will agree to help me. Thank you in advance.

Still processing, but in the meantime I cleaned up the last 2 routines…

-- does file exist yet
on existo(fil)
	try
		alias fil -- no need to send out command to 'Finder'
		return true
	on error
		return false
	end try
end existo

-- test that base64 file exists and then decode to png
on nowWhat(mewf)
	repeat until existo(mewf)
		try
			set qewf to quoted form of POSIX path of mewf
			do shell script "/usr/bin/base64 --decode -i " & qewf & " -o ~/Desktop/base.png" -- can change resulting image name here
			exit repeat
		end try
		delay 0.5 -- depending upon computer performance could be reduced or possibly removed
	end repeat
end nowWhat

Here is another version of “nowWhat()” routine that has a timeout so as not to get into an infinite loop.

on nowWhat(mewf)
	repeat with i from 30 to 0 by -1
		if existo(mewf) then exit repeat
		delay 0.5 -- depending upon computer performance could be reduced or possibly removed
	end repeat
	if i = 0 then
		display alert "Timeout occured while saving image file!" giving up after 10
		return false
	end if
	set qewf to quoted form of POSIX path of mewf
	try
		do shell script "/usr/bin/base64 --decode -i " & qewf & " -o ~/Desktop/base.png" -- can change resulting image name here
	end try
	return true
end nowWhat
1 Like

Thank a lot @robertfern for your help ! :smiley:

UPDATES:

I received more help from @Mockman on SO.
The result is great and works perfectly!

use scripting additions

-- get original file and backup to desktop
tell application "Finder"
    set gcodeFile to ((path to desktop as text) & "code.gcode")
    set repository to (path to desktop as text) -- where to deposit backup and CSV
    set dupeReference to duplicate file gcodeFile to folder repository
end tell

-- use 'try' to halt script if file with backup name already exists
try
    try -- remove in final version; trashes backup file automatically;
        tell application "Finder" to move file (repository & "backup.gcode" as text) to trash
    end try
    tell application "Finder" to set name of dupeReference to "backup.gcode" -- rename backup
    
    -- read original
    set intake to read file gcodeFile as «class utf8» --> 722 paragraph text file
    
    -- §
    -- process CONFIG_BLOCK §
    set text item delimiters to "; CONFIG_BLOCK_"
    set configBlock to text item 2 of intake
    set text item delimiters to linefeed
    set configBlockList to text items 2 thru -2 of configBlock -- list of 483 items (all settings)
    -- set configBlockList to text items of configBlock -- list of 485 items, includes 'start' and ""
    set configLength to length of configBlockList -- count of settings
    
    -- specified settings entries to extract
    set targetList to {"; raft_layers", "; seam_position", "; sparse_infill_density", "; wall_loops"}
    set targetLength to length of targetList -- count of target settings
    
    set cc to 0 -- counter for config list cycling - inner loop
    set tt to 0 -- counter for target list cycling - outer loop
    set loopTotal to 0
    set keepList to {}
    
    -- NB I have commented out the logging as it slows things down considerably
    -- enable the log commands to see what values were compared on each cycle
    repeat -- cycle through specified settings
        set tt to tt + 1 -- counter for target list cycling
        set loopTotal to loopTotal + 1 -- total loops required - outer loop
        repeat -- cycle through config settings
            set cc to cc + 1 -- counter for config list cycling
            set loopTotal to loopTotal + 1 -- total loops required - inner loop
            
            -- log cc
            -- log "config-used: " & item cc of configBlockList -- compare item in config list
            -- log "search-master: " & item tt of targetList -- compare item in target list
            
            -- compare item from each list
            if (item cc of configBlockList) begins with (item tt of targetList) then
                set end of keepList to item cc of configBlockList -- compile setting list
                exit repeat -- exit inner loop when matching to begin next outer loop item
            end if
            
            -- log "cc: " & cc
            if cc is equal to configLength then exit repeat -- exit inner loop when unmatched
        end repeat
        if tt is equal to targetLength then exit repeat -- exit outer loop once all matches made
    end repeat
    -- log loopTotal -- log combined inner and outer loop cycles
    keepList --> list of matched settings (including key and value); will be written to CSV;
    
    -- extract matching specified entries to list 
    set recordList to {}
    repeat with setting in keepList
        set text item delimiters to {"; ", " = "} -- bits to exclude
        set loops to text items of setting -- split on bits
        --> "(, sparse_infill_density, 100%" -- NB list of 3 items, first item is "" before first comma
        set text item delimiters to "," -- csv separator
        set end of recordList to items 2 thru 3 of loops as text -- join on csv separator
    end repeat
    
    -- write settings pairs to csv §
    set text item delimiters to linefeed
    set tin to (recordList as text) & linefeed -- default text input
    
    -- name and location for csv (dp is currently the desktop)
    set newf to (choose file name with prompt "Enter name for CSV file" default location alias repository default name "fields.csv")
    
    set frn to open for access newf with write permission
    set eof frn to 0
    write (tin & linefeed) to frn starting at 0 as «class utf8»
    close access frn
    
    -- §
    -- process THUMBNAIL_BLOCK §
    
    -- extract encoded thumbnail to field,value
    set text item delimiters to {"; thumbnail begin", "; thumbnail end"}
    set thumbBlock to text item 2 of intake
    set thumbLines to paragraphs 2 thru -2 of thumbBlock
    set text item delimiters to linefeed
    set thumbText to thumbLines as text -- encoded lines but still with '; '
    
    -- write base64-encoded image to file
    set text item delimiters to ";" & space
    set base64 to text items of thumbText -- encoded lines sans '; '
    set text item delimiters to ""
    set base64 to base64 as text -- encoded text as single string, suitable for decoding;
    
    set baseFile to ((path to desktop as text) & "image") -- file reference of location for pending base64-encoded text file
    set brn to open for access baseFile with write permission
    set eof brn to 0
    write base64 to brn starting at 0 as «class utf8» -- write encoded text to file for decoding
    close access brn
    delay 1
    
    -- test that base64 file exists and then decode to png
    my nowWhat(baseFile)
    
    
on error number -48 -- triggered when 'backup.gcode' already exists
    display dialog "Conflict: A file with the backup name already exists" buttons {"cancel"} default button "cancel" with icon stop with title "Chaos"
end try

-- §
-- handlers §

-- does file exist yet
on existo(fil)
    tell application "Finder"
        try
            exists alias fil
        end try
    end tell
end existo

-- test that base64 file exists and then decode to png
on nowWhat(baseFile)
    set trueFalse to false
    repeat until trueFalse is true
        try
            set trueFalse to existo(baseFile)
            set qbaseFile to quoted form of POSIX path of baseFile
            do shell script "/usr/bin/base64 --decode -i " & qbaseFile & " -o ~/Desktop/base.png"
            -- can change resulting image name above
            exit repeat
        end try
        delay 0.2 -- depending upon computer performance could be reduced or possibly removed
    end repeat
end nowWhat

Now, I’m going to try (it’s not easy with my limited knowledge) to transform all this code into an Enhanced Applet, because I would like to be able to use a GUI like that and be able to customize the graphic elements —>

For a script application, the general idea would be to have the run, open, and any other application handlers (such as folder actions) just pass items on to the main handler that does stuff. For example, the run handler could pass items from a choose file dialog, an open handler would pass on items passed to it, etc. Then the main handler would just need to deal with the file items it is given.

The script below is from rearranging @Mockman’s script, using a few application and general-purpose handlers and the improvements from @robertfern, with various folders and the extraction list moved to properties so they can be edited a little easier. I chose some items that look like they are blowing out the CSV, so you may need to do something else with those. You will also need to give the application Full Disk Access to write to the temporary folder (I used that to keep from polluting the desktop with temporary image files).

property backupFolder : ((path to desktop) as text) & "gcode stuff:backup:" -- or wherever
property csvFolder : ((path to desktop) as text) & "gcode stuff:csv:"
property imageFolder : ((path to desktop) as text) & "gcode stuff:images:"
property extractions : {¬
    "adaptive_pressure_advance_model", ¬
    "different_settings_to_system", ¬
    "filament_notes", ¬
    "filament_ramming_parameters", ¬
    "machine_start_gcode", ¬
    "small_area_infill_flow_compensation_model", ¬
    "sparse_infill_density", ¬
    "wall_loops", ¬
    "bed_shape"}

on run
    # comment or remove if using an enhanced applet, since it does its own
    open (choose file with prompt "Select gcode files:" with multiple selections allowed)
end run

on open (droppedItems as list)
    repeat with anItem in droppedItems
        doStuff for anItem
    end repeat
end open

to doStuff for (someFile as text)
    -- backup original file
    set {theName, extension} to (getNamePieces from someFile)
    set backupName to theName & " [backup]" & extension -- or whatever
    if not my existing(backupFolder & backupName) then -- only if no existing
        tell application "Finder"
            set d to duplicate file someFile to folder backupFolder
            set name of d to backupName -- rename backup
        end tell
    else -- oops, skip if there is an existing backup file name
        display dialog "Conflict: A file with the backup name '" & backupName & "' already exists" buttons {"Skip"} default button "Skip" with icon caution with title "Chaos"
        return
    end if
    
    -- read original
    set intake to read file someFile as «class utf8» --> 722 paragraph text file
    
    -- extract config block
    set text item delimiters to {"; CONFIG_BLOCK_START", "; CONFIG_BLOCK_END"}
    set configs to text item 2 of intake
    set found to {}
    repeat with setting in (paragraphs 2 thru -2 of configs)
        set text item delimiters to {"; ", " = "} -- bits to exclude
        set loops to rest of text items of setting -- split on bits, dropping first (blank) item --> "sparse_infill_density, 100%"
        set text item delimiters to "," -- csv separator
        if first item of loops is in extractions then set end of found to loops as text -- add if setting is in extractions
    end repeat
    
    -- name and location for csv
    set newf to (choose file name with prompt "Enter name for CSV file" default location alias csvFolder default name theName & " [fields].csv") -- ask
    -- set newf to csvFolder & theName & " [fields].csv" -- just save
    
    -- write settings pairs to csv
    set text item delimiters to linefeed
    writeStuff for ((found as text) & linefeed) into newf
    
    -- extract thumbnail block
    set text item delimiters to {"; thumbnail begin", "; thumbnail end"}
    set thumb to paragraphs 2 thru -2 of text item 2 of intake
    set text item delimiters to linefeed
    set thumbnail to "thumbnail," & thumb as text
    
    -- write base64 encode to file
    set text item delimiters to ";" & space
    set base to rest of text items of thumbnail
    set text item delimiters to ""
    set mewf to ((path to temporary items as text) & theName & extension & " [image]") -- file reference for base64-encoded text
    writeStuff for (base as text) into mewf
    delay 1
    
    -- test that base64 file exists and then decode to png
    my nowWhat(mewf, theName)
end doStuff


########################################
#   Utility Handlers
########################################

to getNamePieces from filePath
    tell application "System Events" to tell disk item (filePath as text)
        set {theName, extension} to {name, name extension}
    end tell
    if extension is not "" then
        set theName to text 1 thru -((count extension) + 2) of theName -- just the name part
        set extension to "." & extension
    end if
    return {theName, extension}
end getNamePieces

to writeStuff for whatever into theFile given typeClass:typeClass : «class utf8», append:(append as boolean) : false
    try
        set fileRef to my (open for access theFile with write permission)
        if not append then set eof fileRef to 0 -- start at beginning and overwrite end of file
        write whatever to fileRef starting at eof as typeClass -- text, data, list, etc
        close access fileRef
    on error errmess
        log errmess -- alert, etc
        try -- make sure file is closed (running in script editor, etc)
            close access fileRef
        end try
    end try
end writeStuff

-- does file exist?
on existing(filePath as text)
    try
        alias filePath -- no need to send out command to 'Finder'
        return true
    on error
        return false
    end try
end existing

-- test that base64 file exists and then decode to png
on nowWhat(filePath, outputName)
    repeat with i from 30 to 0 by -1
        if existing(filePath) then exit repeat
        delay 0.5 -- depending upon computer performance could be reduced or possibly removed
    end repeat
    if i = 0 then
        display alert "Timeout occured while saving image file!" giving up after 10
        return false
    end if
    try
        do shell script "/usr/bin/base64 --decode -i " & quoted form of POSIX path of filePath & " -o " & quoted form of POSIX path of (imageFolder & outputName & ".png")
    on error errmess
        display alert "Alert" message errmess
    end try
    return true
end nowWhat
2 Likes

Thank you @red_menace,

I managed to get your code to work, but would it be possible to have the script create all the folders (gcode stuff, backup, csv, images) rather than creating them manually before running the script?

I managed to create the “droplet” app, but I can’t seem to rearrange the code to make it work.

Could someone please point me in the right direction?

Should I replace “someFile” with “theFile”?

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

--	Properties configuting the droplet shell
property appletDropImage : "DropIcons"
property appletDropName : "Drop your gcode file here"
property appletSearchName : "Find gcode files"
property appletSearchImage : "GBadge"

property extractions : {¬
	"adaptive_pressure_advance_model", ¬
	"different_settings_to_system", ¬
	"filament_notes", ¬
	"filament_ramming_parameters", ¬
	"machine_start_gcode", ¬
	"small_area_infill_flow_compensation_model", ¬
	"sparse_infill_density", ¬
	"wall_loops", ¬
	"bed_shape"}

on appletFileIsAcceptable(theFile)
	return true
end appletFileIsAcceptable

on prepareToProcessFiles(theFiles)
	set progress description to "This application allows you to extract the main printing settings, and the thumbnail image of a gcode file generated for a 3D printer."
	--	your initialization code goes here
	
	return theFiles
end prepareToProcessFiles

on processAFile(theFile)
	--	your code to process theFile goes here
	doStuff for theFile
end processAFile

on cleanup()
	--	your cleanup code goes here
end cleanup


on open theFiles
	try
		--	Initialization...
		set progress total steps to -1
		set progress completed steps to 0
		set progress description to ""
		set progress additional description to "Preparing to process files..."
		set theFiles to prepareToProcessFiles(theFiles)
		
		--	Process Files...
		set progress total steps to count of theFiles
		set progress completed steps to 0
		repeat with aFile in theFiles
			tell application "System Events" to set theFileName to name of aFile
			set progress additional description to "Processing “" & theFileName & "”..."
			processAFile(contents of aFile)
			set progress completed steps to progress completed steps + 1
		end repeat
		
		--	CLeanup...
		set progress total steps to -1
		set progress completed steps to 0
		set progress additional description to "Cleaning up..."
		cleanup()
	on error errMsg number errNum from errFrom to errTo partial result errPartialResult
		--	Cleanup...
		set progress total steps to 0
		set progress completed steps to 0
		set progress additional description to "Cleaning up..."
		cleanup()
		error errMsg number errNum from errFrom to errTo partial result errPartialResult -- resignal the error
	end try
end open

to doStuff for (someFile as text)
	-- backup original file
	set {theName, extension} to (getNamePieces from someFile)
	set backupName to theName & " [backup]" & extension -- or whatever
	if not my existing(backupFolder & backupName) then -- only if no existing
		tell application "Finder"
			set d to duplicate file someFile to folder backupFolder
			set name of d to backupName -- rename backup
		end tell
	else -- oops, skip if there is an existing backup file name
		display dialog "Conflict: A file with the backup name '" & backupName & "' already exists" buttons {"Skip"} default button "Skip" with icon caution with title "Chaos"
		return
	end if
	
	-- read original
	set intake to read file someFile as «class utf8» --> 722 paragraph text file
	
	-- extract config block
	set text item delimiters to {"; CONFIG_BLOCK_START", "; CONFIG_BLOCK_END"}
	set configs to text item 2 of intake
	set found to {}
	repeat with setting in (paragraphs 2 thru -2 of configs)
		set text item delimiters to {"; ", " = "} -- bits to exclude
		set loops to rest of text items of setting -- split on bits, dropping first (blank) item --> "sparse_infill_density, 100%"
		set text item delimiters to "," -- csv separator
		if first item of loops is in extractions then set end of found to loops as text -- add if setting is in extractions
	end repeat
	
	-- name and location for csv
	set newf to (choose file name with prompt "Enter name for CSV file" default location alias csvFolder default name theName & " [fields].csv") -- ask
	-- set newf to csvFolder & theName & " [fields].csv" -- just save
	
	-- write settings pairs to csv
	set text item delimiters to linefeed
	writeStuff for ((found as text) & linefeed) into newf
	
	-- extract thumbnail block
	set text item delimiters to {"; thumbnail begin", "; thumbnail end"}
	set thumb to paragraphs 2 thru -2 of text item 2 of intake
	set text item delimiters to linefeed
	set thumbnail to "thumbnail," & thumb as text
	
	-- write base64 encode to file
	set text item delimiters to ";" & space
	set base to rest of text items of thumbnail
	set text item delimiters to ""
	set mewf to ((path to temporary items as text) & theName & extension & " [image]") -- file reference for base64-encoded text
	writeStuff for (base as text) into mewf
	delay 1
	
	-- test that base64 file exists and then decode to png
	my nowWhat(mewf, theName)
end doStuff

########################################
#   Utility Handlers
########################################

to getNamePieces from filePath
	tell application "System Events" to tell disk item (filePath as text)
		set {theName, extension} to {name, name extension}
	end tell
	if extension is not "" then
		set theName to text 1 thru -((count extension) + 2) of theName -- just the name part
		set extension to "." & extension
	end if
	return {theName, extension}
end getNamePieces

to writeStuff for whatever into theFile given typeClass:typeClass : «class utf8», append:(append as boolean) : false
	try
		set fileRef to my (open for access theFile with write permission)
		if not append then set eof fileRef to 0 -- start at beginning and overwrite end of file
		write whatever to fileRef starting at eof as typeClass -- text, data, list, etc
		close access fileRef
	on error errmess
		log errmess -- alert, etc
		try -- make sure file is closed (running in script editor, etc)
			close access fileRef
		end try
	end try
end writeStuff

-- does file exist?
on existing(filePath as text)
	try
		alias filePath -- no need to send out command to 'Finder'
		return true
	on error
		return false
	end try
end existing

-- test that base64 file exists and then decode to png
on nowWhat(filePath, outputName)
	repeat with i from 30 to 0 by -1
		if existing(filePath) then exit repeat
		delay 0.5 -- depending upon computer performance could be reduced or possibly removed
	end repeat
	if i = 0 then
		display alert "Timeout occured while saving image file!" giving up after 10
		return false
	end if
	try
		do shell script "/usr/bin/base64 --decode -i " & quoted form of POSIX path of filePath & " -o " & quoted form of POSIX path of (imageFolder & outputName & ".png")
	on error errmess
		display alert "Alert" message errmess
	end try
	return true
end nowWhat

A normal script application calls the run hander when double-clicked, or the open handler when items are dropped, so you need to do something in the run handler to get files. The enhanced applet makes this a little easier by providing a drop window instead of the run handler, which then passes the files to the open handler - however, it looks like these items are URL’s («class furl»). The easiest way to take care of that is to coerce the file item to an alias for System Events or the Finder, for example:
tell application "System Events" to set theFileName to name of (aFile as alias)

After making the above change and adding the properties that declare the various folder paths, your script appears to run OK.

Here is one way to do this:

Add these two utility handlers:

-- make root folder and subfolders on desktop
on makeFolders()
	set pdt to (get path to desktop) as text
	tell application "System Events"
		set gcodeFolder to make new folder at folder pdt with properties {name:"gcode-stuff"}
	end tell
	set subFolders to {backupFolder, csvFolder, imageFolder}
	makeSubFolders(gcodeFolder, subFolders)
end makeFolders

-- create subfolders from supplied list at supplied location
on makeSubFolders(top, subFolders)
	set text item delimiters to ":"
	tell application "System Events"
		repeat with xf in subFolders
			set newfol to text item -2 of xf
			make new folder at top with properties {name:newfol}
		end repeat
	end tell
	set text item delimiters to ""
end makeSubFolders

Then add this line at the top of the on run and on open handlers:

	makeFolders()

Regardless of how the script is triggered, it will then call the makeFolder handler, which will create the root folder “gcode-stuff” and then call the makeSubFolders handler which will create the subfolders.

1 Like

I’m here to give you an update on the progress!

First of all, thank you again for your help @red_menace & @Mockman .

I tried to understand @red_menace’s explanation by reading and doing a lot more research and reading, but I think my rookie shortcomings are preventing me from going any further (for now).

I’ve still made a step forward since I managed to “drop” the gcode file into the enhanced applet, and the folders and subfolders are successfully created on the desktop. Then the application gets stuck on “Preparing to process files…”.

The main issue with progress indicators is that the system needs to be given time to update them. For whatever reason, the built-in stuff winds up being a royal pain, usually with various main thread and event issues. Most of the time I use an indeterminate spinner or a third party background app such as SKProgressBar.

With that said, I’ve been playing with this script and have it working as a regular and enhanced applet, using the built-in progress indicator. The way I handle the progress is to put the updates into separate handers, which gives the system a chance to do some of its stuff in between calls - putting everything inside a repeat statement tends to jam up the event queue, and not just for progress indicators.

The script has been rearranged and optimized a bit more, including some tweaks to also allow it to run in a script editor for testing, so here is the current version:

use framework "Foundation" -- for AppleScriptObjC
use scripting additions

property backupFolder : ((path to desktop) as text) & "gcode stuff:backup:" -- or wherever
property csvFolder : ((path to desktop) as text) & "gcode stuff:csv:"
property imageFolder : ((path to desktop) as text) & "gcode stuff:images:"
property extractedSettings : {¬
    "adaptive_pressure_advance_model", ¬
    "different_settings_to_system", ¬
    "filament_notes", ¬
    "filament_ramming_parameters", ¬
    "machine_start_gcode", ¬
    "small_area_infill_flow_compensation_model", ¬
    "sparse_infill_density", ¬
    "wall_loops", ¬
    "bed_shape"}

property uiDelay : 1 -- delay to view progress
global initialized, itemCount, currentStep

on run -- check for Enhanced Applet before putting up file dialog
    setup()
    tell application "System Events" to set executableName to value of property list item "CFBundleExecutable" of property list file (POSIX path of (path to current application) & "/Contents/Info.plist")
    if executableName is not in {"FancyDropletFat"} then open (choose file with prompt "Select gcode files:" with multiple selections allowed)
end run

on open (droppedItems as list)
    setup()
    resetProgress()
    set itemCount to (count droppedItems)
    set progress total steps to itemCount
    repeat with anItem in droppedItems
        showTaskStart(anItem)
        doStuff for anItem
        showTaskFinish()
    end repeat
    cleanup()
end open

to setup() -- set things up
    try
        if intitialized then return -- guard
    end try
    repeat with folderPath in {backupFolder, csvFolder, imageFolder} -- create output folders
        (current application's NSFileManager's defaultManager's createDirectoryAtPath:(POSIX path of folderPath) withIntermediateDirectories:true attributes:(missing value) |error|:(missing value))
    end repeat
    -- whatever
    set initialized to true
end setup

to doStuff for (someFile as text) -- main processing handler
    tell (makeBackup for someFile)
        if it is missing value then return -- skipped
        set {theName, extension} to it
    end tell
    set input to current application's (read file someFile as «class utf8») -- read original
    
    # extract settings in config block
    set found to {}
    repeat with setting in paragraphs 2 thru -2 of (findSubstring of input from "; CONFIG_BLOCK_START" to "; CONFIG_BLOCK_END")
        set text item delimiters to {"; ", " = "} -- bits to exclude
        tell (text items of setting) to if second item is in extractedSettings then
            set AppleScript's text item delimiters to "," -- csv separator
            set end of found to (rest of it as text) -- add if setting is in extracted list
        end if
    end repeat
    
    # name and location for csv
    -- set newf to (choose file name with prompt "Enter name for CSV file" default location alias csvFolder default name theName & " [fields].csv") -- ask
    set newf to csvFolder & theName & " [fields].csv" -- just save to pre-set folder
    
    # write settings pairs to csv
    set text item delimiters to linefeed
    writeStuff for ((found as text) & linefeed) into newf
    
    # extract lines in thumbnail block and write base64 encode to temporary file
    set thumbnail to (findSubstring of input from "; thumbnail begin" to "; thumbnail end")
    set text item delimiters to ";" & space
    set thumbnail to text items of thumbnail
    set tempFile to ((path to temporary items as text) & theName & extension & " [image]") -- file reference for later
    set text item delimiters to ""
    writeStuff for (rest of thumbnail) as text into tempFile
    delay 0.5
    
    nowWhat(tempFile, theName)
end doStuff

on nowWhat(filePath, outputName) -- test that base64 file exists and decode to png
    repeat with i from 60 to 0 by -1
        if existing(filePath) then exit repeat
        delay 0.25
    end repeat
    if i = 0 then
        display alert "Error checking for base64 file" message "Timeout occured while testing for existence of image file " & quoted form of filePath & "." giving up after 10
    else
        try
            do shell script "/usr/bin/base64 --decode -i " & quoted form of POSIX path of filePath & " -o " & quoted form of POSIX path of (imageFolder & outputName & ".png")
            return true
        on error errmess
            display alert "Decoding Error" message errmess
        end try
    end if
    return false
end nowWhat

to resetProgress()
    set currentStep to 0
    set progress completed steps to 0
    set progress description to "This application allows you to extract specified print settings and the thumbnail image of a gcode file generated for a 3D printer."
    set progress additional description to "Preparing to process files..."
end resetProgress

to showTaskStart(theFile as text)
    set progress additional description to "Processing " & quoted form of theFile & " (" & (currentStep + 1) & " of " & itemCount & ")"
    delay uiDelay
end showTaskStart

to showTaskFinish()
    set currentStep to currentStep + 1
    set progress completed steps to currentStep
    delay uiDelay
end showTaskFinish

on cleanup()
    set progress additional description to "Tasks complete, cleaning up..."
    --  cleanup whatever
    delay uiDelay * 2
end cleanup


########################################
#   Utility Handlers
########################################

on existing(filePath as text) -- does file exist?
    try
        alias filePath -- no need to use 'Finder'
        return true
    end try
    return false
end existing

to makeBackup for (theFile as text) -- backup original file
    set {theName, extension} to (getNamePieces from theFile)
    set backupName to theName & " [backup]" & extension -- or whatever
    if not my existing(backupFolder & backupName) then -- only if no existing
        tell application "Finder" to set name of (duplicate file theFile to folder backupFolder) to backupName -- rename backup
        return {theName, extension}
    else -- oops, skip if there is an existing backup file
        display dialog "Conflict: A file with the backup name " & quoted form of backupName & " already exists" buttons {"Skip"} default button "Skip" with icon caution with title "Chaos" giving up after 10
        return missing value
    end if
end makeBackup

to getNamePieces from (filePath as text)
    tell application "System Events" to tell disk item filePath to set {theName, extension} to {name, name extension}
    if extension is not "" then
        set theName to text 1 thru -((count extension) + 2) of theName -- just the name part
        set extension to "." & extension
    end if
    return {theName, extension}
end getNamePieces

# Find a substring that is between start and end items, optionally keeping delimiting items.
to findSubstring of (theText as text) from (startText as text) to (endText as text) given delimitingItems:(delimitingItems as boolean) : false
    set {here, there} to {1, -1} -- default beginning and end
    tell (offset of startText in theText) to ¬
        if it is not 0 then set here to it + (count startText) -- skip startText for there offset
    tell (offset of endText in (text here thru -1 of theText)) to ¬
        if it is not 0 then set there to it + here - 2
    if delimitingItems then -- adjust indexes to include
        if here is not 1 then set here to here - (count startText)
        if there is not -1 then set there to there + (count endText)
    end if
    return (text here thru there of theText)
end findSubstring

to writeStuff for whatever into theFile given typeClass:typeClass : «class utf8», append:(append as boolean) : false
    try
        set fileRef to my (open for access theFile with write permission)
        if not append then set eof fileRef to 0 -- overwrite existing
        write whatever to fileRef starting at eof as typeClass -- text, data, list, etc
        close access fileRef
    on error errmess
        log errmess -- alert, etc
        try -- make sure file is closed (testing in script editor, etc)
            close access fileRef
        end try
    end try
end writeStuff

Thank you @red_menace ,

First results: by creating an simple “Applescript bundle” your code works perfectly except for one small detail that bothers me a little: it creates the “gcode stuff” folder and subfolders as soon as it’s opened/executed, without even having had time to select the .gcode file to process! This is just a detail, but I think it can confuse the user! So, I simply moved the “setup()” just before “showTaskStart(anItem)”, it works!

Second result: by creating a simple “applet” or an “enhanced applet” the code works quite well. Everything in the process runs smoothly, and the gcode file is processed correctly, as is the backup. The same goes for the creation of folders and subfolders, which is done before selecting the .gcode file to process. So, I simply moved the “setup()” just before “showTaskStart(anItem)”, it works!

Thank you very much for your help and for the time you dedicate to my project!

Oops, the destination folders do need to exist to put stuff in them, but it looks like I got carried away with the setup. Both the run handler and the Enhanced Applet call the open handler, so the setup can just be done there after the files have been selected - just removing the setup() statement from the run handler will also fix that.

Great :+1: Thanks a lot @red_menace

This is really great, it works great!

Just a few questions and a few small aesthetic details (GUI) to fix.

Is it possible to replace the icon displayed on the alerts?

Is it possible to center the text on the processing window by displaying only the file name without its full URL, and then display a ticker icon when the process is 100% Completed?

Is it possible to add an “info” icon to the top right of the “search” icon to display another window/tab with information?

Ah, feature creep - I know it well…

• If you are talking about display alert, it has a couple of built-in icons, and NSAlert can use any icon, although all alerts have that ugly iOS vertical orientation - and while alert icons can be changed, you can’t get rid of them. For that you would need to use a NSWindow or NSPanel, which will start getting into some AppleScriptObjC.

• The text in the progress additional description can easily be changed to just the name, for example:

to showTaskStart(theFile as text)
    tell application "Finder" to set theName to name of file theFile
    set progress additional description to "Processing:  " & quoted form of theName & " (" & (currentStep + 1) & " of " & itemCount & ")"
    delay uiDelay
end showTaskStart

You would need to access the underlying NSTextField to change its alignment to centered - you have access to the main window, perhaps the author(s) could tell you what the subviews are. (Edit: After looking at it again, that just looks like the built-in progress. Since the window was a bit bigger, I thought it might be doing something else.) What do you mean by a “ticker icon”?

• The applet window doesn’t seem to have many customizations available, but don’t forget that an application has an about panel, and with a bit of AppleScriptObjC, its menus can also be edited to provide custom functions.

1 Like

Ok, i see ! Thank you @red_menace

The ideal would be to achieve something like this!

I’ve already worked on iOS application user interfaces, but in Obj-C. That was about ten years ago; today, with Swift, I’m a bit overwhelmed!

But it’s true that I’m thinking about creating a real Mac OS and maybe iOS app. The most complicated thing would be finding someone, a freelancer, to create it for me.

PS : @red_menace & @Mockman Oh yes, I almost forgot, my job is a graphic designer, so if you need graphic design work, don’t hesitate to ask me; I’d be happy to help you like you did for me!

1 Like

You can still use Objective-C, and the method calls in AppleScriptObjC are similar (but a bit more wordy). Swift looked interesting when it came out, but it really turned into a mess. There are obviously people that like it for whatever reason, but I’m not one of them. I’m not a pro, so I don’t use anything that has a bazillion little details that I can’t remember if I don’t do anything for a few months - just trying to navigate the Cocoa API is enough.

A NSAlert could be used for the cleanup dialog (and you could even use that checkmark image), but as mentioned the built-in progress doesn’t have much customization. And unfortunately, one of the details is that I haven’t seen a way to close the progress panel other than quitting the application, so even if using NSAlert, the progress window would still be in the background.

Ok thank you for these informations! I think I’m going to be satisfied with the great result that you’ve already given to me. It’s very largely sufficient. I’m a perfectionist and I always try to improve things especially with graphics! :wink:

It’s already very good that the script works like a charm, and it’s enough for the use that I’ll make it.

Now the next step for me is to import the variables (csv file) into Adobe Illustrator to create a nice layout which will then be exported to PDF.

Thanks a lot

You’re probably going to curse me and rage at me, saying, “Oh, that one’s a bit overboard.” :confused: sing

But I’m working on importing variables into Illustrator, and I’ve a few quick questions.

To simplify the process and prevent the user from getting lost when processing multiple files, I think the best thing would be to automatically name the .csv results file with the same name as the processed .gcode file. What do you think? It would also be interesting to add an entry to the csv file with “project_name = gcode file name”

Would it be possible, without too much work, to add a function that would allow renaming certain text extracted from the gcode file, such as “Flashforge” to “FF”?

Also, on certain extracted lines like “enable_support = 0,” the “0” is a boolean meaning “NO” and the “1” is “YES”. Would it be possible to convert this into the csv results? I think it’s quite difficult to isolate this because certain other extracted lines like “nozzle_diameter=0.4” also have a “0” in their results.

In Illustrator, with variable import, I need to be able to retrieve the image URL (thumbnail) directly from an entry in the csv file. Is this also possible?