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:
@@ -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),
|
||||||
|
|||||||
Reference in New Issue
Block a user