fix: clip pockets and triangulation to boundary in plots — no visual crossovers

This commit is contained in:
2026-02-17 12:42:52 +00:00
parent 39a3420a8e
commit 732e41ec3a
2 changed files with 72 additions and 5 deletions

View File

@@ -64,17 +64,46 @@ def _plot_density(geometry: Dict[str, Any], params: Dict[str, Any], out_path: Pa
def _plot_triangulation(geometry: Dict[str, Any], triangulation: Dict[str, Any], out_path: Path) -> None:
from shapely.geometry import Polygon as ShapelyPolygon, LineString
verts = triangulation["vertices"]
tris = triangulation["triangles"]
fig, ax = plt.subplots(figsize=(8, 6), dpi=160)
ax.triplot(verts[:, 0], verts[:, 1], tris, color="#1f77b4", lw=0.4, alpha=0.85)
outer = np.asarray(geometry["outer_boundary"])
plate_poly = ShapelyPolygon(outer)
# Draw only triangle edges that are inside the boundary (clipped)
drawn_edges = set()
for tri in tris:
centroid = verts[tri].mean(axis=0)
from shapely.geometry import Point
if not plate_poly.contains(Point(centroid)):
continue
for i in range(3):
edge = tuple(sorted([tri[i], tri[(i + 1) % 3]]))
if edge in drawn_edges:
continue
drawn_edges.add(edge)
p1, p2 = verts[edge[0]], verts[edge[1]]
line = LineString([p1, p2])
clipped = plate_poly.intersection(line)
if clipped.is_empty:
continue
if clipped.geom_type == "LineString":
cx, cy = clipped.xy
ax.plot(cx, cy, color="#1f77b4", lw=0.4, alpha=0.85)
elif clipped.geom_type == "MultiLineString":
for seg in clipped.geoms:
cx, cy = seg.xy
ax.plot(cx, cy, color="#1f77b4", lw=0.4, alpha=0.85)
ax.plot(np.r_[outer[:, 0], outer[0, 0]], np.r_[outer[:, 1], outer[0, 1]], "k-", lw=1.6)
for hole in geometry.get("holes", []):
hb = np.asarray(hole["boundary"])
ax.plot(np.r_[hb[:, 0], hb[0, 0]], np.r_[hb[:, 1], hb[0, 1]], "r-", lw=1.0)
ax.fill(hb[:, 0], hb[:, 1], color="white", zorder=2)
ax.plot(np.r_[hb[:, 0], hb[0, 0]], np.r_[hb[:, 1], hb[0, 1]], "r-", lw=1.0, zorder=3)
ax.set_aspect("equal", adjustable="box")
ax.set_title("Constrained Delaunay Triangulation / Rib Pattern")
@@ -86,16 +115,42 @@ def _plot_triangulation(geometry: Dict[str, Any], triangulation: Dict[str, Any],
def _plot_final_profile(geometry, pockets, ribbed_plate, out_path: Path) -> None:
from shapely.geometry import Polygon as ShapelyPolygon
from matplotlib.patches import PathPatch
from matplotlib.path import Path as MplPath
fig, ax = plt.subplots(figsize=(8, 6), dpi=160)
outer = np.asarray(geometry["outer_boundary"])
plate_poly = ShapelyPolygon(outer)
ax.plot(np.r_[outer[:, 0], outer[0, 0]], np.r_[outer[:, 1], outer[0, 1]], "k-", lw=1.8, label="Outer boundary")
# Draw pockets clipped to the plate boundary — no crossovers
for pocket in pockets:
polyline = pocket.get("polyline", pocket.get("vertices", []))
pv = np.asarray(polyline)
if len(pv) >= 3:
ax.fill(pv[:, 0], pv[:, 1], color="#88ccee", alpha=0.35, lw=0.0)
pocket_poly = ShapelyPolygon(pv)
if not pocket_poly.is_valid:
pocket_poly = pocket_poly.buffer(0)
if pocket_poly.is_empty:
continue
clipped = plate_poly.intersection(pocket_poly)
if clipped.is_empty:
continue
# Draw clipped geometry
clip_geoms = [clipped] if clipped.geom_type == "Polygon" else list(clipped.geoms)
for cg in clip_geoms:
if cg.geom_type != "Polygon" or cg.is_empty:
continue
cx, cy = cg.exterior.xy
ax.fill(cx, cy, color="#88ccee", alpha=0.35, lw=0.0)
# Draw holes from geometry
for hole in geometry.get("holes", []):
hb = np.asarray(hole["boundary"])
ax.fill(hb[:, 0], hb[:, 1], color="white", lw=0.0)
ax.plot(np.r_[hb[:, 0], hb[0, 0]], np.r_[hb[:, 1], hb[0, 1]], "k-", lw=0.7)
if ribbed_plate.geom_type == "Polygon":
geoms = [ribbed_plate]

View File

@@ -224,8 +224,20 @@ def generate_triangulation(geometry, params, max_refinement_passes=3):
cx = np.mean(all_pts[t, 0])
cy = np.mean(all_pts[t, 1])
centroid = Point(cx, cy)
# Keep if centroid is inside plate frame and outside keepouts
if inner_plate.contains(centroid) and not keepout_union.contains(centroid):
# Keep if centroid is inside plate frame and outside keepouts,
# AND all 3 vertices are inside the plate boundary (no crossovers)
if not inner_plate.contains(centroid):
continue
if keepout_union.contains(centroid):
continue
all_inside = True
for vi in t:
if not plate_poly.contains(Point(all_pts[vi])):
# Allow small tolerance (vertex on boundary is OK)
if not plate_poly.buffer(0.5).contains(Point(all_pts[vi])):
all_inside = False
break
if all_inside:
keep.append(i)
triangles = triangles[keep] if keep else np.empty((0, 3), dtype=int)