132 lines
4.0 KiB
Python
132 lines
4.0 KiB
Python
import importlib
|
|
from pathlib import Path
|
|
|
|
import matplotlib
|
|
|
|
matplotlib.use("Agg")
|
|
import matplotlib.pyplot as plt
|
|
import nazca as nd
|
|
|
|
from generate_handbook import DEFAULT_PACKAGE_ROOT, DEFAULT_SRC_ROOT, public_top_level_members
|
|
|
|
|
|
PALETTE = {
|
|
"WG": "blue",
|
|
"SLAB": "cyan",
|
|
"M1": "#FFD700",
|
|
"M2": "silver",
|
|
"DEEP_TRENCH": "black",
|
|
(1, 0): "darkred",
|
|
(2, 0): "green",
|
|
(1111, 0): "green",
|
|
(63, 30): "#FFD700",
|
|
}
|
|
|
|
|
|
def apply_mxpic_colors() -> None:
|
|
"""Apply the mxPIC color palette to Nazca when the layers are available."""
|
|
print("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 generated images while preserving Markdown and other docs files."""
|
|
if not img_root.exists():
|
|
return
|
|
|
|
for png_file in img_root.rglob("*.png"):
|
|
png_file.unlink()
|
|
|
|
|
|
def module_name_for(py_file: Path, src_root: Path, package_root: str) -> str:
|
|
relative_path = py_file.relative_to(src_root).with_suffix("")
|
|
return f"{package_root}.{'.'.join(relative_path.parts)}"
|
|
|
|
|
|
def generate_image_for_class(target_dir: Path, class_name: str, component_class: type) -> str:
|
|
"""Instantiate one component class and save its cell image."""
|
|
nd.clear_layout()
|
|
instance = component_class()
|
|
cell = getattr(instance, "cell", None)
|
|
if cell is None:
|
|
return "skipped"
|
|
|
|
target_dir.mkdir(parents=True, exist_ok=True)
|
|
image_path = target_dir / f"{class_name}.png"
|
|
nd.export_plt(path="", title=class_name, topcells=[cell])
|
|
plt.savefig(image_path, bbox_inches="tight", dpi=300)
|
|
plt.close()
|
|
return "generated"
|
|
|
|
|
|
def generate_component_images(
|
|
img_root: Path = Path("images/components"),
|
|
src_root: Path = DEFAULT_SRC_ROOT,
|
|
package_root: str = DEFAULT_PACKAGE_ROOT,
|
|
) -> dict[str, int]:
|
|
print("Starting mxPIC component image generation...")
|
|
|
|
img_root = Path(img_root)
|
|
src_root = Path(src_root)
|
|
counts = {"generated": 0, "skipped": 0, "failed": 0}
|
|
|
|
if not src_root.exists():
|
|
print(f"Source directory not found: {src_root}")
|
|
counts["failed"] += 1
|
|
return counts
|
|
|
|
img_root.mkdir(parents=True, exist_ok=True)
|
|
remove_generated_pngs(img_root)
|
|
apply_mxpic_colors()
|
|
|
|
for py_file in sorted(src_root.rglob("*.py")):
|
|
if py_file.name == "__init__.py":
|
|
continue
|
|
|
|
module_name = module_name_for(py_file, src_root, package_root)
|
|
try:
|
|
module = importlib.import_module(module_name)
|
|
except Exception as error:
|
|
print(f"Skipped module import {module_name}: {error}")
|
|
counts["skipped"] += 1
|
|
continue
|
|
|
|
class_names, _ = public_top_level_members(py_file)
|
|
for class_name in class_names:
|
|
component_class = getattr(module, class_name, None)
|
|
if component_class is None:
|
|
print(f"Skipped missing class {module_name}.{class_name}")
|
|
counts["skipped"] += 1
|
|
continue
|
|
|
|
try:
|
|
target_dir = img_root / py_file.relative_to(src_root).parent
|
|
result = generate_image_for_class(target_dir, class_name, component_class)
|
|
except Exception as error:
|
|
plt.close()
|
|
print(f"Failed image for {module_name}.{class_name}: {error}")
|
|
counts["failed"] += 1
|
|
continue
|
|
|
|
counts[result] += 1
|
|
if result == "generated":
|
|
print(f"Generated: {target_dir / f'{class_name}.png'}")
|
|
else:
|
|
print(f"Skipped class without cell: {module_name}.{class_name}")
|
|
|
|
print(
|
|
"Image generation complete. "
|
|
f"Generated: {counts['generated']} | "
|
|
f"Skipped: {counts['skipped']} | "
|
|
f"Failures: {counts['failed']}"
|
|
)
|
|
return counts
|
|
|
|
|
|
if __name__ == "__main__":
|
|
generate_component_images(img_root=Path("docs/source/mxpic/components"))
|