Skip to content

Sheet metal: cut-outs that meet nothing skip the flat booleans - #8707

Closed
NOWN-DEV wants to merge 1 commit into
tangency-labs:mainfrom
NOWN-DEV:upstream-pr/flat-free-cutouts
Closed

NOWN-DEV wants to merge 1 commit into
tangency-labs:mainfrom
NOWN-DEV:upstream-pr/flat-free-cutouts

Conversation

@NOWN-DEV

Copy link
Copy Markdown
Contributor

Change

Cutting many shapes through an unfolded sheet was refused with "sheet metal flat geometry exceeds the bounded processing budget". A 1200 × 600 tray with 96 logos of 32 edges cut through its flat was refused. The flat spent its budget in three places:

  • every cut-out went through one boolean with the material;
  • the material union was one boolean of every region;
  • faces() compared every edge pair.

Now (crates/doc/src/sheet.rs):

  • A cut-out whose box meets no other cut-out and no material edge, and whose outline is a plain closed loop, becomes a hole of the material directly, without a boolean. Anything else (overlapping, touching an edge, open loops) still goes through the booleans as before.
  • The material union clusters regions by box overlap with a sweep over sorted boxes, and unions each cluster on its own.
  • faces() finds corners and shared edges through a grid and an edge-start hash instead of scanning every pair.

Every step still charges the flat work budget, so hostile input stays bounded. box_slack is the box growth that loop_box already used inline, now shared.

In eval_more.rs (sheet_prism_cut), the work charged for reading a cut's regions is now counted per tool and text item instead of per face of the tools. A sketch of hundreds of logos is one tool with a face per edge, so the old count refused it before anything was cut.

Changes a default: no

Validation

New tests in sheet.rs:

  • cut_outs_apart_skip_the_booleans sorts cut-outs into free ones and the rest.
    • Free: wholly in the material, wholly in its hole, wholly outside.
    • Not free: across the outline, across the hole's edge, two that overlap, one with an island, and a loop open by 1 mm.
    • Only the free one in the material becomes a hole, and the area comes out exact. 2,000 cut-outs apart stay 2,000 regions within the work budget, and overlapping ones merge.
  • a_sheet_of_thousands_of_cut_outs_stays_within_its_budget puts 1,600 squares (6,400 edges) on a 1000 × 500 plate with a flange. The case was refused before. Now the flat loses exactly their area, has 1,600 holes, and has a wall face for every edge.

Timing, from a generic repro made with public commands (repro_gen.py below):

  1. A 1200 × 600 base with four 38 mm flanges, unfolded.
  2. A grid of cut-outs is extruded through the flat. Each cut-out is a wavy 32-edge star about 22 mm across, or a circle when NSEG is 0.
  3. The four sides are folded.
  4. inspect.measure runs.

python3 repro_gen.py NX NY NSEG out.json, then solidcraft-cli run out.json --results --quiet. Release solidcraft-cli from main ae47ed3 and the same with this change, Linux x86_64 with 4 cores, load 1.7–3.2, wall time:

Case main this PR
200 round holes (20 10 0), 3 alternated runs 3.28 s (3.22–3.42) 3.08 s (3.00–3.25)
96 logos (8 12 32), 3 alternated runs refused at the cut 2.81 s (2.80–2.93)
216 logos (12 18 32) refused at the cut 10.6 s

The 200-hole tray, which main builds, comes out the same. It has the same 246 faces, 500 edges and 456 vertices, and the same volume and area, which differ only in the 14th digit. A render of it from main and from this branch is the same pixel for pixel. On the logo trays, the measured volume is the plate's minus exactly n × the logo area × 1.5 mm. The faces are 46 + 32 per logo (3,118 and 6,958).

What this PR does not lift: round holes count by their chords in the flat budget, so 800 round holes (40 20 0) are still refused with or without this change. Lifting that touches the planar boolean's check in crates/kernel, which waits for the kernel decision.

Checks on the head c6a9fa7:

  • cargo fmt --all --check: clean.
  • cargo clippy -p solidcraft-doc -p solidcraft-engine --all-targets -- -D warnings: clean.
  • cargo test -p solidcraft-doc --lib: 485 passed, 1 ignored.
  • cargo test -p solidcraft-engine --lib -- cmd::sheet: 210 passed, 1 ignored.
  • cargo test -p solidcraft-engine --lib naming: 53 passed.
repro_gen.py
import json, math, sys
nx, ny, nseg = int(sys.argv[1]), int(sys.argv[2]), int(sys.argv[3])
out = sys.argv[4]
W, H, L = 1200.0, 600.0, 38.0
cmds = [
 {"command":"sheet.manage_rules","params":{"name":"Al 1.5","thickness":"1.5","k_factor":"0.44","bend_radius":"1.5","active":True}},
 {"command":"sketch.create","params":{"plane":"XY","name":"Blank"}},
 {"command":"sketch.line","params":{"points":[[0,-L],[W,-L],[W,0],[W+L,0],[W+L,H],[W,H],[W,H+L],[0,H+L],[0,H],[-L,H],[-L,0],[0,0]],"closed":True}},
 {"command":"sketch.finish","params":{}},
 {"command":"sheet.flange","params":{"sketch":"Blank"}},
]
# logos: a wavy star of nseg segments, about 22 mm across
pts_logo = []
for k in range(nseg):
    a = 2*math.pi*k/nseg
    r = 9 + 2.5*math.sin(5*a) + 0.8*math.sin(13*a)
    pts_logo.append((r*math.cos(a), r*math.sin(a)))
cmds.append({"command":"sketch.create","params":{"plane":"XY","name":"Logos"}})
px, py = (W-60)/(nx-1), (H-60)/(ny-1)
for i in range(nx):
    for j in range(ny):
        cx, cy = 30+i*px, 30+j*py
        if nseg == 0:
            cmds.append({"command":"sketch.circle.center","params":{"center":[round(cx,4),round(cy,4)],"diameter":min(px,py)*0.5}})
        else:
            cmds.append({"command":"sketch.line","params":{"points":[[round(cx+x,4),round(cy+y,4)] for x,y in pts_logo],"closed":True}})
cmds.append({"command":"sketch.finish","params":{}})
cmds.append({"command":"solid.extrude","params":{"sketch":"Logos","through_all":True,"operation":"cut","direction":"symmetric"}})
e=2
for name, p in [("b",[[e,-2,1.5],[W-e,-2,1.5]]),("t",[[e,H+2,1.5],[W-e,H+2,1.5]]),("l",[[-2,e,1.5],[-2,H-e,1.5]]),("r",[[W+2,e,1.5],[W+2,H-e,1.5]])]:
    cmds.append({"command":"sheet.fold","params":{"points":p,"fixed":[W/2,H/2,1.5]}})
cmds.append({"command":"inspect.measure","params":{}})
json.dump({"commands":cmds}, open(out,"w"))
print(len(cmds))

Screenshots of the behavior (when observable)

The 96-logo tray (8 12 32) built with this change. On main, the cut is refused (step 104 (solid.extrude): sheet metal flat geometry exceeds the bounded processing budget) and there is no tray to show.

96 logos cut through the flat of a 1200 x 600 tray, folded

Integration status

One commit, c6a9fa7, on main ae47ed3 (checked 2026-10-29 06:18 UTC). There are no conflicts, and no open PR changes these flat functions; I looked at #8397, #8346 and #8326, the open sheet-metal PRs.


Generated by Claude Code

A sheet with hundreds of cut-outs was refused by the flat budget: every
cut-out went through one boolean with the material, the material union
ran as one boolean of every region, and faces() compared every edge pair.
A tray with 96 logos of 32 edges cut through its flat was refused.

A cut-out whose box meets no other cut-out and no material edge, and whose
outline is a plain closed loop, now becomes a hole directly. The material
union clusters regions by box overlap (a sweep over sorted boxes) and
unions each cluster on its own. faces() finds corners and shared edges
through a grid and an edge-start hash instead of scanning all pairs. The
cut's work for reading its regions is charged per tool, not per face of
the tools: a sketch of hundreds of logos is one tool with a face per edge.
Every step still charges the flat budget, so hostile inputs stay bounded.

Measured on a 1200x600 tray with four folds (release build): 96 logos of
32 edges cut in the flat now build in 2.8 s and 216 in 10.6 s; both were
refused before. 200 round holes build as before (3.2 s), with the same
faces, edges and volume.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added area:doc timeline, components, parameters, naming kernel-paused labels Oct 29, 2026
NOWN-DEV added a commit to NOWN-DEV/solidcraft that referenced this pull request Oct 30, 2026
NOWN-DEV added a commit to NOWN-DEV/solidcraft that referenced this pull request Oct 31, 2026
@NOWN-DEV

Copy link
Copy Markdown
Contributor Author

Superseded by #10356: the same change, plus the shortcut for a flat cut's tool clear of the panel outline with its engine test, and checks that keep degenerate cut-outs on the booleans. Closing this one in its favour.

@NOWN-DEV NOWN-DEV closed this Oct 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:doc timeline, components, parameters, naming

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant