Repository navigation
Add dynamic preset support #41
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
69e5fbb
eeb5dad
36ccc5c
000c159
0160732
028b382
0408b60
5287418
e3a86bd
5463331
22fcd80
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -30,38 +30,58 @@ | |||||
| # ============================================================================ | ||||||
|
|
||||||
| # PDK Configuration paths | ||||||
| PDK_CONFIGS = { | ||||||
| 'IHP_SG13G2': { | ||||||
| 'name': 'IHP Open PDK (SG13G2)', | ||||||
| 'config_path': 'configs/ihp-sg13g2.yaml', | ||||||
| 'color_path': 'configs/colors/ihp-sg13g2/' | ||||||
| }, | ||||||
| 'IHP_SG13CMOS5L': { | ||||||
| 'name': 'IHP Open PDK (SG13CMOS5L)', | ||||||
| 'config_path': 'configs/ihp-sg13cmos5l.yaml', | ||||||
| 'color_path': 'configs/colors/ihp-sg13cmos5l/' | ||||||
| }, | ||||||
| 'SKY130': { | ||||||
| 'name': 'SkyWater SKY130 PDK', | ||||||
| 'config_path': 'configs/sky130.yaml', | ||||||
| 'color_path': 'configs/colors/sky130/' | ||||||
| }, | ||||||
| 'GF180MCU': { | ||||||
| 'name': 'GlobalFoundries GF180MCU PDK', | ||||||
| 'config_path': 'configs/gf180mcu.yaml', | ||||||
| 'color_path': 'configs/colors/gf180mcu/' | ||||||
| }, | ||||||
| 'SIEPIC_EBEAM': { | ||||||
| 'name': 'SiEPIC EBeam PDK', | ||||||
| 'config_path': 'configs/siepic.yaml', | ||||||
| 'color_path': 'configs/colors/siepic/' | ||||||
| }, | ||||||
| 'LNOI400': { | ||||||
| 'name': 'Luxtelligence LNOI400 PDK', | ||||||
| 'config_path': 'configs/lnoi400.yaml', | ||||||
| 'color_path': 'configs/colors/lnoi400/' | ||||||
| }, | ||||||
| } | ||||||
| def load_pdk_configs(configs_dir=Path(__file__).parent / "configs"): | ||||||
| pdks = {} | ||||||
| pdk_order = None | ||||||
|
|
||||||
| for config_path in configs_dir.iterdir(): | ||||||
| if not(config_path.is_file() and | ||||||
| config_path.suffix.lower() in [".yaml", ".yml"]): | ||||||
| continue | ||||||
|
|
||||||
| file_name = config_path.stem | ||||||
|
|
||||||
| # Get pdk_order | ||||||
| if file_name == "_config_order": | ||||||
| with open(config_path) as f: | ||||||
| pdk_order = yaml.safe_load(f) | ||||||
| continue | ||||||
|
|
||||||
| key = file_name.upper().replace('-', '_') # Expected key format | ||||||
|
|
||||||
| with open(config_path) as f: | ||||||
| config = yaml.safe_load(f) | ||||||
|
|
||||||
| pdks[key] = { | ||||||
| 'config_path': config_path, # Don't acces per-layer info yet | ||||||
| 'color_path': configs_dir / 'colors' / file_name, | ||||||
| 'file_name': file_name, | ||||||
| } | ||||||
| if "pdk_config" in config: | ||||||
| # name, description, def_color | ||||||
| pdks[key].update(**config["pdk_config"]) | ||||||
|
|
||||||
| pdks[key].setdefault("name", key) | ||||||
| pdks[key].setdefault("description", key) | ||||||
| pdks[key].setdefault("def_color", "") | ||||||
| # Sort "pdks" dictionary | ||||||
| if pdk_order is None: | ||||||
| return dict(sorted(pdks.items())) | ||||||
|
|
||||||
| sorted_pdks = {} | ||||||
| for item in pdk_order: | ||||||
| if item=="_": # Get all pdks not listed in pdk_order | ||||||
| remaining = { | ||||||
| k: v | ||||||
| for k, v in pdks.items() | ||||||
| if k not in pdk_order | ||||||
| } | ||||||
| sorted_pdks.update(dict(sorted(remaining.items()))) | ||||||
| elif item in pdks.keys(): | ||||||
| sorted_pdks[item] = pdks[item] | ||||||
| return sorted_pdks | ||||||
|
|
||||||
| PDK_CONFIGS = load_pdk_configs() | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't use a constant variable for those information. |
||||||
|
|
||||||
|
|
||||||
| # ============================================================================ | ||||||
|
|
@@ -232,7 +252,9 @@ def _create_merged_gds(gds_path, layerstack, tmp_dir): | |||||
| merged_layout.dbu = layout.dbu | ||||||
| merged_top_cell = merged_layout.create_cell(top_cell.name) | ||||||
|
|
||||||
| for data in layerstack.values(): | ||||||
| for layer_name, data in layerstack.items(): | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why skipping merge GDS if
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "layerstack.items()" is looping through the contents inside each .YAML file. Before this PR, only layer names were expected, but now that we have a non-layer name with non-layer contents, this should be explicitly skipped |
||||||
| if layer_name=="pdk_config": | ||||||
| continue | ||||||
| layer = (data['index'], data['type']) | ||||||
| layer_index = layout.layer(*layer) | ||||||
| region = db.Region(top_cell.begin_shapes_rec(layer_index)) | ||||||
|
|
@@ -335,21 +357,34 @@ def create_extruded_layer(report, gds_path, z, height, layer, name, color, unit= | |||||
| # PRE-IMPORT PDK SELECTION DIALOG | ||||||
| # ============================================================================ | ||||||
|
|
||||||
| def get_pdk_list(self, context): | ||||||
| """Dynamically generate pdk list""" | ||||||
|
|
||||||
| pdk_list = [] | ||||||
| for k,v in PDK_CONFIGS.items(): | ||||||
| pdk_list.append( | ||||||
| (k, v["name"], v["description"]) | ||||||
| ) | ||||||
| return pdk_list | ||||||
|
|
||||||
| def get_color_schemes(self, context): | ||||||
| """Dynamically generate color scheme list based on selected PDK""" | ||||||
| items = [] | ||||||
| # TODO: Load only when accessing list or when opening file explorer, not every refresh. Or add a refresh button | ||||||
|
|
||||||
| pdk = getattr(context.scene, 'gdsii_pdk_selection', 'IHP_SG13G2') | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Claude: nit: this fallback is still the old hardcoded
Suggested change
|
||||||
| addon_dir = Path(__file__).parent | ||||||
| color_path = addon_dir / PDK_CONFIGS.get(pdk, {}).get('color_path', pdk) | ||||||
| schemes = [('realistic', 'Realistic', 'Realistic color scheme')] | ||||||
| pdf_dict = PDK_CONFIGS.get(pdk, {}) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Call
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm having mixed feelings about this. This constant is being used across 7 functions. While that'd help to maintain latest folder status, I think it's a bit too much to call this every time. What's the usual way on handling these cases? Is it the way you suggest?🤔
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the function should be run inside "register_properties", GDSIIPreImportDialog class and Import GDSII class once, and pass around data for functions that need it. What do you think?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, loading it once and storing it internally is the right way. I think I already did this with other data. |
||||||
| color_path = addon_dir / pdf_dict.get('color_path', pdk) | ||||||
| schemes = [] | ||||||
| for file in color_path.glob('*yaml'): | ||||||
| # Make sure realistic is the default choice | ||||||
| if file.stem == 'realistic': | ||||||
| continue | ||||||
| color_file = yaml.safe_load(file.read_text(encoding='utf-8')) | ||||||
| schemes.append((file.stem, color_file.get('name', file.stem), | ||||||
| color_file.get('description', file.stem))) | ||||||
| if file.stem==pdf_dict["def_color"]: | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Claude: bug: if
Suggested change
|
||||||
|
|
||||||
| schemes.insert(0,(file.stem, color_file.get('name', file.stem), | ||||||
|
dnltz marked this conversation as resolved.
Resolved
|
||||||
| color_file.get('description', file.stem))) | ||||||
| else: | ||||||
| schemes.append((file.stem, color_file.get('name', file.stem), | ||||||
| color_file.get('description', file.stem))) | ||||||
| return schemes | ||||||
|
|
||||||
|
|
||||||
|
|
@@ -362,15 +397,7 @@ class GDSIIPreImportDialog(bpy.types.Operator): | |||||
| pdk_selection: EnumProperty( | ||||||
| name="PDK", | ||||||
| description="Process Design Kit to use for layer stack", | ||||||
| items=[ | ||||||
| ('IHP_SG13G2', "IHP Open PDK SG13G2", "IHP SG13G2 130nm BiCMOS process"), | ||||||
| ('IHP_SG13CMOS5L', "IHP Open PDK SG13CMOS5L", "IHP SG13CMOS5L 130nm CMOS5L process"), | ||||||
| ('SKY130', "SkyWater SKY130 PDK", "SkyWater SKY130 130nm process"), | ||||||
| ('GF180MCU', "GlobalFoundries GF180MCU PDK", "GlobalFoundries GF180MCU 180nm process"), | ||||||
| ('SIEPIC_EBEAM', "SiEPIC EBeam PDK", "SiEPIC EBeam silicon photonics PDK (220 nm SOI)"), | ||||||
| ('LNOI400', "Luxtelligence LNOI400 PDK", "Luxtelligence LNOI400 thin-film lithium niobate (X-cut, 400 nm LN, 200 nm etch) photonics PDK"), | ||||||
| ], | ||||||
| default='IHP_SG13G2', | ||||||
| items=get_pdk_list, | ||||||
| ) | ||||||
|
|
||||||
| use_custom_config: BoolProperty( | ||||||
|
|
@@ -407,10 +434,12 @@ def update_config_path(self): | |||||
| config_file = addon_dir / pdk_info['config_path'] | ||||||
| if config_file.exists(): | ||||||
| self.custom_config_path = str(config_file) | ||||||
| default_color = addon_dir / pdk_info['color_path'] / 'realistic.yaml' | ||||||
| if pdk_info["def_color"] == "": | ||||||
| return | ||||||
| default_color = addon_dir / pdk_info['color_path'] / f"{pdk_info['def_color']}.yaml" | ||||||
| if default_color.exists(): | ||||||
| self.custom_color_path = str(default_color) | ||||||
|
|
||||||
|
Gerard-Taulats marked this conversation as resolved.
Resolved
|
||||||
| def draw(self, context): | ||||||
| layout = self.layout | ||||||
|
|
||||||
|
|
@@ -499,7 +528,7 @@ class ImportGDSII(bpy.types.Operator, ImportHelper): | |||||
| color_scheme: EnumProperty( | ||||||
| name="Color Scheme", | ||||||
| description="Select color scheme for the PDK", | ||||||
| items=get_color_schemes | ||||||
| items=get_color_schemes, | ||||||
| ) | ||||||
|
|
||||||
| # Crop region options | ||||||
|
|
@@ -593,7 +622,7 @@ def draw(self, context): | |||||
| box = layout.box() | ||||||
| box.label(text="Selected PDK:", icon='PRESET') | ||||||
| if not use_custom: | ||||||
| pdk = getattr(context.scene, 'gdsii_pdk_selection', 'IHP_SG13G2') | ||||||
| pdk = getattr(context.scene, 'gdsii_pdk_selection', next(iter(PDK_CONFIGS))) | ||||||
| pdk_name = PDK_CONFIGS.get(pdk, {}).get('name', pdk) | ||||||
| else: | ||||||
| pdk_name = "Custom" | ||||||
|
|
@@ -606,7 +635,7 @@ def import_gdsii(self, context, filepath): | |||||
| """Main import function""" | ||||||
| try: | ||||||
| # Get PDK settings from scene | ||||||
| pdk_selection = getattr(context.scene, 'gdsii_pdk_selection', 'IHP_SG13G2') | ||||||
| pdk_selection = getattr(context.scene, 'gdsii_pdk_selection', next(iter(PDK_CONFIGS))) | ||||||
| use_custom = getattr(context.scene, 'gdsii_use_custom_config', False) | ||||||
| custom_config_path = getattr(context.scene, 'gdsii_custom_config_path', '') | ||||||
| custom_color_path = getattr(context.scene, 'gdsii_custom_color_path', '') | ||||||
|
|
@@ -618,7 +647,7 @@ def import_gdsii(self, context, filepath): | |||||
| # Use built-in PDK config | ||||||
| pdk_info = PDK_CONFIGS.get(pdk_selection, {}) | ||||||
| addon_dir = Path(__file__).parent | ||||||
| yamlfile = addon_dir / pdk_info.get('config_path', 'configs/ihp-sg13g2.yaml') | ||||||
| yamlfile = addon_dir / pdk_info.get('config_path', f"configs/{pdk_info["file_name"]}.yaml") | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Claude: bug: nested
Suggested change
|
||||||
|
|
||||||
| # Load layer stack configuration | ||||||
| if not yamlfile.is_file(): | ||||||
|
|
@@ -668,7 +697,7 @@ def import_gdsii(self, context, filepath): | |||||
| if use_custom: | ||||||
| colorfile = Path(custom_color_path) | ||||||
| else: | ||||||
| color_dir = addon_dir / pdk_info.get('color_path', 'configs/colors/ihp-sg13g2') | ||||||
| color_dir = addon_dir / pdk_info.get('color_path', f"configs/colors/{pdk_info["file_name"]}") | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Claude: bug: same nested-quote SyntaxError as the
Suggested change
|
||||||
| colorfile = color_dir / f"{self.color_scheme}.yaml" | ||||||
| if not colorfile.is_file(): | ||||||
| self.report({'ERROR'}, f"Color schema file not found: {colorfile}") | ||||||
|
|
@@ -690,6 +719,8 @@ def import_gdsii(self, context, filepath): | |||||
| # Import each layer from the stack | ||||||
| imported_count = 0 | ||||||
| for layer_name, data in layerstack.items(): | ||||||
| if layer_name=="pdk_config": | ||||||
| continue | ||||||
| z = data['z'] * self.z_scale | ||||||
| height = data['height'] * self.z_scale | ||||||
| layer_index = (data['index'], data['type']) | ||||||
|
|
@@ -743,7 +774,7 @@ def menu_func_import(self, context): | |||||
|
|
||||||
| # Properties to store PDK settings in scene | ||||||
| def register_properties(): | ||||||
| bpy.types.Scene.gdsii_pdk_selection = StringProperty(default='IHP_SG13G2') | ||||||
| bpy.types.Scene.gdsii_pdk_selection = StringProperty(default=next(iter(PDK_CONFIGS))) | ||||||
| bpy.types.Scene.gdsii_use_custom_config = BoolProperty(default=False) | ||||||
| bpy.types.Scene.gdsii_custom_config_path = StringProperty(default='') | ||||||
| bpy.types.Scene.gdsii_custom_color_path = StringProperty(default='') | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| # SPDX-FileCopyrightText: 2025 aesc silicon | ||
| # | ||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||
|
|
||
| - _ | ||
| - ihp-sg13g2 | ||
| - ihp-sg13cmos5l | ||
| - sky130 | ||
| - gf180mcu | ||
| - siepic | ||
| - lnoi400 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Claude: bug:
_config_order.yamllists raw file stems (ihp-sg13g2), butpdksis keyed by the uppercased form (IHP_SG13G2) from line 50 — soitem in pdks.keys()andk not in pdk_order(line 77) never match, and custom ordering silently falls back to alphabetical every time. Normalizeitem/pdk_orderentries with the same.upper().replace('-', '_')transform used on line 50 before comparing.