Files
mxpic_EDA/backend/pdk_registry.py
T
2026-05-30 12:23:51 +08:00

101 lines
4.1 KiB
Python

# -----------------------------------------------------------------------------
# Description: PDK registry scanning helpers for finding component YAML/GDS assets and building library trees.
# Inside functions: __init__, resolve, _find_yaml, _find_gds, _load_yaml, _inside_root
# Developer : Qin Yue @ 2026
# Organization : OptiHK Limited
# -----------------------------------------------------------------------------
import os
from dataclasses import dataclass
from typing import Optional
import yaml
@dataclass
class PdkAsset:
component: str
yaml_path: Optional[str] = None
gds_path: Optional[str] = None
metadata: Optional[dict] = None
class PdkRegistry:
"""Resolve public PDK component names to metadata and public GDS assets."""
def __init__(self, public_root: str, prefer_full_gds: bool = False):
self.public_root = os.path.abspath(public_root)
self.prefer_full_gds = prefer_full_gds
self._asset_cache = {}
def resolve(self, component: str) -> PdkAsset:
key = (component or "").strip().replace("\\", "/").strip("/")
if not key:
return PdkAsset(component=component)
if key in self._asset_cache:
return self._asset_cache[key]
yaml_path = self._find_yaml(key)
gds_path = self._find_gds(key, yaml_path)
metadata = self._load_yaml(yaml_path) if yaml_path else None
asset = PdkAsset(component=component, yaml_path=yaml_path, gds_path=gds_path, metadata=metadata)
self._asset_cache[key] = asset
return asset
def _find_yaml(self, key: str) -> Optional[str]:
direct = os.path.join(self.public_root, *key.split("/"))
candidates = []
if direct.lower().endswith((".yml", ".yaml")):
candidates.append(direct)
else:
name = key.split("/")[-1]
candidates.append(os.path.join(direct, f"{name}.yml"))
candidates.append(os.path.join(direct, f"{name}.yaml"))
for candidate in candidates:
if self._inside_root(candidate) and os.path.exists(candidate):
return os.path.abspath(candidate)
name = key.split("/")[-1]
for root, dirs, files in os.walk(self.public_root):
if os.path.basename(root) == name:
for filename in files:
if filename.lower().endswith((".yml", ".yaml")):
return os.path.join(root, filename)
dirs.clear()
return None
def _find_gds(self, key: str, yaml_path: Optional[str]) -> Optional[str]:
search_dir = os.path.dirname(yaml_path) if yaml_path else os.path.join(self.public_root, *key.split("/"))
name = key.split("/")[-1]
if self.prefer_full_gds:
candidates = [
os.path.join(search_dir, f"{name}.gds"),
os.path.join(search_dir, f"{name}_BB.gds"),
]
else:
candidates = [
os.path.join(search_dir, f"{name}_BB.gds"),
os.path.join(search_dir, f"{name}.gds"),
]
for candidate in candidates:
if self._inside_root(candidate) and os.path.exists(candidate):
return os.path.abspath(candidate)
if os.path.isdir(search_dir):
gds_files = sorted(filename for filename in os.listdir(search_dir) if filename.lower().endswith(".gds"))
full_files = [filename for filename in gds_files if not filename.lower().endswith("_bb.gds")]
bb_files = [filename for filename in gds_files if filename.lower().endswith("_bb.gds")]
ordered = (full_files + bb_files) if self.prefer_full_gds else (bb_files + full_files)
if ordered:
return os.path.join(search_dir, ordered[0])
return None
def _load_yaml(self, yaml_path: Optional[str]) -> Optional[dict]:
if not yaml_path:
return None
with open(yaml_path, "r", encoding="utf-8") as file:
return yaml.safe_load(file) or {}
def _inside_root(self, path: str) -> bool:
target = os.path.abspath(path)
return target == self.public_root or target.startswith(self.public_root + os.sep)