216 lines
6.4 KiB
Python
216 lines
6.4 KiB
Python
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
import os
|
|
from pathlib import Path
|
|
import sys
|
|
from typing import Any, Optional
|
|
|
|
import matplotlib
|
|
|
|
matplotlib.use("Agg")
|
|
import matplotlib.pyplot as plt
|
|
import nazca as nd
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
RUNS_DIR = Path(__file__).resolve().parent
|
|
OUTPUT_IMAGE_ROOT = ROOT / "docs" / "source" / "images"
|
|
PACKAGE_ROOT = "mxpic.components.primitives"
|
|
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
_PRIMITIVE_BUILDER_PATH = RUNS_DIR / "build_all_primitives.py"
|
|
_PRIMITIVE_BUILDER_SPEC = importlib.util.spec_from_file_location(
|
|
"mxpic_build_all_primitives",
|
|
_PRIMITIVE_BUILDER_PATH,
|
|
)
|
|
if _PRIMITIVE_BUILDER_SPEC is None or _PRIMITIVE_BUILDER_SPEC.loader is None:
|
|
raise ImportError(f"Could not load primitive builder: {_PRIMITIVE_BUILDER_PATH}")
|
|
|
|
_primitive_builder = importlib.util.module_from_spec(_PRIMITIVE_BUILDER_SPEC)
|
|
_PRIMITIVE_BUILDER_SPEC.loader.exec_module(_primitive_builder)
|
|
|
|
bootstrap_technology = _primitive_builder.bootstrap_technology
|
|
build_context = _primitive_builder.build_context
|
|
build_kwargs = _primitive_builder.build_kwargs
|
|
discover_primitive_classes = _primitive_builder.discover_primitive_classes
|
|
get_cell = _primitive_builder.get_cell
|
|
safe_name = _primitive_builder.safe_name
|
|
|
|
|
|
PALETTE = {
|
|
"WG": "blue",
|
|
"SLAB": "cyan",
|
|
"M1": "#FFD700",
|
|
"M2": "silver",
|
|
"DEEP_TRENCH": "black",
|
|
(1, 0): "darkred",
|
|
(2, 0): "green",
|
|
(1111, 0): "green",
|
|
(63, 30): "#FFD700",
|
|
}
|
|
|
|
|
|
USE_COLOR = sys.stdout.isatty() and os.environ.get("NO_COLOR") is None
|
|
COLORS = {
|
|
"bold": "\033[1m",
|
|
"cyan": "\033[36m",
|
|
"green": "\033[32m",
|
|
"red": "\033[31m",
|
|
"yellow": "\033[33m",
|
|
"reset": "\033[0m",
|
|
}
|
|
STATUS_STYLES = {
|
|
"info": (">", "cyan", "Info"),
|
|
"generated": ("+", "green", "Generated"),
|
|
"skipped": ("-", "yellow", "Skipped"),
|
|
"failed": ("x", "red", "Failed"),
|
|
}
|
|
PIN_NAME_EXCLUDE = {"org"}
|
|
|
|
|
|
def colorize(text: str, color: str) -> str:
|
|
if not USE_COLOR:
|
|
return text
|
|
return f"{COLORS[color]}{text}{COLORS['reset']}"
|
|
|
|
|
|
def status_item(status: str, count: Optional[int] = None) -> str:
|
|
symbol, color, label = STATUS_STYLES[status]
|
|
suffix = f": {count}" if count is not None else ""
|
|
return colorize(f"{symbol} {label}{suffix}", color)
|
|
|
|
|
|
def print_status(status: str, message: str) -> None:
|
|
print(f"{status_item(status)} {message}")
|
|
|
|
|
|
def apply_mxpic_colors() -> None:
|
|
"""Apply the mxPIC color palette to Nazca when the layers are available."""
|
|
print_status("info", "Applying mxPIC layer colors...")
|
|
for layer_id, color in PALETTE.items():
|
|
try:
|
|
nd.set_layercolor(layer=layer_id, color=color)
|
|
except Exception:
|
|
continue
|
|
|
|
|
|
def remove_generated_pngs(img_root: Path) -> None:
|
|
"""Remove previously generated image artifacts."""
|
|
if not img_root.exists():
|
|
return
|
|
|
|
for png_file in img_root.rglob("*.png"):
|
|
png_file.unlink()
|
|
|
|
|
|
def image_subdir_for_module(module_name: str, package_root: str) -> Path:
|
|
module_suffix = module_name.removeprefix(f"{package_root}.")
|
|
parts = module_suffix.split(".")[:-1]
|
|
if not parts:
|
|
return Path()
|
|
return Path(*parts)
|
|
|
|
|
|
def visible_pin_names(cell: nd.Cell) -> list[str]:
|
|
"""Return all user-facing cell pins in deterministic cell order."""
|
|
return [name for name in cell.pin if name not in PIN_NAME_EXCLUDE]
|
|
|
|
|
|
def build_image_top_cell(cell: nd.Cell, top_name: str) -> nd.Cell:
|
|
"""Build a plotting wrapper that exposes and draws every component pin."""
|
|
pin_names = visible_pin_names(cell)
|
|
with nd.Cell(name=top_name, instantiate=False) as top_cell:
|
|
instance = cell.put(0, 0, 0)
|
|
if pin_names:
|
|
instance.raise_pins(pin_names, pin_names)
|
|
nd.put_stub(pinname=pin_names, pinsize=0.8)
|
|
|
|
return top_cell
|
|
|
|
|
|
def generate_image_for_class(
|
|
target_dir: Path,
|
|
class_name: str,
|
|
component_class: type,
|
|
device_name: str,
|
|
top_name: str,
|
|
context: dict[str, Any],
|
|
) -> str:
|
|
"""Instantiate one component class and save its cell image."""
|
|
kwargs = build_kwargs(component_class, device_name, context)
|
|
instance = component_class(**kwargs)
|
|
top_cell = build_image_top_cell(get_cell(instance), top_name)
|
|
|
|
target_dir.mkdir(parents=True, exist_ok=True)
|
|
image_path = target_dir / f"{class_name}.png"
|
|
nd.export_plt(path="", title=top_name, topcells=[top_cell])
|
|
plt.savefig(image_path, bbox_inches="tight", dpi=300)
|
|
plt.close()
|
|
return "generated"
|
|
|
|
|
|
def generate_component_images(
|
|
img_root: Path = OUTPUT_IMAGE_ROOT,
|
|
package_root: str = PACKAGE_ROOT,
|
|
) -> dict[str, int]:
|
|
print(colorize("mxPIC primitive image generation", "bold"))
|
|
|
|
img_root = Path(img_root)
|
|
counts = {"generated": 0, "skipped": 0, "failed": 0}
|
|
|
|
img_root.mkdir(parents=True, exist_ok=True)
|
|
remove_generated_pngs(img_root)
|
|
nd.clear_layout()
|
|
bootstrap_technology()
|
|
apply_mxpic_colors()
|
|
context = build_context()
|
|
|
|
for index, component_class in enumerate(discover_primitive_classes(), start=1):
|
|
class_name = component_class.__name__
|
|
module_name = component_class.__module__
|
|
target_dir = img_root / image_subdir_for_module(module_name, package_root)
|
|
|
|
try:
|
|
device_name = safe_name("IMG", index, class_name)
|
|
top_name = safe_name("IMGTOP", index, class_name)
|
|
result = generate_image_for_class(
|
|
target_dir,
|
|
class_name,
|
|
component_class,
|
|
device_name=device_name,
|
|
top_name=top_name,
|
|
context=context,
|
|
)
|
|
except Exception as error:
|
|
plt.close()
|
|
print_status("failed", f"{module_name}.{class_name}: {error}")
|
|
counts["failed"] += 1
|
|
continue
|
|
|
|
counts[result] += 1
|
|
if result == "generated":
|
|
print_status("generated", str(target_dir / f"{class_name}.png"))
|
|
else:
|
|
print_status("skipped", f"{module_name}.{class_name} has no cell")
|
|
|
|
print(
|
|
"\n"
|
|
+ colorize("Image generation complete.", "bold")
|
|
+ " "
|
|
+ status_item("generated", counts["generated"])
|
|
+ " | "
|
|
+ status_item("skipped", counts["skipped"])
|
|
+ " | "
|
|
+ status_item("failed", counts["failed"])
|
|
)
|
|
if counts["failed"]:
|
|
raise RuntimeError(f"{counts['failed']} primitive image(s) failed to build")
|
|
|
|
return counts
|
|
|
|
|
|
if __name__ == "__main__":
|
|
generate_component_images()
|