fix: clip pockets and triangulation to boundary in plots — no visual crossovers
This commit is contained in:
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user