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) plt.close(fig)
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, params: Dict[str, Any] = None) -> None:
from shapely.geometry import Polygon as ShapelyPolygon, LineString """
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"] verts = triangulation["vertices"]
tris = triangulation["triangles"] tris = triangulation["triangles"]
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) 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() drawn_edges = set()
for tri in tris: for tri in tris:
centroid = verts[tri].mean(axis=0) centroid = Point(verts[tri].mean(axis=0))
from shapely.geometry import Point if not inner_plate.contains(centroid):
if not plate_poly.contains(Point(centroid)):
continue continue
for i in range(3): for i in range(3):
edge = tuple(sorted([tri[i], tri[(i + 1) % 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) drawn_edges.add(edge)
p1, p2 = verts[edge[0]], verts[edge[1]] p1, p2 = verts[edge[0]], verts[edge[1]]
line = LineString([p1, p2]) line = LineString([p1, p2])
clipped = plate_poly.intersection(line) clipped = inner_plate.intersection(line)
if clipped.is_empty: if clipped.is_empty:
continue continue
if clipped.geom_type == "LineString": if clipped.geom_type == "LineString":
cx, cy = clipped.xy 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": elif clipped.geom_type == "MultiLineString":
for seg in clipped.geoms: for seg in clipped.geoms:
cx, cy = seg.xy 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", []): for hole in geometry.get("holes", []):
hb = np.asarray(hole["boundary"]) 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]],
ax.plot(np.r_[hb[:, 0], hb[0, 0]], np.r_[hb[:, 1], hb[0, 1]], "r-", lw=1.0, zorder=3) "b-", lw=0.8, 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")
@@ -114,60 +125,77 @@ def _plot_triangulation(geometry: Dict[str, Any], triangulation: Dict[str, Any],
plt.close(fig) 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 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"]) outer = np.asarray(geometry["outer_boundary"])
plate_poly = ShapelyPolygon(outer) 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 fig, (ax_full, ax_zoom) = plt.subplots(1, 2, figsize=(14, 6), dpi=160)
for pocket in pockets:
polyline = pocket.get("polyline", pocket.get("vertices", [])) for ax in (ax_full, ax_zoom):
pv = np.asarray(polyline) # Green sandbox boundary
if len(pv) >= 3: 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) pocket_poly = ShapelyPolygon(pv)
if not pocket_poly.is_valid: if not pocket_poly.is_valid:
pocket_poly = pocket_poly.buffer(0) pocket_poly = pocket_poly.buffer(0)
if pocket_poly.is_empty: if pocket_poly.is_empty:
continue continue
clipped = plate_poly.intersection(pocket_poly) clipped = inner_plate.intersection(pocket_poly)
if clipped.is_empty: if clipped.is_empty:
continue continue
# Draw clipped geometry clip_geoms = [clipped] if clipped.geom_type == "Polygon" else list(getattr(clipped, "geoms", []))
clip_geoms = [clipped] if clipped.geom_type == "Polygon" else list(clipped.geoms)
for cg in clip_geoms: for cg in clip_geoms:
if cg.geom_type != "Polygon" or cg.is_empty: if cg.geom_type != "Polygon" or cg.is_empty:
continue continue
cx, cy = cg.exterior.xy 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 # Blue hole circles
for hole in geometry.get("holes", []): for hole in geometry.get("holes", []):
hb = np.asarray(hole["boundary"]) 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]],
ax.plot(np.r_[hb[:, 0], hb[0, 0]], np.r_[hb[:, 1], hb[0, 1]], "k-", lw=0.7) "b-", lw=0.8, zorder=3)
if ribbed_plate.geom_type == "Polygon": ax.set_aspect("equal", adjustable="box")
geoms = [ribbed_plate] ax.set_xlabel("x [mm]")
else: ax.set_ylabel("y [mm]")
geoms = list(ribbed_plate.geoms)
for g in geoms: # Full view
x, y = g.exterior.xy num_pockets_shown = sum(
ax.plot(x, y, color="#228833", lw=1.2) 1 for p in pockets
for i in g.interiors: if len(p.get("polyline", p.get("vertices", []))) >= 3
ix, iy = i.xy and not inner_plate.intersection(
ax.plot(ix, iy, color="#cc6677", lw=0.9) 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.tight_layout()
fig.savefig(out_path) fig.savefig(out_path)
plt.close(fig) plt.close(fig)
@@ -199,8 +227,8 @@ def run_pipeline(geometry_path: Path, params_path: Path | None, output_dir: Path
stem = geometry_path.stem stem = geometry_path.stem
_plot_density(geometry, params, output_dir / f"{stem}_density.png") _plot_density(geometry, params, output_dir / f"{stem}_density.png")
_plot_triangulation(geometry, triangulation, output_dir / f"{stem}_triangulation.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") _plot_final_profile(geometry, pockets, ribbed_plate, output_dir / f"{stem}_final_profile.png", params=params)
summary = { summary = {
"geometry": geometry.get("plate_id", geometry_path.name), "geometry": geometry.get("plate_id", geometry_path.name),