fix: match reference rib profile style — green boundary, pink outlines, blue holes, 2mm w_frame, zoomed corner view, pocket clipping to inner plate

This commit is contained in:
2026-02-17 12:56:58 +00:00
parent 732e41ec3a
commit 856ff239d6

View File

@@ -63,23 +63,30 @@ def _plot_density(geometry: Dict[str, Any], params: Dict[str, Any], out_path: Pa
plt.close(fig)
def _plot_triangulation(geometry: Dict[str, Any], triangulation: Dict[str, Any], out_path: Path) -> None:
from shapely.geometry import Polygon as ShapelyPolygon, LineString
def _plot_triangulation(geometry: Dict[str, Any], triangulation: Dict[str, Any], out_path: Path, params: Dict[str, Any] = None) -> None:
"""
Plot the Delaunay triangulation clipped to the inner frame (w_frame inset).
Green boundary, blue rib edges, blue hole circles.
"""
from shapely.geometry import Polygon as ShapelyPolygon, LineString, Point
verts = triangulation["vertices"]
tris = triangulation["triangles"]
fig, ax = plt.subplots(figsize=(8, 6), dpi=160)
outer = np.asarray(geometry["outer_boundary"])
plate_poly = ShapelyPolygon(outer)
w_frame = (params or {}).get("w_frame", 2.0)
inner_plate = plate_poly.buffer(-w_frame)
if inner_plate.is_empty or not inner_plate.is_valid:
inner_plate = plate_poly
# Draw only triangle edges that are inside the boundary (clipped)
fig, ax = plt.subplots(figsize=(8, 6), dpi=160)
# Draw triangle edges clipped to inner plate
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)):
centroid = Point(verts[tri].mean(axis=0))
if not inner_plate.contains(centroid):
continue
for i in range(3):
edge = tuple(sorted([tri[i], tri[(i + 1) % 3]]))
@@ -88,22 +95,26 @@ def _plot_triangulation(geometry: Dict[str, Any], triangulation: Dict[str, Any],
drawn_edges.add(edge)
p1, p2 = verts[edge[0]], verts[edge[1]]
line = LineString([p1, p2])
clipped = plate_poly.intersection(line)
clipped = inner_plate.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)
ax.plot(cx, cy, color="#1f77b4", lw=0.5, 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(cx, cy, color="#1f77b4", lw=0.5, alpha=0.85)
ax.plot(np.r_[outer[:, 0], outer[0, 0]], np.r_[outer[:, 1], outer[0, 1]], "k-", lw=1.6)
# Green sandbox boundary
ax.plot(np.r_[outer[:, 0], outer[0, 0]], np.r_[outer[:, 1], outer[0, 1]],
color="#228833", lw=1.8, zorder=5)
# Blue hole circles
for hole in geometry.get("holes", []):
hb = np.asarray(hole["boundary"])
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.plot(np.r_[hb[:, 0], hb[0, 0]], np.r_[hb[:, 1], hb[0, 1]],
"b-", lw=0.8, zorder=3)
ax.set_aspect("equal", adjustable="box")
ax.set_title("Constrained Delaunay Triangulation / Rib Pattern")
@@ -114,60 +125,77 @@ def _plot_triangulation(geometry: Dict[str, Any], triangulation: Dict[str, Any],
plt.close(fig)
def _plot_final_profile(geometry, pockets, ribbed_plate, out_path: Path) -> None:
def _plot_final_profile(geometry, pockets, ribbed_plate, out_path: Path, params: Dict[str, Any] = None) -> None:
"""
Plot the rib profile: green sandbox boundary, pink pocket outlines (clipped),
blue hole circles. Pockets must respect w_frame inset from boundary.
Also produces a zoomed corner view as a second subplot.
"""
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")
w_frame = (params or {}).get("w_frame", 2.0)
inner_plate = plate_poly.buffer(-w_frame)
if inner_plate.is_empty or not inner_plate.is_valid:
inner_plate = plate_poly
# 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:
fig, (ax_full, ax_zoom) = plt.subplots(1, 2, figsize=(14, 6), dpi=160)
for ax in (ax_full, ax_zoom):
# Green sandbox boundary
ax.plot(np.r_[outer[:, 0], outer[0, 0]], np.r_[outer[:, 1], outer[0, 1]],
color="#228833", lw=1.8, zorder=5)
# Draw pockets clipped to inner plate (w_frame inset) — outlines only
for pocket in pockets:
polyline = pocket.get("polyline", pocket.get("vertices", []))
pv = np.asarray(polyline)
if len(pv) < 3:
continue
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)
clipped = inner_plate.intersection(pocket_poly)
if clipped.is_empty:
continue
# Draw clipped geometry
clip_geoms = [clipped] if clipped.geom_type == "Polygon" else list(clipped.geoms)
clip_geoms = [clipped] if clipped.geom_type == "Polygon" else list(getattr(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)
ax.fill(cx, cy, color="#ffcccc", alpha=0.25, lw=0.0)
ax.plot(cx, cy, color="#cc6677", lw=0.9, zorder=4)
# 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)
# Blue hole circles
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]],
"b-", lw=0.8, zorder=3)
if ribbed_plate.geom_type == "Polygon":
geoms = [ribbed_plate]
else:
geoms = list(ribbed_plate.geoms)
ax.set_aspect("equal", adjustable="box")
ax.set_xlabel("x [mm]")
ax.set_ylabel("y [mm]")
for g in geoms:
x, y = g.exterior.xy
ax.plot(x, y, color="#228833", lw=1.2)
for i in g.interiors:
ix, iy = i.xy
ax.plot(ix, iy, color="#cc6677", lw=0.9)
# Full view
num_pockets_shown = sum(
1 for p in pockets
if len(p.get("polyline", p.get("vertices", []))) >= 3
and not inner_plate.intersection(
ShapelyPolygon(p.get("polyline", p.get("vertices", []))).buffer(0)
).is_empty
)
ax_full.set_title(f"Full view ({num_pockets_shown} pockets)")
# Zoomed view on a corner area (top-right where crossovers were worst)
bounds = plate_poly.bounds # minx, miny, maxx, maxy
mid_x = (bounds[0] + bounds[2]) / 2
ax_zoom.set_xlim(mid_x - 30, bounds[2] + 10)
ax_zoom.set_ylim(bounds[3] - 60, bounds[3] + 10)
ax_zoom.set_title("Zoomed: corner area")
ax.set_aspect("equal", adjustable="box")
ax.set_title("Final Ribbed Plate Profile with Pockets")
ax.set_xlabel("x [mm]")
ax.set_ylabel("y [mm]")
fig.tight_layout()
fig.savefig(out_path)
plt.close(fig)
@@ -199,8 +227,8 @@ def run_pipeline(geometry_path: Path, params_path: Path | None, output_dir: Path
stem = geometry_path.stem
_plot_density(geometry, params, output_dir / f"{stem}_density.png")
_plot_triangulation(geometry, triangulation, output_dir / f"{stem}_triangulation.png")
_plot_final_profile(geometry, pockets, ribbed_plate, output_dir / f"{stem}_final_profile.png")
_plot_triangulation(geometry, triangulation, output_dir / f"{stem}_triangulation.png", params=params)
_plot_final_profile(geometry, pockets, ribbed_plate, output_dir / f"{stem}_final_profile.png", params=params)
summary = {
"geometry": geometry.get("plate_id", geometry_path.name),