New forge coding added
This commit is contained in:
+95
-82
@@ -1,118 +1,131 @@
|
||||
import os
|
||||
import sys
|
||||
import importlib
|
||||
import inspect
|
||||
from pathlib import Path
|
||||
|
||||
import matplotlib
|
||||
import shutil
|
||||
matplotlib.use('Agg')
|
||||
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
import nazca as nd
|
||||
import mxpic as mx
|
||||
|
||||
# 2. Define your mxPIC Color Palette
|
||||
# You can map by your custom layer names (if defined) or raw GDS (layer, datatype) tuples.
|
||||
# Matplotlib accepts standard color names ('blue', 'cyan') or hex codes ('#FFD700').
|
||||
from generate_handbook import DEFAULT_PACKAGE_ROOT, DEFAULT_SRC_ROOT, public_top_level_members
|
||||
|
||||
|
||||
PALETTE = {
|
||||
"WG": "blue", # Example: Core waveguide layer
|
||||
"SLAB": "cyan", # Example: Shallow etch / Slab
|
||||
"M1": "#FFD700", # Example: Metal 1 (Gold)
|
||||
"M2": "silver", # Example: Metal 2
|
||||
"DEEP_TRENCH": "black", # Example: Trenching
|
||||
(1, 0): "darkred", # Fallback: You can use raw GDS tuples directly
|
||||
"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():
|
||||
"""Applies the custom color palette to Nazca's active layer map."""
|
||||
print("🎨 Applying mxPIC layer colors...")
|
||||
|
||||
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:
|
||||
# Nazca's built-in command to register plot colors
|
||||
nd.set_layercolor(layer=layer_id, color=color)
|
||||
except Exception:
|
||||
# If a specific layer name doesn't exist in the registry yet, it safely skips
|
||||
pass
|
||||
continue
|
||||
|
||||
def generate_component_images(img_root="images/components"):
|
||||
print("📸 Starting mxPIC Component Image Generation...")
|
||||
|
||||
# Define our source and target directories
|
||||
src_root = Path("mxpic/components")
|
||||
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)
|
||||
|
||||
# 1. Delete the directory and all its contents
|
||||
if img_root.exists() and img_root.is_dir():
|
||||
shutil.rmtree(img_root)
|
||||
|
||||
# 2. Recreate the directory
|
||||
img_root.mkdir(parents=True, exist_ok=True)
|
||||
src_root = Path(src_root)
|
||||
counts = {"generated": 0, "skipped": 0, "failed": 0}
|
||||
|
||||
if not src_root.exists():
|
||||
print(f"❌ Error: Source directory '{src_root}' not found.")
|
||||
sys.exit(1)
|
||||
print(f"Source directory not found: {src_root}")
|
||||
counts["failed"] += 1
|
||||
return counts
|
||||
|
||||
# Walk through all Python files in the components folder
|
||||
success_count = 0
|
||||
fail_count = 0
|
||||
|
||||
tapeout = mx.foundries.Silterra.EOM1_2ML_CU()
|
||||
img_root.mkdir(parents=True, exist_ok=True)
|
||||
remove_generated_pngs(img_root)
|
||||
apply_mxpic_colors()
|
||||
|
||||
for py_file in src_root.rglob("*.py"):
|
||||
for py_file in sorted(src_root.rglob("*.py")):
|
||||
if py_file.name == "__init__.py":
|
||||
continue
|
||||
|
||||
# Convert the file path to a Python module path (e.g., mxpic.components.mzm)
|
||||
rel_path = py_file.relative_to(src_root)
|
||||
module_name = "mxpic.components." + str(rel_path.with_suffix("")).replace(os.sep, ".")
|
||||
|
||||
module_name = module_name_for(py_file, src_root, package_root)
|
||||
try:
|
||||
# Dynamically import the module
|
||||
module = importlib.import_module(module_name)
|
||||
except Exception as e:
|
||||
print(f"⚠️ Could not import {module_name}: {e}")
|
||||
fail_count += 1
|
||||
except Exception as error:
|
||||
print(f"Skipped module import {module_name}: {error}")
|
||||
counts["skipped"] += 1
|
||||
continue
|
||||
|
||||
|
||||
# Scan the module for all defined classes
|
||||
for name, obj in inspect.getmembers(module, inspect.isclass):
|
||||
# Only process classes actually defined IN this file (ignore imported classes)
|
||||
if obj.__module__ == module_name:
|
||||
|
||||
# Determine where to save the image
|
||||
target_dir = img_root / rel_path.parent
|
||||
target_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# img_path = target_dir / f"{name}.png"
|
||||
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:
|
||||
# 1. Clear the Nazca canvas so components don't overlap!
|
||||
nd.clear_layout()
|
||||
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
|
||||
|
||||
# 2. Instantiate the class (assuming zero arguments)
|
||||
instance = obj()
|
||||
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}")
|
||||
|
||||
# 4. Export the image using Nazca
|
||||
nd.export_plt(path="",title=f"{name}",topcells=[instance.cell])
|
||||
# 3. Explicitly save to disk with tight borders
|
||||
plt.savefig(str(target_dir)+f"\\{name}.png", bbox_inches='tight', dpi=300)
|
||||
|
||||
# 4. CRITICAL: Clear the figure from RAM so the next loop is clean
|
||||
plt.close()
|
||||
|
||||
print(f"✅ Generated: {str(target_dir)}\\{name}.png")
|
||||
success_count += 1
|
||||
print(
|
||||
"Image generation complete. "
|
||||
f"Generated: {counts['generated']} | "
|
||||
f"Skipped: {counts['skipped']} | "
|
||||
f"Failures: {counts['failed']}"
|
||||
)
|
||||
return counts
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Failed to generate image for {name} in {module_name}: {e}")
|
||||
fail_count += 1
|
||||
|
||||
print("\n✨ Image generation complete!")
|
||||
print(f"📊 Success: {success_count} | Failures: {fail_count}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
generate_component_images(img_root="docs/source/mxpic/components/")
|
||||
generate_component_images(img_root=Path("docs/source/mxpic/components"))
|
||||
|
||||
Reference in New Issue
Block a user