feat: enforce Delaunay vertices at inset boundary corners + update geometry to v2.0 with arcs

- Add explicit corner vertices of the inset boundary (w_frame offset) to Delaunay point set
- This guarantees no triangle can cross a boundary corner
- Updated test_data geometry files to v2.0 format with typed segments
- Sandbox 2 now has proper arc curves (4 arc segments) from extract_sandbox
- Preserved holes from v1.0 geometry
- Boundary vertices also enforced on keepout boundaries
This commit is contained in:
2026-02-17 13:41:24 +00:00
parent 856ff239d6
commit 18a8347765
3 changed files with 3720 additions and 2382 deletions

View File

@@ -154,29 +154,55 @@ def _add_boundary_vertices(points, geometry, params, keepout_union):
This ensures triangles conform to boundaries rather than just being This ensures triangles conform to boundaries rather than just being
clipped. Points are spaced at approximately s_min along boundaries. clipped. Points are spaced at approximately s_min along boundaries.
KEY: Enforce explicit vertices at every corner of the inset boundary.
This guarantees no triangle can cross a corner — the Delaunay triangulation
is forced to use these corner points as vertices.
""" """
s_min = params['s_min'] s_min = params['s_min']
w_frame = params.get('w_frame', 8.0) w_frame = params.get('w_frame', 8.0)
new_pts = list(points) new_pts = list(points)
# Add points along inset outer boundary (frame inner edge)
plate_poly = Polygon(geometry['outer_boundary']) plate_poly = Polygon(geometry['outer_boundary'])
inner_frame = plate_poly.buffer(-w_frame) inner_frame = plate_poly.buffer(-w_frame)
if not inner_frame.is_empty and inner_frame.geom_type == 'Polygon': if not inner_frame.is_empty:
ring = inner_frame.exterior # Handle MultiPolygon from buffer on complex shapes
length = ring.length if inner_frame.geom_type == 'MultiPolygon':
n_pts = max(int(length / s_min), 4) inner_polys = list(inner_frame.geoms)
for i in range(n_pts): else:
frac = i / n_pts inner_polys = [inner_frame]
pt = ring.interpolate(frac, normalized=True)
new_pts.append([pt.x, pt.y]) for inner_poly in inner_polys:
ring = inner_poly.exterior
# 1) ENFORCE corner vertices: add every vertex of the inset boundary
# These are the actual corner points — critical for preventing crossovers
coords = list(ring.coords)[:-1] # skip closing duplicate
for cx, cy in coords:
new_pts.append([cx, cy])
# 2) Add evenly spaced points along edges for density
length = ring.length
n_pts = max(int(length / s_min), 4)
for i in range(n_pts):
frac = i / n_pts
pt = ring.interpolate(frac, normalized=True)
new_pts.append([pt.x, pt.y])
# Also add inner ring vertices (for any holes in the inset boundary)
for interior in inner_poly.interiors:
for cx, cy in list(interior.coords)[:-1]:
new_pts.append([cx, cy])
# Add points along hole keepout boundaries # Add points along hole keepout boundaries
if not keepout_union.is_empty: if not keepout_union.is_empty:
geoms = [keepout_union] if keepout_union.geom_type == 'Polygon' else list(keepout_union.geoms) geoms = [keepout_union] if keepout_union.geom_type == 'Polygon' else list(keepout_union.geoms)
for geom in geoms: for geom in geoms:
ring = geom.exterior ring = geom.exterior
# Enforce corner vertices on keepout boundaries too
for cx, cy in list(ring.coords)[:-1]:
new_pts.append([cx, cy])
length = ring.length length = ring.length
n_pts = max(int(length / (s_min * 0.7)), 6) n_pts = max(int(length / (s_min * 0.7)), 6)
for i in range(n_pts): for i in range(n_pts):

View File

@@ -1,4 +1,7 @@
{ {
"schema_version": "2.0",
"units": "mm",
"sandbox_id": "sandbox_1",
"outer_boundary": [ "outer_boundary": [
[ [
381.787159, 381.787159,
@@ -121,6 +124,773 @@
0.02177 0.02177
] ]
], ],
"outer_boundary_typed": [
{
"type": "line",
"start": [
381.787159,
14.92177
],
"end": [
132.687159,
14.92177
]
},
{
"type": "line",
"start": [
132.687159,
14.92177
],
"end": [
132.687159,
-13.57823
]
},
{
"type": "line",
"start": [
132.687159,
-13.57823
],
"end": [
88.687159,
-13.57823
]
},
{
"type": "line",
"start": [
88.687159,
-13.57823
],
"end": [
88.687159,
14.92177
]
},
{
"type": "line",
"start": [
88.687159,
14.92177
],
"end": [
-13.412841,
14.92177
]
},
{
"type": "line",
"start": [
-13.412841,
14.92177
],
"end": [
-13.412841,
0.02177
]
},
{
"type": "line",
"start": [
-13.412841,
0.02177
],
"end": [
-30.812841,
0.02177
]
},
{
"type": "line",
"start": [
-30.812841,
0.02177
],
"end": [
-30.812841,
-254.17823
]
},
{
"type": "line",
"start": [
-30.812841,
-254.17823
],
"end": [
169.435852,
-254.17823
]
},
{
"type": "line",
"start": [
169.435852,
-254.17823
],
"end": [
169.435852,
-417.57823
]
},
{
"type": "line",
"start": [
169.435852,
-417.57823
],
"end": [
197.121675,
-417.57823
]
},
{
"type": "line",
"start": [
197.121675,
-417.57823
],
"end": [
197.121675,
-401.57823
]
},
{
"type": "line",
"start": [
197.121675,
-401.57823
],
"end": [
212.121675,
-401.57823
]
},
{
"type": "line",
"start": [
212.121675,
-401.57823
],
"end": [
212.121675,
-417.57823
]
},
{
"type": "line",
"start": [
212.121675,
-417.57823
],
"end": [
289.687159,
-417.57823
]
},
{
"type": "line",
"start": [
289.687159,
-417.57823
],
"end": [
304.687159,
-406.57823
]
},
{
"type": "line",
"start": [
304.687159,
-406.57823
],
"end": [
317.687159,
-406.57823
]
},
{
"type": "line",
"start": [
317.687159,
-406.57823
],
"end": [
332.687159,
-417.57823
]
},
{
"type": "line",
"start": [
332.687159,
-417.57823
],
"end": [
381.787159,
-417.57823
]
},
{
"type": "line",
"start": [
381.787159,
-417.57823
],
"end": [
381.787159,
-395.17823
]
},
{
"type": "line",
"start": [
381.787159,
-395.17823
],
"end": [
404.187159,
-395.17823
]
},
{
"type": "line",
"start": [
404.187159,
-395.17823
],
"end": [
404.187159,
-322.57823
]
},
{
"type": "line",
"start": [
404.187159,
-322.57823
],
"end": [
352.787159,
-322.57823
]
},
{
"type": "line",
"start": [
352.787159,
-322.57823
],
"end": [
352.787159,
-304.17823
]
},
{
"type": "line",
"start": [
352.787159,
-304.17823
],
"end": [
361.187159,
-304.17823
]
},
{
"type": "line",
"start": [
361.187159,
-304.17823
],
"end": [
361.187159,
-24.57823
]
},
{
"type": "line",
"start": [
361.187159,
-24.57823
],
"end": [
404.187159,
-24.57823
]
},
{
"type": "line",
"start": [
404.187159,
-24.57823
],
"end": [
404.187159,
0.02177
]
},
{
"type": "line",
"start": [
404.187159,
0.02177
],
"end": [
381.787159,
0.02177
]
},
{
"type": "line",
"start": [
381.787159,
0.02177
],
"end": [
381.787159,
14.92177
]
}
],
"inner_boundaries": [
{
"index": 0,
"segments": [
{
"type": "arc",
"start": [
0.0,
0.0
],
"end": [
0.0,
0.0
],
"center": [
0.0,
-3.07823
],
"radius": 3.07823,
"mid": [
0.0,
-6.15646
],
"clockwise": false
}
],
"num_segments": 1
},
{
"index": 1,
"segments": [
{
"type": "arc",
"start": [
366.187159,
1.02177
],
"end": [
366.187159,
1.02177
],
"center": [
366.187159,
-3.07823
],
"radius": 4.1,
"mid": [
366.187159,
-7.17823
],
"clockwise": false
}
],
"num_segments": 1
},
{
"index": 2,
"segments": [
{
"type": "arc",
"start": [
44.987159,
0.0
],
"end": [
44.987159,
0.0
],
"center": [
44.987159,
-3.07823
],
"radius": 3.07823,
"mid": [
44.987159,
-6.15646
],
"clockwise": false
}
],
"num_segments": 1
},
{
"index": 3,
"segments": [
{
"type": "arc",
"start": [
250.707159,
-272.32823
],
"end": [
250.707159,
-272.32823
],
"center": [
250.707159,
-275.57823
],
"radius": 3.25,
"mid": [
250.707159,
-278.82823
],
"clockwise": false
}
],
"num_segments": 1
},
{
"index": 4,
"segments": [
{
"type": "arc",
"start": [
44.987159,
-155.5
],
"end": [
44.987159,
-155.5
],
"center": [
44.987159,
-158.57823
],
"radius": 3.07823,
"mid": [
44.987159,
-161.65646
],
"clockwise": false
}
],
"num_segments": 1
},
{
"index": 5,
"segments": [
{
"type": "arc",
"start": [
125.187159,
-232.47823
],
"end": [
125.187159,
-232.47823
],
"center": [
125.187159,
-236.57823
],
"radius": 4.1,
"mid": [
125.187159,
-240.67823
],
"clockwise": false
}
],
"num_segments": 1
},
{
"index": 6,
"segments": [
{
"type": "arc",
"start": [
-9.812841,
-67.82823
],
"end": [
-9.812841,
-67.82823
],
"center": [
-9.812841,
-71.07823
],
"radius": 3.25,
"mid": [
-9.812841,
-74.32823
],
"clockwise": false
}
],
"num_segments": 1
},
{
"index": 7,
"segments": [
{
"type": "arc",
"start": [
362.787159,
-372.9
],
"end": [
362.787159,
-372.9
],
"center": [
362.787159,
-375.97823
],
"radius": 3.07823,
"mid": [
362.787159,
-379.05646
],
"clockwise": false
}
],
"num_segments": 1
},
{
"index": 8,
"segments": [
{
"type": "arc",
"start": [
250.707159,
-372.72823
],
"end": [
250.707159,
-372.72823
],
"center": [
250.707159,
-375.97823
],
"radius": 3.25,
"mid": [
250.707159,
-379.22823
],
"clockwise": false
}
],
"num_segments": 1
},
{
"index": 9,
"segments": [
{
"type": "arc",
"start": [
291.457159,
-311.1
],
"end": [
291.457159,
-311.1
],
"center": [
291.457159,
-314.17823
],
"radius": 3.07823,
"mid": [
291.457159,
-317.25646
],
"clockwise": false
}
],
"num_segments": 1
},
{
"index": 10,
"segments": [
{
"type": "arc",
"start": [
44.987159,
-68.0
],
"end": [
44.987159,
-68.0
],
"center": [
44.987159,
-71.07823
],
"radius": 3.07823,
"mid": [
44.987159,
-74.15646
],
"clockwise": false
}
],
"num_segments": 1
},
{
"index": 11,
"segments": [
{
"type": "arc",
"start": [
194.447159,
-372.72823
],
"end": [
194.447159,
-372.72823
],
"center": [
194.447159,
-375.97823
],
"radius": 3.25,
"mid": [
194.447159,
-379.22823
],
"clockwise": false
}
],
"num_segments": 1
},
{
"index": 12,
"segments": [
{
"type": "arc",
"start": [
291.457159,
-372.9
],
"end": [
291.457159,
-372.9
],
"center": [
291.457159,
-375.97823
],
"radius": 3.07823,
"mid": [
291.457159,
-379.05646
],
"clockwise": false
}
],
"num_segments": 1
},
{
"index": 13,
"segments": [
{
"type": "arc",
"start": [
125.187159,
-154.47823
],
"end": [
125.187159,
-154.47823
],
"center": [
125.187159,
-158.57823
],
"radius": 4.1,
"mid": [
125.187159,
-162.67823
],
"clockwise": false
}
],
"num_segments": 1
},
{
"index": 14,
"segments": [
{
"type": "arc",
"start": [
125.187159,
-66.97823
],
"end": [
125.187159,
-66.97823
],
"center": [
125.187159,
-71.07823
],
"radius": 4.1,
"mid": [
125.187159,
-75.17823
],
"clockwise": false
}
],
"num_segments": 1
},
{
"index": 15,
"segments": [
{
"type": "arc",
"start": [
194.447159,
-272.32823
],
"end": [
194.447159,
-272.32823
],
"center": [
194.447159,
-275.57823
],
"radius": 3.25,
"mid": [
194.447159,
-278.82823
],
"clockwise": false
}
],
"num_segments": 1
}
],
"num_inner_boundaries": 16,
"holes": [ "holes": [
{ {
"index": 0, "index": 0,
@@ -2363,5 +3133,27 @@
"weight": 0.5 "weight": 0.5
} }
], ],
"thickness": null "thickness": null,
"transform": {
"origin": [
197.57823,
184.187159,
6.35
],
"x_axis": [
0.0,
-1.0,
0.0
],
"y_axis": [
1.0,
0.0,
-0.0
],
"normal": [
0.0,
0.0,
1.0
]
}
} }

View File

@@ -1,5 +1,5 @@
{ {
"schema_version": "1.0", "schema_version": "2.0",
"units": "mm", "units": "mm",
"sandbox_id": "sandbox_2", "sandbox_id": "sandbox_2",
"outer_boundary": [ "outer_boundary": [
@@ -7,6 +7,66 @@
0.0, 0.0,
0.0 0.0
], ],
[
0.735129,
-0.036115
],
[
1.463177,
-0.14411
],
[
2.177135,
-0.322947
],
[
2.870126,
-0.570904
],
[
3.535476,
-0.885591
],
[
4.166777,
-1.263978
],
[
4.75795,
-1.702422
],
[
5.303301,
-2.196699
],
[
5.797578,
-2.74205
],
[
6.236022,
-3.333223
],
[
6.614409,
-3.964524
],
[
6.929096,
-4.629874
],
[
7.177053,
-5.322865
],
[
7.35589,
-6.036823
],
[
7.463885,
-6.764871
],
[ [
7.5, 7.5,
-7.5 -7.5
@@ -39,6 +99,66 @@
102.5, 102.5,
-7.5 -7.5
], ],
[
102.64411,
-8.963177
],
[
103.070904,
-10.370126
],
[
103.763978,
-11.666777
],
[
104.696699,
-12.803301
],
[
105.833223,
-13.736022
],
[
107.129874,
-14.429096
],
[
108.536823,
-14.85589
],
[
110.0,
-15.0
],
[
111.463177,
-14.85589
],
[
112.870126,
-14.429096
],
[
114.166777,
-13.736022
],
[
115.303301,
-12.803301
],
[
116.236022,
-11.666777
],
[
116.929096,
-10.370126
],
[
117.35589,
-8.963177
],
[ [
117.5, 117.5,
-7.5 -7.5
@@ -63,6 +183,66 @@
117.5, 117.5,
102.5 102.5
], ],
[
117.35589,
101.036823
],
[
116.929096,
99.629874
],
[
116.236022,
98.333223
],
[
115.303301,
97.196699
],
[
114.166777,
96.263978
],
[
112.870126,
95.570904
],
[
111.463177,
95.14411
],
[
110.0,
95.0
],
[
108.536823,
95.14411
],
[
107.129874,
95.570904
],
[
105.833223,
96.263978
],
[
104.696699,
97.196699
],
[
103.763978,
98.333223
],
[
103.070904,
99.629874
],
[
102.64411,
101.036823
],
[ [
102.5, 102.5,
102.5 102.5
@@ -79,6 +259,66 @@
7.5, 7.5,
102.5 102.5
], ],
[
7.463885,
101.764871
],
[
7.35589,
101.036823
],
[
7.177053,
100.322865
],
[
6.929096,
99.629874
],
[
6.614409,
98.964524
],
[
6.236022,
98.333223
],
[
5.797578,
97.74205
],
[
5.303301,
97.196699
],
[
4.75795,
96.702422
],
[
4.166777,
96.263978
],
[
3.535476,
95.885591
],
[
2.870126,
95.570904
],
[
2.177135,
95.322947
],
[
1.463177,
95.14411
],
[
0.735129,
95.036115
],
[ [
0.0, 0.0,
95.0 95.0
@@ -90,14 +330,295 @@
[ [
-13.5, -13.5,
0.0 0.0
],
[
0.0,
0.0
] ]
], ],
"outer_boundary_typed": [
{
"type": "arc",
"start": [
0.0,
0.0
],
"end": [
7.5,
-7.5
],
"center": [
0.0,
-7.5
],
"radius": 7.5,
"mid": [
5.303301,
-2.196699
],
"clockwise": true
},
{
"type": "line",
"start": [
7.5,
-7.5
],
"end": [
7.5,
-22.6
]
},
{
"type": "line",
"start": [
7.5,
-22.6
],
"end": [
22.5,
-22.6
]
},
{
"type": "line",
"start": [
22.5,
-22.6
],
"end": [
22.5,
-13.496098
]
},
{
"type": "line",
"start": [
22.5,
-13.496098
],
"end": [
74.5,
-13.496098
]
},
{
"type": "line",
"start": [
74.5,
-13.496098
],
"end": [
74.5,
-22.6
]
},
{
"type": "line",
"start": [
74.5,
-22.6
],
"end": [
102.5,
-22.6
]
},
{
"type": "line",
"start": [
102.5,
-22.6
],
"end": [
102.5,
-7.5
]
},
{
"type": "arc",
"start": [
102.5,
-7.5
],
"end": [
117.5,
-7.5
],
"center": [
110.0,
-7.5
],
"radius": 7.5,
"mid": [
110.0,
0.0
],
"clockwise": false
},
{
"type": "line",
"start": [
117.5,
-7.5
],
"end": [
117.5,
-22.6
]
},
{
"type": "line",
"start": [
117.5,
-22.6
],
"end": [
140.748693,
-22.6
]
},
{
"type": "line",
"start": [
140.748693,
-22.6
],
"end": [
140.748693,
124.4
]
},
{
"type": "line",
"start": [
140.748693,
124.4
],
"end": [
117.5,
124.4
]
},
{
"type": "line",
"start": [
117.5,
124.4
],
"end": [
117.5,
102.5
]
},
{
"type": "arc",
"start": [
117.5,
102.5
],
"end": [
102.5,
102.5
],
"center": [
110.0,
102.5
],
"radius": 7.5,
"mid": [
110.0,
95.0
],
"clockwise": true
},
{
"type": "line",
"start": [
102.5,
102.5
],
"end": [
102.5,
124.4
]
},
{
"type": "line",
"start": [
102.5,
124.4
],
"end": [
7.5,
124.4
]
},
{
"type": "line",
"start": [
7.5,
124.4
],
"end": [
7.5,
102.5
]
},
{
"type": "arc",
"start": [
7.5,
102.5
],
"end": [
0.0,
95.0
],
"center": [
0.0,
102.5
],
"radius": 7.5,
"mid": [
5.303301,
97.196699
],
"clockwise": true
},
{
"type": "line",
"start": [
0.0,
95.0
],
"end": [
-13.5,
95.0
]
},
{
"type": "line",
"start": [
-13.5,
95.0
],
"end": [
-13.5,
0.0
]
},
{
"type": "line",
"start": [
-13.5,
0.0
],
"end": [
0.0,
0.0
]
}
],
"inner_boundaries": [], "inner_boundaries": [],
"num_inner_boundaries": 0, "num_inner_boundaries": 0,
"holes": [],
"thickness": null, "thickness": null,
"transform": { "transform": {
"origin": [ "origin": [
@@ -120,6 +641,5 @@
0.0, 0.0,
1.0 1.0
] ]
}, }
"holes": []
} }