Spaces:
Running
Merge slicing into one tab and one button: Generate G-Code slices, Split slices
Browse files- The Shapes & Slicing and Generate G-Code tabs are one "Shapes & G-Code"
tab: upload, table, split and preview accordions, then the generation
options, button, downloads, and G-code preview in one flow (the app is
now two tabs: Shapes & G-Code, Visualization)
- The Slice Shapes button is gone. Generate G-Code slices every shape
from the current table settings before writing G-code, and Split
Selected Shape into Grid Pieces slices before splitting (same shared
Z-grid group plans), so the whole workflow is one button - two with a
split. The layer preview and split-source refreshes moved from the old
slice chain onto the generate and split chains
- Status texts that pointed at the removed button/tabs reworded; README
updated
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- README.md +5 -5
- app.py +45 -35
- tests/test_nozzle_spacing.py +6 -0
|
@@ -33,7 +33,7 @@ uv run gradio app.py
|
|
| 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
|
| 37 |
|
| 38 |
## What the app does
|
| 39 |
|
|
@@ -54,7 +54,7 @@ Then open the local Gradio URL in your browser, upload STL files or load the bun
|
|
| 54 |
- **Infill %** per shape skips dispensing on evenly-distributed raster lines (rings/revolutions for spiral patterns) — at 50% every other line prints — while the motion path stays exactly the same, so parallel shapes with different infill still share one print path
|
| 55 |
- Appends a shape outline contour after each enabled shape layer by tracing that layer's polygon boundary
|
| 56 |
- Offers a choice of raster pattern for G-code generation; all shapes always share one combined reference outline for motion (one nozzle path; each dispenses only its own geometry), and every move is emitted as `G1` at one constant speed (no `G0` rapid travel)
|
| 57 |
-
-
|
| 58 |
- Calculates X/Y nozzle spacing from a grid layout (columns/rows plus gaps) in the Visualization tab's Nozzle Spacing accordion, with an optional per-connection Advanced Grid Spacing table, then visualizes the resulting nozzle layout
|
| 59 |
- Previews selected generated G-code inline
|
| 60 |
- One Visualization tab that defaults to the parallel print of every generated shape (configured nozzle spacing, animated, with a server-side GIF export), and can switch to a single tool path from any generated shape or an uploaded G-code file
|
|
@@ -69,7 +69,7 @@ Slicing uses `trimesh` cross-sections composed into shapely polygons (`slice_stl
|
|
| 69 |
|
| 70 |
### Reference Layer Union
|
| 71 |
|
| 72 |
-
|
| 73 |
|
| 74 |
- Shapes are aligned by centering each shape's XY bounding box on a common center before the union.
|
| 75 |
- Alignment is centered placement (in exact millimetres), not bottom-left anchoring.
|
|
@@ -89,11 +89,11 @@ For a multi-material object exported as separate STLs (one per material), give e
|
|
| 89 |
|
| 90 |
### Multi-Nozzle Split
|
| 91 |
|
| 92 |
-
The **Multi-Nozzle Split** accordion on the **Shapes &
|
| 93 |
|
| 94 |
- Each layer's geometry is clipped against equal-size grid cells, so every piece keeps exact vector outlines.
|
| 95 |
- The selected shape is replaced in Shape Settings by one generated record per grid cell, named by row and column.
|
| 96 |
-
- Nozzle and valve numbers are assigned sequentially from the starting values, and
|
| 97 |
- **Overlapping Layers** alternates the interior cut lines by one filament width per layer so neighbouring pieces interlock.
|
| 98 |
- **Multi-material assemblies split as one shape**: if the selected shape shares its nozzle with other shapes, the whole group is split together — every material is clipped by the same cell grid over the group's combined bounds. Pieces are emitted cell by cell: each cell's pieces share a nozzle (so every cell is itself a multi-material group, keeping the alignment and seam-free contour behavior), each piece gets its own valve, and cells where a material has no geometry are skipped. Auto Align works on the result like any other split.
|
| 99 |
|
|
|
|
| 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 press **Generate G-Code**.
|
| 37 |
|
| 38 |
## What the app does
|
| 39 |
|
|
|
|
| 54 |
- **Infill %** per shape skips dispensing on evenly-distributed raster lines (rings/revolutions for spiral patterns) — at 50% every other line prints — while the motion path stays exactly the same, so parallel shapes with different infill still share one print path
|
| 55 |
- Appends a shape outline contour after each enabled shape layer by tracing that layer's polygon boundary
|
| 56 |
- Offers a choice of raster pattern for G-code generation; all shapes always share one combined reference outline for motion (one nozzle path; each dispenses only its own geometry), and every move is emitted as `G1` at one constant speed (no `G0` rapid travel)
|
| 57 |
+
- Slicing is automatic: **Generate G-Code** slices every shape (fresh or stale) before writing G-code, and **Split Selected Shape into Grid Pieces** slices before splitting — "upload, then Generate G-Code" works in one click with no separate slice step
|
| 58 |
- Calculates X/Y nozzle spacing from a grid layout (columns/rows plus gaps) in the Visualization tab's Nozzle Spacing accordion, with an optional per-connection Advanced Grid Spacing table, then visualizes the resulting nozzle layout
|
| 59 |
- Previews selected generated G-code inline
|
| 60 |
- One Visualization tab that defaults to the parallel print of every generated shape (configured nozzle spacing, animated, with a server-side GIF export), and can switch to a single tool path from any generated shape or an uploaded G-code file
|
|
|
|
| 69 |
|
| 70 |
### Reference Layer Union
|
| 71 |
|
| 72 |
+
Whenever shapes are sliced (automatically, during G-code generation or a split), the app unions the sliced shapes layer-by-layer into a combined reference layer set (used for shared-motion G-code).
|
| 73 |
|
| 74 |
- Shapes are aligned by centering each shape's XY bounding box on a common center before the union.
|
| 75 |
- Alignment is centered placement (in exact millimetres), not bottom-left anchoring.
|
|
|
|
| 89 |
|
| 90 |
### Multi-Nozzle Split
|
| 91 |
|
| 92 |
+
The **Multi-Nozzle Split** accordion on the **Shapes & G-Code** tab can split one shape into a grid of print-ready piece stacks. Choose a source shape, set the number of columns and rows, choose the starting nozzle and valve numbers, then click **Split Selected Shape into Grid Pieces** — the shapes are sliced automatically first if needed.
|
| 93 |
|
| 94 |
- Each layer's geometry is clipped against equal-size grid cells, so every piece keeps exact vector outlines.
|
| 95 |
- The selected shape is replaced in Shape Settings by one generated record per grid cell, named by row and column.
|
| 96 |
+
- Nozzle and valve numbers are assigned sequentially from the starting values, and **Generate G-Code** produces separate G-code for each piece.
|
| 97 |
- **Overlapping Layers** alternates the interior cut lines by one filament width per layer so neighbouring pieces interlock.
|
| 98 |
- **Multi-material assemblies split as one shape**: if the selected shape shares its nozzle with other shapes, the whole group is split together — every material is clipped by the same cell grid over the group's combined bounds. Pieces are emitted cell by cell: each cell's pieces share a nozzle (so every cell is itself a multi-material group, keeping the alignment and seam-free contour behavior), each piece gets its own valve, and cells where a material has no geometry are skipped. Auto Align works on the result like any other split.
|
| 99 |
|
|
@@ -2947,7 +2947,7 @@ def update_layer_preview(
|
|
| 2947 |
if stack is None or not getattr(stack, "layers", None):
|
| 2948 |
return (
|
| 2949 |
gr.update(maximum=1, value=1),
|
| 2950 |
-
_layer_preview_message("
|
| 2951 |
)
|
| 2952 |
|
| 2953 |
layer_count = len(stack.layers)
|
|
@@ -3291,9 +3291,10 @@ def generate_dynamic_reference_stack(
|
|
| 3291 |
|
| 3292 |
|
| 3293 |
SPLIT_STATUS_DEFAULT = (
|
| 3294 |
-
"
|
| 3295 |
-
"
|
| 3296 |
-
"splits the **whole
|
|
|
|
| 3297 |
)
|
| 3298 |
|
| 3299 |
|
|
@@ -3353,8 +3354,8 @@ def _split_group_records(
|
|
| 3353 |
return _outputs(
|
| 3354 |
records,
|
| 3355 |
selected,
|
| 3356 |
-
f"Split failed: shape(s) on nozzle {group_nozzle}
|
| 3357 |
-
"
|
| 3358 |
)
|
| 3359 |
stacks = [member["layer_stack"] for member in group_members]
|
| 3360 |
layer_counts = {len(stack.layers) for stack in stacks}
|
|
@@ -3364,7 +3365,7 @@ def _split_group_records(
|
|
| 3364 |
records,
|
| 3365 |
selected,
|
| 3366 |
f"Split failed: the shapes on nozzle {group_nozzle} were sliced on different "
|
| 3367 |
-
"Z grids -
|
| 3368 |
)
|
| 3369 |
|
| 3370 |
# Stamp group frames + seam-free contours so pieces inherit contour
|
|
@@ -3468,10 +3469,20 @@ def split_selected_shape_for_grid(
|
|
| 3468 |
starting_nozzle: float,
|
| 3469 |
starting_valve: float,
|
| 3470 |
fil_width: float,
|
|
|
|
|
|
|
| 3471 |
) -> tuple:
|
| 3472 |
records = _apply_shape_settings(records or [], settings_table)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3473 |
|
| 3474 |
def _outputs(next_records: list[dict], selected_value: str | None, status: str) -> tuple:
|
|
|
|
|
|
|
| 3475 |
return (
|
| 3476 |
next_records,
|
| 3477 |
_shape_settings_rows(next_records),
|
|
@@ -3485,7 +3496,7 @@ def split_selected_shape_for_grid(
|
|
| 3485 |
)
|
| 3486 |
|
| 3487 |
if not records:
|
| 3488 |
-
return _outputs(records, None, "
|
| 3489 |
|
| 3490 |
pos = _selected_record_index(records, selected)
|
| 3491 |
if pos < 0:
|
|
@@ -3496,7 +3507,7 @@ def split_selected_shape_for_grid(
|
|
| 3496 |
return _outputs(
|
| 3497 |
records,
|
| 3498 |
selected,
|
| 3499 |
-
f"Split failed: Shape {source.get('idx', pos + 1)}
|
| 3500 |
)
|
| 3501 |
|
| 3502 |
split_column_count = max(1, _coerce_int(columns, 2))
|
|
@@ -3781,7 +3792,7 @@ def _parts_from_records(records: list[dict] | None) -> tuple[list[dict], list[st
|
|
| 3781 |
path = record.get("gcode_path")
|
| 3782 |
idx = int(record.get("idx", len(parts) + 1))
|
| 3783 |
if not path:
|
| 3784 |
-
messages.append(f"Shape {idx}: no G-code (
|
| 3785 |
continue
|
| 3786 |
try:
|
| 3787 |
parsed = parse_gcode_path(Path(path).read_text())
|
|
@@ -3907,7 +3918,7 @@ def render_dynamic_parallel(
|
|
| 3907 |
records = _apply_shape_settings(records or [], settings_table)
|
| 3908 |
parts, messages = _parts_from_records(records)
|
| 3909 |
if not parts:
|
| 3910 |
-
return None, "No shape G-code available. Generate G-
|
| 3911 |
offsets, spacings = _resolve_nozzle_grid_layout(
|
| 3912 |
parts,
|
| 3913 |
columns,
|
|
@@ -3998,11 +4009,11 @@ def build_dynamic_demo() -> gr.Blocks:
|
|
| 3998 |
last_shape_delete_at = gr.State(0.0)
|
| 3999 |
ref_layers = gr.State(None)
|
| 4000 |
|
| 4001 |
-
with gr.Tab("Shapes &
|
| 4002 |
gr.Markdown(
|
| 4003 |
"""
|
| 4004 |
-
# Shapes &
|
| 4005 |
-
Upload any number of STL files, edit per-shape dimensions and print settings in the table, then
|
| 4006 |
Pick each shape's plot color straight from the dropdown in the **Color** column.
|
| 4007 |
"""
|
| 4008 |
)
|
|
@@ -4068,7 +4079,6 @@ def build_dynamic_demo() -> gr.Blocks:
|
|
| 4068 |
with gr.Row():
|
| 4069 |
layer_height = gr.Number(label="Layer Height (mm)", value=0.8, minimum=0.0001, step=0.01)
|
| 4070 |
fil_width = gr.Number(label="Filament/Line Width (mm)", value=0.8, minimum=0.0001, step=0.01)
|
| 4071 |
-
generate_button = gr.Button("Slice Shapes", variant="primary")
|
| 4072 |
|
| 4073 |
with gr.Accordion("Multi-Nozzle Split", open=False, elem_classes=["settings-accordion"]):
|
| 4074 |
with gr.Row():
|
|
@@ -4109,13 +4119,11 @@ def build_dynamic_demo() -> gr.Blocks:
|
|
| 4109 |
with gr.Column(scale=1, min_width=300):
|
| 4110 |
model_details = gr.Markdown("No model loaded.")
|
| 4111 |
|
| 4112 |
-
slicer_status = gr.Markdown("")
|
| 4113 |
-
|
| 4114 |
-
with gr.Tab("Generate G-Code"):
|
| 4115 |
gr.Markdown(
|
| 4116 |
"""
|
| 4117 |
-
|
| 4118 |
-
Generate G-
|
|
|
|
| 4119 |
All shapes share one combined nozzle path (each dispenses only its own geometry), so parallel heads always stay in sync.
|
| 4120 |
"""
|
| 4121 |
)
|
|
@@ -4380,21 +4388,6 @@ def build_dynamic_demo() -> gr.Blocks:
|
|
| 4380 |
outputs=[model_viewer, model_details],
|
| 4381 |
)
|
| 4382 |
|
| 4383 |
-
generate_button.click(
|
| 4384 |
-
fn=generate_dynamic_layer_stacks,
|
| 4385 |
-
inputs=[shape_records, shape_settings, layer_height, scale_mode, fil_width],
|
| 4386 |
-
outputs=[shape_records, slicer_status, ref_layers],
|
| 4387 |
-
).then(
|
| 4388 |
-
fn=update_layer_preview,
|
| 4389 |
-
inputs=layer_preview_inputs,
|
| 4390 |
-
outputs=layer_preview_outputs,
|
| 4391 |
-
).then(
|
| 4392 |
-
fn=lambda records: _dropdown_update(records),
|
| 4393 |
-
inputs=[shape_records],
|
| 4394 |
-
outputs=[split_source],
|
| 4395 |
-
queue=False,
|
| 4396 |
-
)
|
| 4397 |
-
|
| 4398 |
split_refresh_sources.click(fn=lambda records: _dropdown_update(records), inputs=[shape_records], outputs=[split_source], queue=False)
|
| 4399 |
# .input (user selections only): programmatic dropdown refills after a
|
| 4400 |
# split must not overwrite the split result message.
|
|
@@ -4416,6 +4409,8 @@ def build_dynamic_demo() -> gr.Blocks:
|
|
| 4416 |
split_start_nozzle,
|
| 4417 |
split_start_valve,
|
| 4418 |
fil_width,
|
|
|
|
|
|
|
| 4419 |
],
|
| 4420 |
outputs=[
|
| 4421 |
shape_records,
|
|
@@ -4432,6 +4427,10 @@ def build_dynamic_demo() -> gr.Blocks:
|
|
| 4432 |
fn=generate_dynamic_reference_stack,
|
| 4433 |
inputs=[shape_records, fil_width],
|
| 4434 |
outputs=[ref_layers],
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4435 |
).then(
|
| 4436 |
fn=_grid_spacing_table_update,
|
| 4437 |
inputs=grid_spacing_refresh_inputs,
|
|
@@ -4459,6 +4458,17 @@ def build_dynamic_demo() -> gr.Blocks:
|
|
| 4459 |
fn=load_selected_gcode_text,
|
| 4460 |
inputs=[shape_records, gcode_text_source],
|
| 4461 |
outputs=[gcode_text],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4462 |
)
|
| 4463 |
gcode_text_source.change(fn=load_selected_gcode_text, inputs=[shape_records, gcode_text_source], outputs=[gcode_text])
|
| 4464 |
refresh_gcode_text_button.click(fn=load_selected_gcode_text, inputs=[shape_records, gcode_text_source], outputs=[gcode_text])
|
|
|
|
| 2947 |
if stack is None or not getattr(stack, "layers", None):
|
| 2948 |
return (
|
| 2949 |
gr.update(maximum=1, value=1),
|
| 2950 |
+
_layer_preview_message("Generate G-Code (or split) to slice this shape and see its layer outlines."),
|
| 2951 |
)
|
| 2952 |
|
| 2953 |
layer_count = len(stack.layers)
|
|
|
|
| 3291 |
|
| 3292 |
|
| 3293 |
SPLIT_STATUS_DEFAULT = (
|
| 3294 |
+
"Pick a shape to split for multi-nozzle printing - shapes are sliced "
|
| 3295 |
+
"automatically when you split. Shapes that share a nozzle number are one "
|
| 3296 |
+
"multi-material assembly: selecting any of them splits the **whole "
|
| 3297 |
+
"group** together as one shape."
|
| 3298 |
)
|
| 3299 |
|
| 3300 |
|
|
|
|
| 3354 |
return _outputs(
|
| 3355 |
records,
|
| 3356 |
selected,
|
| 3357 |
+
f"Split failed: shape(s) on nozzle {group_nozzle} could not be sliced - "
|
| 3358 |
+
"check their STLs.",
|
| 3359 |
)
|
| 3360 |
stacks = [member["layer_stack"] for member in group_members]
|
| 3361 |
layer_counts = {len(stack.layers) for stack in stacks}
|
|
|
|
| 3365 |
records,
|
| 3366 |
selected,
|
| 3367 |
f"Split failed: the shapes on nozzle {group_nozzle} were sliced on different "
|
| 3368 |
+
"Z grids - edit a dimension (or nozzle) so the group re-slices together.",
|
| 3369 |
)
|
| 3370 |
|
| 3371 |
# Stamp group frames + seam-free contours so pieces inherit contour
|
|
|
|
| 3469 |
starting_nozzle: float,
|
| 3470 |
starting_valve: float,
|
| 3471 |
fil_width: float,
|
| 3472 |
+
layer_height: float = 0.8,
|
| 3473 |
+
scale_mode: str | None = None,
|
| 3474 |
) -> tuple:
|
| 3475 |
records = _apply_shape_settings(records or [], settings_table)
|
| 3476 |
+
# One-button flow: shapes are sliced (or re-sliced when their settings
|
| 3477 |
+
# changed) straight from the table here, so splitting never needs a
|
| 3478 |
+
# separate slicing step.
|
| 3479 |
+
slice_messages: list[str] = []
|
| 3480 |
+
if records:
|
| 3481 |
+
_ensure_records_sliced(records, float(layer_height or 0.8), scale_mode, slice_messages)
|
| 3482 |
|
| 3483 |
def _outputs(next_records: list[dict], selected_value: str | None, status: str) -> tuple:
|
| 3484 |
+
if slice_messages:
|
| 3485 |
+
status = " \n".join([*slice_messages, status])
|
| 3486 |
return (
|
| 3487 |
next_records,
|
| 3488 |
_shape_settings_rows(next_records),
|
|
|
|
| 3496 |
)
|
| 3497 |
|
| 3498 |
if not records:
|
| 3499 |
+
return _outputs(records, None, "Add a shape before splitting it.")
|
| 3500 |
|
| 3501 |
pos = _selected_record_index(records, selected)
|
| 3502 |
if pos < 0:
|
|
|
|
| 3507 |
return _outputs(
|
| 3508 |
records,
|
| 3509 |
selected,
|
| 3510 |
+
f"Split failed: Shape {source.get('idx', pos + 1)} could not be sliced - check its STL.",
|
| 3511 |
)
|
| 3512 |
|
| 3513 |
split_column_count = max(1, _coerce_int(columns, 2))
|
|
|
|
| 3792 |
path = record.get("gcode_path")
|
| 3793 |
idx = int(record.get("idx", len(parts) + 1))
|
| 3794 |
if not path:
|
| 3795 |
+
messages.append(f"Shape {idx}: no G-code (press Generate G-Code on the Shapes & G-Code tab).")
|
| 3796 |
continue
|
| 3797 |
try:
|
| 3798 |
parsed = parse_gcode_path(Path(path).read_text())
|
|
|
|
| 3918 |
records = _apply_shape_settings(records or [], settings_table)
|
| 3919 |
parts, messages = _parts_from_records(records)
|
| 3920 |
if not parts:
|
| 3921 |
+
return None, "No shape G-code available. Press Generate G-Code on the Shapes & G-Code tab first."
|
| 3922 |
offsets, spacings = _resolve_nozzle_grid_layout(
|
| 3923 |
parts,
|
| 3924 |
columns,
|
|
|
|
| 4009 |
last_shape_delete_at = gr.State(0.0)
|
| 4010 |
ref_layers = gr.State(None)
|
| 4011 |
|
| 4012 |
+
with gr.Tab("Shapes & G-Code"):
|
| 4013 |
gr.Markdown(
|
| 4014 |
"""
|
| 4015 |
+
# Shapes & G-Code
|
| 4016 |
+
Upload any number of STL files, edit per-shape dimensions and print settings in the table, then press **Generate G-Code** below - every shape is sliced automatically as part of generation (and before a split).
|
| 4017 |
Pick each shape's plot color straight from the dropdown in the **Color** column.
|
| 4018 |
"""
|
| 4019 |
)
|
|
|
|
| 4079 |
with gr.Row():
|
| 4080 |
layer_height = gr.Number(label="Layer Height (mm)", value=0.8, minimum=0.0001, step=0.01)
|
| 4081 |
fil_width = gr.Number(label="Filament/Line Width (mm)", value=0.8, minimum=0.0001, step=0.01)
|
|
|
|
| 4082 |
|
| 4083 |
with gr.Accordion("Multi-Nozzle Split", open=False, elem_classes=["settings-accordion"]):
|
| 4084 |
with gr.Row():
|
|
|
|
| 4119 |
with gr.Column(scale=1, min_width=300):
|
| 4120 |
model_details = gr.Markdown("No model loaded.")
|
| 4121 |
|
|
|
|
|
|
|
|
|
|
| 4122 |
gr.Markdown(
|
| 4123 |
"""
|
| 4124 |
+
---
|
| 4125 |
+
### Generate G-Code
|
| 4126 |
+
One button slices every shape from the current table settings and writes its G-code. Pressure, valve, nozzle, port, and color come from the Shape Settings table.
|
| 4127 |
All shapes share one combined nozzle path (each dispenses only its own geometry), so parallel heads always stay in sync.
|
| 4128 |
"""
|
| 4129 |
)
|
|
|
|
| 4388 |
outputs=[model_viewer, model_details],
|
| 4389 |
)
|
| 4390 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4391 |
split_refresh_sources.click(fn=lambda records: _dropdown_update(records), inputs=[shape_records], outputs=[split_source], queue=False)
|
| 4392 |
# .input (user selections only): programmatic dropdown refills after a
|
| 4393 |
# split must not overwrite the split result message.
|
|
|
|
| 4409 |
split_start_nozzle,
|
| 4410 |
split_start_valve,
|
| 4411 |
fil_width,
|
| 4412 |
+
layer_height,
|
| 4413 |
+
scale_mode,
|
| 4414 |
],
|
| 4415 |
outputs=[
|
| 4416 |
shape_records,
|
|
|
|
| 4427 |
fn=generate_dynamic_reference_stack,
|
| 4428 |
inputs=[shape_records, fil_width],
|
| 4429 |
outputs=[ref_layers],
|
| 4430 |
+
).then(
|
| 4431 |
+
fn=update_layer_preview,
|
| 4432 |
+
inputs=layer_preview_inputs,
|
| 4433 |
+
outputs=layer_preview_outputs,
|
| 4434 |
).then(
|
| 4435 |
fn=_grid_spacing_table_update,
|
| 4436 |
inputs=grid_spacing_refresh_inputs,
|
|
|
|
| 4458 |
fn=load_selected_gcode_text,
|
| 4459 |
inputs=[shape_records, gcode_text_source],
|
| 4460 |
outputs=[gcode_text],
|
| 4461 |
+
).then(
|
| 4462 |
+
# Generation slices the shapes, so refresh everything that
|
| 4463 |
+
# depends on the sliced stacks (the Slice Shapes button is gone).
|
| 4464 |
+
fn=update_layer_preview,
|
| 4465 |
+
inputs=layer_preview_inputs,
|
| 4466 |
+
outputs=layer_preview_outputs,
|
| 4467 |
+
).then(
|
| 4468 |
+
fn=lambda records: _dropdown_update(records),
|
| 4469 |
+
inputs=[shape_records],
|
| 4470 |
+
outputs=[split_source],
|
| 4471 |
+
queue=False,
|
| 4472 |
)
|
| 4473 |
gcode_text_source.change(fn=load_selected_gcode_text, inputs=[shape_records, gcode_text_source], outputs=[gcode_text])
|
| 4474 |
refresh_gcode_text_button.click(fn=load_selected_gcode_text, inputs=[shape_records, gcode_text_source], outputs=[gcode_text])
|
|
@@ -766,6 +766,12 @@ def test_group_split_splits_all_materials_on_one_shared_grid() -> None:
|
|
| 766 |
_record(1, "bottom", _material(box(0.0, 0.0, 20.0, 5.0), "bottom")),
|
| 767 |
_record(2, "top", _material(box(0.0, 5.0, 20.0, 10.0), "top")),
|
| 768 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 769 |
|
| 770 |
outputs = split_selected_shape_for_grid(
|
| 771 |
records,
|
|
|
|
| 766 |
_record(1, "bottom", _material(box(0.0, 0.0, 20.0, 5.0), "bottom")),
|
| 767 |
_record(2, "top", _material(box(0.0, 5.0, 20.0, 10.0), "top")),
|
| 768 |
]
|
| 769 |
+
# Mark the stacks as freshly sliced for the split's auto-slice step (the
|
| 770 |
+
# fixture stl paths don't exist, so a re-slice attempt would wipe them).
|
| 771 |
+
from app import _slice_params_snapshot
|
| 772 |
+
|
| 773 |
+
for record in records:
|
| 774 |
+
record["slice_params"] = _slice_params_snapshot(record, 0.8, None, None)
|
| 775 |
|
| 776 |
outputs = split_selected_shape_for_grid(
|
| 777 |
records,
|