CyGuy8 commited on
Commit
8c02eb4
·
1 Parent(s): 017ca97

Refine G-code startup and preview refresh

Browse files
Files changed (3) hide show
  1. app.py +44 -10
  2. tests/test_tiff_to_gcode.py +41 -2
  3. tiff_to_gcode.py +12 -4
app.py CHANGED
@@ -2928,6 +2928,22 @@ def shift_selected_slice(records: list[dict] | None, selected: str | None, index
2928
  return gr.update(value=next_index), label, preview
2929
 
2930
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2931
  def generate_dynamic_stacks(
2932
  records: list[dict] | None,
2933
  settings_table: Any,
@@ -2989,20 +3005,12 @@ def generate_dynamic_stacks(
2989
  messages.append("Reference TIFF Stack: updated automatically.")
2990
  else:
2991
  messages.append("Reference TIFF Stack: skipped (no generated shape slices available).")
2992
- first_state = records[0].get("tiff_state") or _empty_state()
2993
- label, preview = _render_selected_slice(first_state, 0)
2994
- slider = gr.update(
2995
- minimum=0,
2996
- maximum=max(0, len(first_state.get("tiff_paths", [])) - 1),
2997
- value=0,
2998
- step=1,
2999
- interactive=len(first_state.get("tiff_paths", [])) > 1,
3000
- )
3001
  return (
3002
  records,
3003
  [record.get("zip_path") for record in records if record.get("zip_path")],
3004
  "\n".join(messages),
3005
- _dropdown_update(records),
3006
  slider,
3007
  label,
3008
  preview,
@@ -3089,6 +3097,10 @@ def split_selected_shape_for_grid(
3089
  records,
3090
  _shape_settings_rows(records),
3091
  _spacing_table_update(records, existing_spacing, use_individual_spacing),
 
 
 
 
3092
  [],
3093
  [],
3094
  _gcode_dropdown_update(records),
@@ -3123,6 +3135,10 @@ def split_selected_shape_for_grid(
3123
  records,
3124
  _shape_settings_rows(records),
3125
  _spacing_table_update(records, existing_spacing, use_individual_spacing),
 
 
 
 
3126
  [record.get("zip_path") for record in records if record.get("zip_path")],
3127
  [record.get("gcode_path") for record in records if record.get("gcode_path")],
3128
  _gcode_dropdown_update(records),
@@ -3168,6 +3184,8 @@ def split_selected_shape_for_grid(
3168
  })
3169
  split_records.append(piece_record)
3170
  next_records = _reindex_shape_records([*records[:pos], *split_records, *records[pos + 1:]])
 
 
3171
  slider, label, preview = preview_selected_split_piece(pieces, None)
3172
  status = (
3173
  f"Split Shape {source.get('idx', pos + 1)} into {len(pieces)} print-ready stacks "
@@ -3180,6 +3198,10 @@ def split_selected_shape_for_grid(
3180
  next_records,
3181
  _shape_settings_rows(next_records),
3182
  _spacing_table_update(next_records, existing_spacing, use_individual_spacing),
 
 
 
 
3183
  [record.get("zip_path") for record in next_records if record.get("zip_path")],
3184
  [record.get("gcode_path") for record in next_records if record.get("gcode_path")],
3185
  _gcode_dropdown_update(next_records),
@@ -3219,6 +3241,7 @@ def generate_dynamic_gcode(
3219
  all_g1: bool,
3220
  use_reference_motion: bool,
3221
  raster_pattern: str | None,
 
3222
  lead_in_enabled: bool,
3223
  lead_in_length: float,
3224
  lead_in_clearance: float,
@@ -3252,6 +3275,7 @@ def generate_dynamic_gcode(
3252
  port=int(record.get("port", 1)),
3253
  layer_height=float(layer_height),
3254
  fil_width=float(pixel_size),
 
3255
  all_g1=bool(all_g1),
3256
  motion_tiffs=motion_tiffs,
3257
  raster_pattern=raster_pattern,
@@ -3653,6 +3677,7 @@ def build_dynamic_demo() -> gr.Blocks:
3653
  value=True,
3654
  )
3655
  gcode_all_g1 = gr.Checkbox(label="Move at one constant speed (no fast travel moves)", value=True)
 
3656
  gcode_raster_pattern = gr.Dropdown(
3657
  label="Raster Pattern",
3658
  choices=list(RASTER_PATTERN_CHOICES),
@@ -3942,6 +3967,10 @@ def build_dynamic_demo() -> gr.Blocks:
3942
  shape_records,
3943
  shape_settings,
3944
  nozzle_spacing_table,
 
 
 
 
3945
  tiff_downloads,
3946
  gcode_downloads,
3947
  gcode_text_source,
@@ -3978,6 +4007,7 @@ def build_dynamic_demo() -> gr.Blocks:
3978
  gcode_all_g1,
3979
  gcode_use_ref_motion,
3980
  gcode_raster_pattern,
 
3981
  gcode_lead_in_enabled,
3982
  gcode_lead_in_length,
3983
  gcode_lead_in_clearance,
@@ -3987,6 +4017,10 @@ def build_dynamic_demo() -> gr.Blocks:
3987
  pixel_size,
3988
  ],
3989
  outputs=[shape_records, gcode_downloads, gcode_status, gcode_text_source, gcode_source],
 
 
 
 
3990
  )
3991
  gcode_text_source.change(fn=load_selected_gcode_text, inputs=[shape_records, gcode_text_source], outputs=[gcode_text])
3992
  refresh_gcode_text_button.click(fn=load_selected_gcode_text, inputs=[shape_records, gcode_text_source], outputs=[gcode_text])
 
2928
  return gr.update(value=next_index), label, preview
2929
 
2930
 
2931
+ def _tiff_preview_update(records: list[dict], selected: str | None = None) -> tuple[dict[str, Any], dict[str, Any], str, Image.Image | None]:
2932
+ dropdown = _dropdown_update(records, selected)
2933
+ selected_value = dropdown.get("value") if isinstance(dropdown, dict) else selected
2934
+ pos = _selected_record_index(records, selected_value)
2935
+ state = (records[pos].get("tiff_state") if pos >= 0 else None) or _empty_state()
2936
+ label, preview = _render_selected_slice(state, 0)
2937
+ slider = gr.update(
2938
+ minimum=0,
2939
+ maximum=max(0, len(state.get("tiff_paths", [])) - 1),
2940
+ value=0,
2941
+ step=1,
2942
+ interactive=len(state.get("tiff_paths", [])) > 1,
2943
+ )
2944
+ return dropdown, slider, label, preview
2945
+
2946
+
2947
  def generate_dynamic_stacks(
2948
  records: list[dict] | None,
2949
  settings_table: Any,
 
3005
  messages.append("Reference TIFF Stack: updated automatically.")
3006
  else:
3007
  messages.append("Reference TIFF Stack: skipped (no generated shape slices available).")
3008
+ selected_update, slider, label, preview = _tiff_preview_update(records)
 
 
 
 
 
 
 
 
3009
  return (
3010
  records,
3011
  [record.get("zip_path") for record in records if record.get("zip_path")],
3012
  "\n".join(messages),
3013
+ selected_update,
3014
  slider,
3015
  label,
3016
  preview,
 
3097
  records,
3098
  _shape_settings_rows(records),
3099
  _spacing_table_update(records, existing_spacing, use_individual_spacing),
3100
+ _dropdown_update(records),
3101
+ _reset_slider(),
3102
+ "No slice stack loaded yet.",
3103
+ None,
3104
  [],
3105
  [],
3106
  _gcode_dropdown_update(records),
 
3135
  records,
3136
  _shape_settings_rows(records),
3137
  _spacing_table_update(records, existing_spacing, use_individual_spacing),
3138
+ _dropdown_update(records, selected),
3139
+ _reset_slider(),
3140
+ "No slice stack loaded yet.",
3141
+ None,
3142
  [record.get("zip_path") for record in records if record.get("zip_path")],
3143
  [record.get("gcode_path") for record in records if record.get("gcode_path")],
3144
  _gcode_dropdown_update(records),
 
3184
  })
3185
  split_records.append(piece_record)
3186
  next_records = _reindex_shape_records([*records[:pos], *split_records, *records[pos + 1:]])
3187
+ split_selected = _shape_choice(next_records[pos]) if pos < len(next_records) else None
3188
+ selected_update, main_slider, main_label, main_preview = _tiff_preview_update(next_records, split_selected)
3189
  slider, label, preview = preview_selected_split_piece(pieces, None)
3190
  status = (
3191
  f"Split Shape {source.get('idx', pos + 1)} into {len(pieces)} print-ready stacks "
 
3198
  next_records,
3199
  _shape_settings_rows(next_records),
3200
  _spacing_table_update(next_records, existing_spacing, use_individual_spacing),
3201
+ selected_update,
3202
+ main_slider,
3203
+ main_label,
3204
+ main_preview,
3205
  [record.get("zip_path") for record in next_records if record.get("zip_path")],
3206
  [record.get("gcode_path") for record in next_records if record.get("gcode_path")],
3207
  _gcode_dropdown_update(next_records),
 
3241
  all_g1: bool,
3242
  use_reference_motion: bool,
3243
  raster_pattern: str | None,
3244
+ pressure_ramp_enabled: bool,
3245
  lead_in_enabled: bool,
3246
  lead_in_length: float,
3247
  lead_in_clearance: float,
 
3275
  port=int(record.get("port", 1)),
3276
  layer_height=float(layer_height),
3277
  fil_width=float(pixel_size),
3278
+ pressure_ramp_enabled=bool(pressure_ramp_enabled),
3279
  all_g1=bool(all_g1),
3280
  motion_tiffs=motion_tiffs,
3281
  raster_pattern=raster_pattern,
 
3677
  value=True,
3678
  )
3679
  gcode_all_g1 = gr.Checkbox(label="Move at one constant speed (no fast travel moves)", value=True)
3680
+ gcode_pressure_ramp_enabled = gr.Checkbox(label="Increase Pressure Each Layer", value=True)
3681
  gcode_raster_pattern = gr.Dropdown(
3682
  label="Raster Pattern",
3683
  choices=list(RASTER_PATTERN_CHOICES),
 
3967
  shape_records,
3968
  shape_settings,
3969
  nozzle_spacing_table,
3970
+ selected_shape,
3971
+ slice_slider,
3972
+ slice_label,
3973
+ slice_preview,
3974
  tiff_downloads,
3975
  gcode_downloads,
3976
  gcode_text_source,
 
4007
  gcode_all_g1,
4008
  gcode_use_ref_motion,
4009
  gcode_raster_pattern,
4010
+ gcode_pressure_ramp_enabled,
4011
  gcode_lead_in_enabled,
4012
  gcode_lead_in_length,
4013
  gcode_lead_in_clearance,
 
4017
  pixel_size,
4018
  ],
4019
  outputs=[shape_records, gcode_downloads, gcode_status, gcode_text_source, gcode_source],
4020
+ ).then(
4021
+ fn=load_selected_gcode_text,
4022
+ inputs=[shape_records, gcode_text_source],
4023
+ outputs=[gcode_text],
4024
  )
4025
  gcode_text_source.change(fn=load_selected_gcode_text, inputs=[shape_records, gcode_text_source], outputs=[gcode_text])
4026
  refresh_gcode_text_button.click(fn=load_selected_gcode_text, inputs=[shape_records, gcode_text_source], outputs=[gcode_text])
tests/test_tiff_to_gcode.py CHANGED
@@ -69,6 +69,10 @@ def _moves_with_colors(gcode_text: str) -> list[dict]:
69
  return moves
70
 
71
 
 
 
 
 
72
  def test_trace_mask_contours_uses_tiff_pixel_border_edges() -> None:
73
  contours = _trace_mask_contours(
74
  np.array(
@@ -535,8 +539,8 @@ def test_gcode_header_writes_presets_before_initial_aux_commands(tmp_path) -> No
535
 
536
  assert lines[0] == "G91"
537
  assert lines[1] == "{aux_command}WAGO_ValveCommands(7, 0)"
538
- assert lines[2].startswith("{preset}serialPort3.write(")
539
- assert lines[3].startswith("{preset}serialPort3.write(")
540
  assert lines[4].startswith("{aux_command}WAGO_ValveCommands(")
541
  assert lines[5].startswith("{aux_command}WAGO_ValveCommands(")
542
 
@@ -585,6 +589,41 @@ def test_gcode_lead_in_runs_once_before_first_layer(tmp_path) -> None:
585
  assert not any(move["start"][0] < -3.0 or move["end"][0] < -3.0 for move in moves[first_z_index:])
586
 
587
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
588
  def test_gcode_uses_g1_for_print_and_g0_for_travel(tmp_path) -> None:
589
  tiff_path = tmp_path / "slice_0000.tif"
590
  Image.new("L", (1, 1), 0).save(tiff_path)
 
69
  return moves
70
 
71
 
72
+ def _pressure_set_count(gcode_text: str) -> int:
73
+ return gcode_text.count("\\x30\\x38\\x50\\x53") + gcode_text.count("setpress(")
74
+
75
+
76
  def test_trace_mask_contours_uses_tiff_pixel_border_edges() -> None:
77
  contours = _trace_mask_contours(
78
  np.array(
 
539
 
540
  assert lines[0] == "G91"
541
  assert lines[1] == "{aux_command}WAGO_ValveCommands(7, 0)"
542
+ assert lines[2] == "serialPort3.write(eval(setpress(25)))"
543
+ assert lines[3] == "serialPort3.write(eval(togglepress()))"
544
  assert lines[4].startswith("{aux_command}WAGO_ValveCommands(")
545
  assert lines[5].startswith("{aux_command}WAGO_ValveCommands(")
546
 
 
589
  assert not any(move["start"][0] < -3.0 or move["end"][0] < -3.0 for move in moves[first_z_index:])
590
 
591
 
592
+ def test_gcode_pressure_ramp_can_be_disabled(tmp_path) -> None:
593
+ tiff_paths = []
594
+ for index in range(2):
595
+ tiff_path = tmp_path / f"slice_{index:04d}.tif"
596
+ Image.new("L", (1, 1), 0).save(tiff_path)
597
+ tiff_paths.append(tiff_path)
598
+
599
+ zip_path = tmp_path / "slices.zip"
600
+ with zipfile.ZipFile(zip_path, mode="w") as archive:
601
+ for tiff_path in tiff_paths:
602
+ archive.write(tiff_path, arcname=tiff_path.name)
603
+
604
+ ramped_path = generate_snake_path_gcode(
605
+ zip_path,
606
+ shape_name="pressure_ramped",
607
+ pressure=25,
608
+ valve=7,
609
+ port=3,
610
+ layer_height=1.0,
611
+ pressure_ramp_enabled=True,
612
+ )
613
+ fixed_path = generate_snake_path_gcode(
614
+ zip_path,
615
+ shape_name="pressure_fixed",
616
+ pressure=25,
617
+ valve=7,
618
+ port=3,
619
+ layer_height=1.0,
620
+ pressure_ramp_enabled=False,
621
+ )
622
+
623
+ assert _pressure_set_count(ramped_path.read_text()) > 1
624
+ assert _pressure_set_count(fixed_path.read_text()) == 1
625
+
626
+
627
  def test_gcode_uses_g1_for_print_and_g0_for_travel(tmp_path) -> None:
628
  tiff_path = tmp_path / "slice_0000.tif"
629
  Image.new("L", (1, 1), 0).save(tiff_path)
tiff_to_gcode.py CHANGED
@@ -66,12 +66,16 @@ def _togglepress() -> str:
66
 
67
 
68
  def _setpress_cmd(port: str, pressure: float, start: bool) -> str:
69
- insert = "{preset}" if start else ""
 
 
70
  return f"\n\r{insert}{port}.write({_setpress(pressure)})"
71
 
72
 
73
  def _toggle_cmd(port: str, start: bool) -> str:
74
- insert = "{preset}" if start else ""
 
 
75
  return f"\n\r{insert}{port}.write({_togglepress()})"
76
 
77
 
@@ -1050,6 +1054,7 @@ def generate_snake_path_gcode(
1050
  fil_width: float = 0.8,
1051
  invert: bool = True,
1052
  increase_pressure_per_layer: float = 0.1,
 
1053
  all_g1: bool = False,
1054
  motion_tiffs: list[str] | None = None,
1055
  raster_pattern: str | None = RASTER_PATTERN_SAME_DIRECTION,
@@ -1301,8 +1306,11 @@ def generate_snake_path_gcode(
1301
  f"{move_type} X{move['X']} Y{move['Y']} Z{move['Z']} "
1302
  f"; Color {move['Color']}"
1303
  )
1304
- pressure_cur += increase_pressure_per_layer
1305
- pressure_next = _setpress_cmd(com_port, pressure_cur, start=False)
 
 
 
1306
  else:
1307
  line = (
1308
  f"{move_type} X{move['X']} Y{move['Y']} ; Color {move['Color']}"
 
66
 
67
 
68
  def _setpress_cmd(port: str, pressure: float, start: bool) -> str:
69
+ if start:
70
+ return f"\n\r{port}.write(eval(setpress({pressure:g})))"
71
+ insert = ""
72
  return f"\n\r{insert}{port}.write({_setpress(pressure)})"
73
 
74
 
75
  def _toggle_cmd(port: str, start: bool) -> str:
76
+ if start:
77
+ return f"\n\r{port}.write(eval(togglepress()))"
78
+ insert = ""
79
  return f"\n\r{insert}{port}.write({_togglepress()})"
80
 
81
 
 
1054
  fil_width: float = 0.8,
1055
  invert: bool = True,
1056
  increase_pressure_per_layer: float = 0.1,
1057
+ pressure_ramp_enabled: bool = True,
1058
  all_g1: bool = False,
1059
  motion_tiffs: list[str] | None = None,
1060
  raster_pattern: str | None = RASTER_PATTERN_SAME_DIRECTION,
 
1306
  f"{move_type} X{move['X']} Y{move['Y']} Z{move['Z']} "
1307
  f"; Color {move['Color']}"
1308
  )
1309
+ if pressure_ramp_enabled:
1310
+ pressure_cur += increase_pressure_per_layer
1311
+ pressure_next = _setpress_cmd(com_port, pressure_cur, start=False)
1312
+ else:
1313
+ pressure_next = None
1314
  else:
1315
  line = (
1316
  f"{move_type} X{move['X']} Y{move['Y']} ; Color {move['Color']}"