diff --git a/examples/01_simple_route.py b/examples/01_simple_route.py index cb2b893..0c81782 100644 --- a/examples/01_simple_route.py +++ b/examples/01_simple_route.py @@ -49,7 +49,7 @@ def main() -> None: print("Failed to route.") # 5. Visualize - fig, ax = plot_routing_results(results, [obstacle], bounds) + fig, ax = plot_routing_results(results, [obstacle], bounds, netlist=netlist) fig.savefig("examples/simple_route.png") print("Saved plot to examples/simple_route.png") diff --git a/examples/02_congestion_resolution.py b/examples/02_congestion_resolution.py index 7ebb888..520c631 100644 --- a/examples/02_congestion_resolution.py +++ b/examples/02_congestion_resolution.py @@ -45,7 +45,7 @@ def main() -> None: print(f" {nid}: valid={res.is_valid}, collisions={res.collisions}") # 5. Visualize - fig, ax = plot_routing_results(results, [], bounds) + fig, ax = plot_routing_results(results, [], bounds, netlist=netlist) fig.savefig("examples/congestion.png") print("Saved plot to examples/congestion.png") diff --git a/examples/03_locked_paths.py b/examples/03_locked_paths.py index 42b2a8f..ace3bb6 100644 --- a/examples/03_locked_paths.py +++ b/examples/03_locked_paths.py @@ -59,16 +59,18 @@ def main() -> None: print("Failed to route secondary net.") # 5. Visualize - # Combine results for plotting + # Combine results and netlists for plotting all_results = {**results1, **results2} + all_netlists = {**netlist_phase1, **netlist_phase2} - # Note: 'critical_net' is now in engine.static_obstacles internally, + # Note: 'critical_net' is now in engine.static_obstacles internally, # but for visualization we plot it from the result object to see it clearly. - # We pass an empty list for 'static_obstacles' to plot_routing_results + # We pass an empty list for 'static_obstacles' to plot_routing_results # because we want to see the path colored, not grayed out as an obstacle. - fig, ax = plot_routing_results(all_results, [], bounds) + fig, ax = plot_routing_results(all_results, [], bounds, netlist=all_netlists) fig.savefig("examples/locked.png") + print("Saved plot to examples/locked.png") diff --git a/examples/04_sbends_and_radii.py b/examples/04_sbends_and_radii.py index c3d2b0b..2234878 100644 --- a/examples/04_sbends_and_radii.py +++ b/examples/04_sbends_and_radii.py @@ -61,7 +61,7 @@ def main() -> None: print(f"{nid}: {status}, collisions={res.collisions}") # 6. Visualize - fig, ax = plot_routing_results(results, [], bounds) + fig, ax = plot_routing_results(results, [], bounds, netlist=netlist) fig.savefig("examples/sbends_radii.png") print("Saved plot to examples/sbends_radii.png") diff --git a/examples/congestion.png b/examples/congestion.png index 83e2a7d..775d78b 100644 Binary files a/examples/congestion.png and b/examples/congestion.png differ diff --git a/examples/locked.png b/examples/locked.png index 7701636..8c45ebf 100644 Binary files a/examples/locked.png and b/examples/locked.png differ diff --git a/examples/sbends_radii.png b/examples/sbends_radii.png index ea7b0e0..1a8f9c5 100644 Binary files a/examples/sbends_radii.png and b/examples/sbends_radii.png differ diff --git a/examples/simple_route.png b/examples/simple_route.png index a012440..400488d 100644 Binary files a/examples/simple_route.png and b/examples/simple_route.png differ diff --git a/inire/utils/visualization.py b/inire/utils/visualization.py index 44667ff..30e5bee 100644 --- a/inire/utils/visualization.py +++ b/inire/utils/visualization.py @@ -3,12 +3,14 @@ from __future__ import annotations from typing import TYPE_CHECKING import matplotlib.pyplot as plt +import numpy as np if TYPE_CHECKING: from matplotlib.axes import Axes from matplotlib.figure import Figure from shapely.geometry import Polygon + from inire.geometry.primitives import Port from inire.router.pathfinder import RoutingResult @@ -16,6 +18,7 @@ def plot_routing_results( results: dict[str, RoutingResult], static_obstacles: list[Polygon], bounds: tuple[float, float, float, float], + netlist: dict[str, tuple[Port, Port]] | None = None, ) -> tuple[Figure, Axes]: """Plot obstacles and routed paths using matplotlib.""" fig, ax = plt.subplots(figsize=(10, 10)) @@ -34,7 +37,8 @@ def plot_routing_results( color = "red" # Highlight failing nets label_added = False - for comp in res.path: + for j, comp in enumerate(res.path): + # 1. Plot geometry for poly in comp.geometry: # Handle both Polygon and MultiPolygon (e.g. from SBend) geoms = [poly] if hasattr(poly, "exterior") else poly.geoms @@ -43,6 +47,26 @@ def plot_routing_results( ax.fill(x, y, alpha=0.7, fc=color, ec="black", label=net_id if not label_added else "") label_added = True + # 2. Plot subtle port orientation arrow for internal ports + # (Every segment's end_port except possibly the last one if it matches target) + p = comp.end_port + rad = np.radians(p.orientation) + u = np.cos(rad) + v = np.sin(rad) + + # Internal ports get smaller, narrower, semi-transparent arrows + ax.quiver(p.x, p.y, u, v, color="black", scale=40, width=0.003, alpha=0.3, pivot="tail", zorder=4) + + # 3. Plot main arrows for netlist ports (if provided) + if netlist and net_id in netlist: + start_p, target_p = netlist[net_id] + for p in [start_p, target_p]: + rad = np.radians(p.orientation) + u = np.cos(rad) + v = np.sin(rad) + # Netlist ports get prominent arrows + ax.quiver(p.x, p.y, u, v, color="black", scale=25, width=0.005, pivot="tail", zorder=6) + ax.set_xlim(bounds[0], bounds[2]) ax.set_ylim(bounds[1], bounds[3]) ax.set_aspect("equal") @@ -51,5 +75,5 @@ def plot_routing_results( handles, labels = ax.get_legend_handles_labels() if labels: ax.legend() - plt.grid(True) + ax.grid(alpha=0.6) return fig, ax