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:
|
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"]
|
verts = triangulation["vertices"]
|
||||||
tris = triangulation["triangles"]
|
tris = triangulation["triangles"]
|
||||||
|
|
||||||
fig, ax = plt.subplots(figsize=(8, 6), dpi=160)
|
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"])
|
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)
|
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", []):
|
for hole in geometry.get("holes", []):
|
||||||
hb = np.asarray(hole["boundary"])
|
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_aspect("equal", adjustable="box")
|
||||||
ax.set_title("Constrained Delaunay Triangulation / Rib Pattern")
|
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:
|
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)
|
fig, ax = plt.subplots(figsize=(8, 6), dpi=160)
|
||||||
|
|
||||||
outer = np.asarray(geometry["outer_boundary"])
|
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")
|
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:
|
for pocket in pockets:
|
||||||
polyline = pocket.get("polyline", pocket.get("vertices", []))
|
polyline = pocket.get("polyline", pocket.get("vertices", []))
|
||||||
pv = np.asarray(polyline)
|
pv = np.asarray(polyline)
|
||||||
if len(pv) >= 3:
|
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":
|
if ribbed_plate.geom_type == "Polygon":
|
||||||
geoms = [ribbed_plate]
|
geoms = [ribbed_plate]
|
||||||
|
|||||||
@@ -224,8 +224,20 @@ def generate_triangulation(geometry, params, max_refinement_passes=3):
|
|||||||
cx = np.mean(all_pts[t, 0])
|
cx = np.mean(all_pts[t, 0])
|
||||||
cy = np.mean(all_pts[t, 1])
|
cy = np.mean(all_pts[t, 1])
|
||||||
centroid = Point(cx, cy)
|
centroid = Point(cx, cy)
|
||||||
# Keep if centroid is inside plate frame and outside keepouts
|
# Keep if centroid is inside plate frame and outside keepouts,
|
||||||
if inner_plate.contains(centroid) and not keepout_union.contains(centroid):
|
# 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)
|
keep.append(i)
|
||||||
|
|
||||||
triangles = triangles[keep] if keep else np.empty((0, 3), dtype=int)
|
triangles = triangles[keep] if keep else np.empty((0, 3), dtype=int)
|
||||||
|
|||||||
Reference in New Issue
Block a user