Spaces:
Sleeping
Sleeping
Commit ·
57b46fb
1
Parent(s): 71c658a
Committing previous changes to README and other files
Browse files- README.md +37 -5
- gcode_viewer.py +1 -1
- tests/test_gcode_viewer.py +18 -0
- tests/test_tiff_to_gcode.py +27 -0
- tiff_to_gcode.py +1 -1
README.md
CHANGED
|
@@ -1,16 +1,22 @@
|
|
| 1 |
---
|
| 2 |
-
title: STL to
|
| 3 |
sdk: gradio
|
| 4 |
sdk_version: 6.10.0
|
| 5 |
python_version: "3.12"
|
| 6 |
app_file: app.py
|
| 7 |
fullWidth: true
|
| 8 |
-
short_description: Upload
|
| 9 |
---
|
| 10 |
|
| 11 |
-
# STL to
|
| 12 |
|
| 13 |
-
This project provides a Gradio app that takes up to three uploaded STL files, shows interactive 3D viewers, slices each model along the Z axis, saves slices as TIFF images, and lets you
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
## Run
|
| 16 |
|
|
@@ -27,17 +33,22 @@ uv run gradio app.py
|
|
| 27 |
|
| 28 |
When `app.py` changes, Gradio will automatically rerun the file and refresh the demo.
|
| 29 |
|
| 30 |
-
Then open the local Gradio URL in your browser, upload
|
| 31 |
|
| 32 |
## What the app does
|
| 33 |
|
| 34 |
- Uploads up to three `.stl` files
|
|
|
|
| 35 |
- Shows interactive 3D viewers for rotating each model
|
|
|
|
| 36 |
- Lets you choose layer height and XY pixel size
|
| 37 |
- Produces one `.tif` image per slice
|
| 38 |
- Encodes material as black (`0`) and empty space as white (`255`) in each TIFF slice
|
| 39 |
- Lets you step through the slice stack in the browser
|
| 40 |
- Exports a ZIP containing the generated TIFF images
|
|
|
|
|
|
|
|
|
|
| 41 |
|
| 42 |
## Behavior and Implementation Notes
|
| 43 |
|
|
@@ -55,6 +66,27 @@ When you click **Generate Reference TIFF Stack**, the app combines available TIF
|
|
| 55 |
|
| 56 |
- 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()`.
|
| 57 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
## Test
|
| 59 |
|
| 60 |
```powershell
|
|
|
|
| 1 |
---
|
| 2 |
+
title: STL to G-Code Slicer
|
| 3 |
sdk: gradio
|
| 4 |
sdk_version: 6.10.0
|
| 5 |
python_version: "3.12"
|
| 6 |
app_file: app.py
|
| 7 |
fullWidth: true
|
| 8 |
+
short_description: Upload STLs, export TIFF stacks, and generate G-code.
|
| 9 |
---
|
| 10 |
|
| 11 |
+
# STL to G-Code Gradio App
|
| 12 |
|
| 13 |
+
This project provides a Gradio app that takes up to three uploaded STL files, shows interactive 3D viewers, slices each model along the Z axis, saves slices as TIFF images, generates G-code from those TIFF stacks, and lets you preview the resulting tool path.
|
| 14 |
+
|
| 15 |
+
## Prerequisites
|
| 16 |
+
|
| 17 |
+
- Python 3.11 or newer for local development
|
| 18 |
+
- `uv` for dependency management and script execution
|
| 19 |
+
- Git LFS for the bundled `.stl` sample files
|
| 20 |
|
| 21 |
## Run
|
| 22 |
|
|
|
|
| 33 |
|
| 34 |
When `app.py` changes, Gradio will automatically rerun the file and refresh the demo.
|
| 35 |
|
| 36 |
+
Then open the local Gradio URL in your browser, upload STL files or load the bundled samples, and generate the TIFF stacks.
|
| 37 |
|
| 38 |
## What the app does
|
| 39 |
|
| 40 |
- Uploads up to three `.stl` files
|
| 41 |
+
- Loads bundled sample STL files
|
| 42 |
- Shows interactive 3D viewers for rotating each model
|
| 43 |
+
- Shows model extents, face count, vertex count, and watertight status
|
| 44 |
- Lets you choose layer height and XY pixel size
|
| 45 |
- Produces one `.tif` image per slice
|
| 46 |
- Encodes material as black (`0`) and empty space as white (`255`) in each TIFF slice
|
| 47 |
- Lets you step through the slice stack in the browser
|
| 48 |
- Exports a ZIP containing the generated TIFF images
|
| 49 |
+
- Combines generated stacks into a reference TIFF stack
|
| 50 |
+
- Converts generated TIFF ZIPs into G-code files with pressure, valve, and port settings per shape
|
| 51 |
+
- Visualizes generated or uploaded G-code tool paths
|
| 52 |
|
| 53 |
## Behavior and Implementation Notes
|
| 54 |
|
|
|
|
| 66 |
|
| 67 |
- 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()`.
|
| 68 |
|
| 69 |
+
### G-code Output
|
| 70 |
+
|
| 71 |
+
- Generated G-code starts in relative coordinate mode (`G91`).
|
| 72 |
+
- `G0` is travel and `G1` is print/feed.
|
| 73 |
+
- The app generates print/feed moves from material pixels and travel moves between material regions.
|
| 74 |
+
- Generated files include pressure preset commands and WAGO valve commands based on the selected pressure, valve, and port.
|
| 75 |
+
- Pressure increases by `0.1` psi per layer by default.
|
| 76 |
+
|
| 77 |
+
### G-code Visualization
|
| 78 |
+
|
| 79 |
+
The G-code visualization tab can render either the generated Shape 1 G-code or an uploaded `.txt`, `.gcode`, or `.nc` file. It parses `G0` and `G1` movement lines, supports relative (`G91`) and absolute (`G90`) positioning, and displays travel and print segments with separate colors and opacity controls.
|
| 80 |
+
|
| 81 |
+
## Dependency Updates
|
| 82 |
+
|
| 83 |
+
When dependencies change, update the lockfile and refresh the Hugging Face `requirements.txt` export:
|
| 84 |
+
|
| 85 |
+
```powershell
|
| 86 |
+
uv sync --all-groups
|
| 87 |
+
uv export --format requirements.txt --no-hashes --no-dev --frozen --output-file requirements.txt
|
| 88 |
+
```
|
| 89 |
+
|
| 90 |
## Test
|
| 91 |
|
| 92 |
```powershell
|
gcode_viewer.py
CHANGED
|
@@ -70,7 +70,7 @@ def parse_gcode_path(gcode_text: str) -> dict:
|
|
| 70 |
if dz_match is not None:
|
| 71 |
z = dz
|
| 72 |
|
| 73 |
-
kind = "
|
| 74 |
|
| 75 |
if kind != current_kind:
|
| 76 |
flush_segment()
|
|
|
|
| 70 |
if dz_match is not None:
|
| 71 |
z = dz
|
| 72 |
|
| 73 |
+
kind = "print" if gcmd == "G1" else "travel"
|
| 74 |
|
| 75 |
if kind != current_kind:
|
| 76 |
flush_segment()
|
tests/test_gcode_viewer.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from gcode_viewer import parse_gcode_path
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def test_parse_gcode_classifies_g0_as_travel_and_g1_as_print() -> None:
|
| 7 |
+
parsed = parse_gcode_path(
|
| 8 |
+
"\n".join(
|
| 9 |
+
[
|
| 10 |
+
"G91",
|
| 11 |
+
"G0 X1 Y0 ; travel",
|
| 12 |
+
"G1 X0 Y1 ; print",
|
| 13 |
+
]
|
| 14 |
+
)
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
assert len(parsed["travel_segments"]) == 1
|
| 18 |
+
assert len(parsed["print_segments"]) == 1
|
tests/test_tiff_to_gcode.py
CHANGED
|
@@ -34,3 +34,30 @@ def test_gcode_header_writes_presets_before_initial_aux_commands(tmp_path) -> No
|
|
| 34 |
assert lines[2].startswith("{preset}serialPort3.write(")
|
| 35 |
assert lines[3].startswith("{aux_command}WAGO_ValveCommands(")
|
| 36 |
assert lines[4].startswith("{aux_command}WAGO_ValveCommands(")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
assert lines[2].startswith("{preset}serialPort3.write(")
|
| 35 |
assert lines[3].startswith("{aux_command}WAGO_ValveCommands(")
|
| 36 |
assert lines[4].startswith("{aux_command}WAGO_ValveCommands(")
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def test_gcode_uses_g1_for_print_and_g0_for_travel(tmp_path) -> None:
|
| 40 |
+
tiff_path = tmp_path / "slice_0000.tif"
|
| 41 |
+
Image.new("L", (1, 1), 0).save(tiff_path)
|
| 42 |
+
|
| 43 |
+
zip_path = tmp_path / "slices.zip"
|
| 44 |
+
with zipfile.ZipFile(zip_path, mode="w") as archive:
|
| 45 |
+
archive.write(tiff_path, arcname=tiff_path.name)
|
| 46 |
+
|
| 47 |
+
gcode_path = generate_snake_path_gcode(
|
| 48 |
+
zip_path,
|
| 49 |
+
shape_name="move_types",
|
| 50 |
+
pressure=25,
|
| 51 |
+
valve=7,
|
| 52 |
+
port=3,
|
| 53 |
+
)
|
| 54 |
+
|
| 55 |
+
move_lines = [
|
| 56 |
+
line.strip()
|
| 57 |
+
for line in gcode_path.read_text().splitlines()
|
| 58 |
+
if line.startswith(("G0", "G1"))
|
| 59 |
+
]
|
| 60 |
+
|
| 61 |
+
assert any(line.startswith("G1") and "; Color 255" in line for line in move_lines)
|
| 62 |
+
assert all(not line.startswith("G0") for line in move_lines if "; Color 255" in line)
|
| 63 |
+
assert all(not line.startswith("G1") for line in move_lines if "; Color 0" in line)
|
tiff_to_gcode.py
CHANGED
|
@@ -306,7 +306,7 @@ def generate_snake_path_gcode(
|
|
| 306 |
f.write(_valve_cmd(color_dict[cur_color], 1))
|
| 307 |
f.write(_valve_cmd(color_dict[prev_color], 0))
|
| 308 |
|
| 309 |
-
move_type = "
|
| 310 |
if "Z" in move:
|
| 311 |
line = (
|
| 312 |
f"{move_type} X{move['X']} Y{move['Y']} Z{move['Z']} "
|
|
|
|
| 306 |
f.write(_valve_cmd(color_dict[cur_color], 1))
|
| 307 |
f.write(_valve_cmd(color_dict[prev_color], 0))
|
| 308 |
|
| 309 |
+
move_type = "G1" if cur_color != off_color else "G0"
|
| 310 |
if "Z" in move:
|
| 311 |
line = (
|
| 312 |
f"{move_type} X{move['X']} Y{move['Y']} Z{move['Z']} "
|