MichaelRKessler commited on
Commit
92bb652
·
1 Parent(s): 04b33e7

Lots of updates (x-y origin, formatting, combined TIFFS)

Browse files
Files changed (2) hide show
  1. README.md +4 -0
  2. app.py +42 -24
README.md CHANGED
@@ -31,6 +31,10 @@ Then open the local Gradio URL in your browser, upload an STL file, and generate
31
  - Lets you step through the slice stack in the browser
32
  - Exports a ZIP containing the generated TIFF images
33
 
 
 
 
 
34
  ## Test
35
 
36
  ```powershell
 
31
  - Lets you step through the slice stack in the browser
32
  - Exports a ZIP containing the generated TIFF images
33
 
34
+ ## Notes
35
+
36
+ - G-code generation uses the slicer's `Pixel Size/Fill Width` for XY step distance by passing `fil_width=pixel_size` into `generate_snake_path_gcode()`.
37
+
38
  ## Test
39
 
40
  ```powershell
app.py CHANGED
@@ -18,7 +18,7 @@ from tiff_to_gcode import generate_snake_path_gcode
18
  ViewerState = dict[str, Any]
19
  SAMPLE_STL_FILENAMES = ("Hollow_Pyramid.stl", "Rounded_Cube_Through_Holes.stl", "halfsphere.stl")
20
  SAMPLE_STL_DIR = Path(__file__).resolve().parent / "sample_stls"
21
- FRONT_CAMERA = (270, 80, None)
22
  APP_CSS = """
23
  .gradio-container {
24
  font-size: 90%;
@@ -360,17 +360,30 @@ def _opacity_to_alpha(opacity: float) -> int:
360
  return int(round(255 * bounded))
361
 
362
 
363
- def _build_annotated_scene(mesh: trimesh.Trimesh, opacity: float = 0.96) -> str:
 
 
 
 
 
 
 
 
 
 
 
 
364
  """Export a GLB containing the mesh, origin axes, and a Z=0 grid plane."""
365
  scene = trimesh.Scene()
366
  display_transform = trimesh.transformations.rotation_matrix(-np.pi / 2, [1, 0, 0])
367
 
368
- # --- Model (mostly opaque medium grey so it stands off the background) ---
369
  model_copy = mesh.copy()
370
  model_copy.apply_transform(display_transform)
 
371
  mat = trimesh.visual.material.PBRMaterial(
372
- baseColorFactor=[160, 160, 170, _opacity_to_alpha(opacity)],
373
- alphaMode="BLEND",
374
  metallicFactor=0.0,
375
  roughnessFactor=0.6,
376
  )
@@ -476,23 +489,24 @@ def _build_annotated_scene(mesh: trimesh.Trimesh, opacity: float = 0.96) -> str:
476
  return str(out_path)
477
 
478
 
479
- def load_single_model(stl_file: str | None, opacity: float = 0.96) -> tuple[str | None, str]:
480
  if not stl_file:
481
- return None, "No model loaded."
482
  mesh = load_mesh(stl_file)
483
- glb_path = _build_annotated_scene(mesh, opacity=opacity)
484
- return glb_path, _format_model_details(Path(stl_file).name, mesh)
485
 
486
 
487
- def preload_sample_models(opacity: float = 0.96) -> tuple:
488
  outputs: list[Any] = []
 
489
 
490
  for filename in SAMPLE_STL_FILENAMES:
491
  stl_path = SAMPLE_STL_DIR / filename
492
  if not stl_path.exists():
493
  outputs.extend([
494
  None,
495
- None,
496
  f"Sample file not found: {stl_path}",
497
  ])
498
  continue
@@ -502,14 +516,14 @@ def preload_sample_models(opacity: float = 0.96) -> tuple:
502
  except Exception as exc:
503
  outputs.extend([
504
  str(stl_path),
505
- None,
506
  f"Failed to load sample model: {stl_path.name} ({exc})",
507
  ])
508
  continue
509
 
510
  outputs.extend([
511
  str(stl_path),
512
- _build_annotated_scene(mesh, opacity=opacity),
513
  _format_model_details(stl_path.name, mesh),
514
  ])
515
 
@@ -523,11 +537,12 @@ def refresh_all_model_viewers(
523
  opacity: float,
524
  ) -> tuple:
525
  outputs: list[Any] = []
 
526
  for stl_file in (stl1, stl2, stl3):
527
  if not stl_file:
528
- outputs.extend([None, "No model loaded."])
529
  continue
530
- outputs.extend(load_single_model(stl_file, opacity))
531
  return tuple(outputs)
532
 
533
 
@@ -848,6 +863,11 @@ def build_demo() -> gr.Blocks:
848
  min_width=140,
849
  scale=0,
850
  )
 
 
 
 
 
851
 
852
  # --- Upload + 3D viewer row ---
853
  stl_files: list[gr.File] = []
@@ -876,15 +896,13 @@ def build_demo() -> gr.Blocks:
876
 
877
  # --- Shared slicing controls ---
878
  with gr.Row():
879
- model_opacity = gr.Slider(
880
- label="3D Model Opacity",
881
- minimum=0.2,
882
- maximum=1.0,
883
- value=0.96,
884
- step=0.05,
885
- )
886
  layer_height = gr.Number(label="Layer Height", value=0.8, minimum=0.0001, step=0.01)
887
- pixel_size = gr.Number(label="Pixel Size/Fill Width", value=0.8, minimum=0.0001, step=0.01)
 
 
 
 
 
888
  generate_button = gr.Button("Generate TIFF Stacks", variant="primary")
889
 
890
  # --- Per-object slice browsers ---
@@ -1030,7 +1048,7 @@ def build_demo() -> gr.Blocks:
1030
  for i in range(3):
1031
  refresh_outputs.extend([model_viewers[i], model_details_list[i]])
1032
 
1033
- model_opacity.release(
1034
  fn=refresh_all_model_viewers,
1035
  inputs=[stl_files[0], stl_files[1], stl_files[2], model_opacity],
1036
  outputs=refresh_outputs,
 
18
  ViewerState = dict[str, Any]
19
  SAMPLE_STL_FILENAMES = ("Hollow_Pyramid.stl", "Rounded_Cube_Through_Holes.stl", "halfsphere.stl")
20
  SAMPLE_STL_DIR = Path(__file__).resolve().parent / "sample_stls"
21
+ FRONT_CAMERA = (90, 80, None)
22
  APP_CSS = """
23
  .gradio-container {
24
  font-size: 90%;
 
360
  return int(round(255 * bounded))
361
 
362
 
363
+ def _resolve_model_opacity(setting: float | bool | None) -> float:
364
+ if isinstance(setting, bool):
365
+ return 0.75 if setting else 1.0
366
+ if setting is None:
367
+ return 1.0
368
+ return max(0.05, min(float(setting), 1.0))
369
+
370
+
371
+ def _viewer_update(model_path: str | None) -> dict[str, Any]:
372
+ return gr.update(value=model_path, camera_position=FRONT_CAMERA)
373
+
374
+
375
+ def _build_annotated_scene(mesh: trimesh.Trimesh, opacity: float = 1.0) -> str:
376
  """Export a GLB containing the mesh, origin axes, and a Z=0 grid plane."""
377
  scene = trimesh.Scene()
378
  display_transform = trimesh.transformations.rotation_matrix(-np.pi / 2, [1, 0, 0])
379
 
380
+ # --- Model (muted orange to match the Gradio theme accent) ---
381
  model_copy = mesh.copy()
382
  model_copy.apply_transform(display_transform)
383
+ bounded_opacity = _resolve_model_opacity(opacity)
384
  mat = trimesh.visual.material.PBRMaterial(
385
+ baseColorFactor=[230, 150, 90, _opacity_to_alpha(bounded_opacity)],
386
+ alphaMode="OPAQUE" if bounded_opacity >= 0.999 else "BLEND",
387
  metallicFactor=0.0,
388
  roughnessFactor=0.6,
389
  )
 
489
  return str(out_path)
490
 
491
 
492
+ def load_single_model(stl_file: str | None, opacity: float = 1.0) -> tuple[str | None, str]:
493
  if not stl_file:
494
+ return _viewer_update(None), "No model loaded."
495
  mesh = load_mesh(stl_file)
496
+ glb_path = _build_annotated_scene(mesh, opacity=_resolve_model_opacity(opacity))
497
+ return _viewer_update(glb_path), _format_model_details(Path(stl_file).name, mesh)
498
 
499
 
500
+ def preload_sample_models(opacity: float = 1.0) -> tuple:
501
  outputs: list[Any] = []
502
+ resolved_opacity = _resolve_model_opacity(opacity)
503
 
504
  for filename in SAMPLE_STL_FILENAMES:
505
  stl_path = SAMPLE_STL_DIR / filename
506
  if not stl_path.exists():
507
  outputs.extend([
508
  None,
509
+ _viewer_update(None),
510
  f"Sample file not found: {stl_path}",
511
  ])
512
  continue
 
516
  except Exception as exc:
517
  outputs.extend([
518
  str(stl_path),
519
+ _viewer_update(None),
520
  f"Failed to load sample model: {stl_path.name} ({exc})",
521
  ])
522
  continue
523
 
524
  outputs.extend([
525
  str(stl_path),
526
+ _viewer_update(_build_annotated_scene(mesh, opacity=resolved_opacity)),
527
  _format_model_details(stl_path.name, mesh),
528
  ])
529
 
 
537
  opacity: float,
538
  ) -> tuple:
539
  outputs: list[Any] = []
540
+ resolved_opacity = _resolve_model_opacity(opacity)
541
  for stl_file in (stl1, stl2, stl3):
542
  if not stl_file:
543
+ outputs.extend([_viewer_update(None), "No model loaded."])
544
  continue
545
+ outputs.extend(load_single_model(stl_file, resolved_opacity))
546
  return tuple(outputs)
547
 
548
 
 
863
  min_width=140,
864
  scale=0,
865
  )
866
+ with gr.Column(scale=0, min_width=240):
867
+ model_opacity = gr.Checkbox(
868
+ label="Use 75% 3D Model Opacity",
869
+ value=False,
870
+ )
871
 
872
  # --- Upload + 3D viewer row ---
873
  stl_files: list[gr.File] = []
 
896
 
897
  # --- Shared slicing controls ---
898
  with gr.Row():
 
 
 
 
 
 
 
899
  layer_height = gr.Number(label="Layer Height", value=0.8, minimum=0.0001, step=0.01)
900
+ pixel_size = gr.Number(
901
+ label="Pixel Size/Fill Width",
902
+ value=0.8,
903
+ minimum=0.0001,
904
+ step=0.01,
905
+ )
906
  generate_button = gr.Button("Generate TIFF Stacks", variant="primary")
907
 
908
  # --- Per-object slice browsers ---
 
1048
  for i in range(3):
1049
  refresh_outputs.extend([model_viewers[i], model_details_list[i]])
1050
 
1051
+ model_opacity.change(
1052
  fn=refresh_all_model_viewers,
1053
  inputs=[stl_files[0], stl_files[1], stl_files[2], model_opacity],
1054
  outputs=refresh_outputs,