-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlyp_parser.py
More file actions
108 lines (84 loc) · 4.33 KB
/
Copy pathlyp_parser.py
File metadata and controls
108 lines (84 loc) · 4.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# SPDX-License-Identifier: GPL-3.0-or-later
"""
KLayout Layer Properties (.lyp) File Parser
Parses LYP XML files to extract layer definitions (name -> layer/datatype mappings).
Provides text layer discovery for pin name extraction.
"""
import xml.etree.ElementTree as ET
from typing import Dict, List, Tuple, Optional
class LYPParser:
"""Parser for KLayout .lyp (layer properties) files"""
def __init__(self, lyp_path: str):
self.lyp_path = lyp_path
self.layers: Dict[str, Tuple[int, int]] = {} # name -> (layer, datatype)
self._parse(lyp_path)
def _parse(self, lyp_path: str):
"""Parse LYP XML file and extract layer definitions"""
try:
tree = ET.parse(lyp_path)
root = tree.getroot()
for props in root.findall('.//properties'):
name_elem = props.find('name')
source_elem = props.find('source')
if name_elem is not None and source_elem is not None:
name = name_elem.text
source = source_elem.text
if name and source:
try:
# Remove @N suffix if present (KLayout cell view indicator)
if '@' in source:
source = source.split('@')[0]
parts = source.split('/')
if len(parts) >= 2:
layer_num = int(parts[0])
datatype = int(parts[1])
# Clean layer name (some LYPs include "name - layer/dt")
clean_name = name.split(' - ')[0] if ' - ' in name else name
self.layers[clean_name] = (layer_num, datatype)
except (ValueError, IndexError):
pass
except FileNotFoundError:
# Raise rather than sys.exit: this is library code constructed
# in-process from the GUIs, whose `except Exception` cannot catch a
# SystemExit (a BaseException). The CLIs catch this at main().
raise FileNotFoundError(f"Could not find LYP file: {lyp_path}")
except ET.ParseError as e:
raise ValueError(f"Error parsing LYP file {lyp_path}: {e}")
def get_layer(self, name: str) -> Optional[Tuple[int, int]]:
"""Get layer number and datatype for given layer name"""
return self.layers.get(name)
def get_layer_names(self) -> List[str]:
"""Get list of all layer names, sorted alphabetically"""
return sorted(self.layers.keys())
def get_layer_display_names(self) -> List[str]:
"""Get list of layer names with layer/datatype info for display"""
result = []
for name in sorted(self.layers.keys()):
layer, datatype = self.layers[name]
result.append(f"{name} ({layer}/{datatype})")
return result
def find_text_layers_for(self, drawing_layer_name: str) -> List[str]:
"""Find candidate text layers for a given drawing layer.
Given a drawing layer like "TopMetal2.drawing" or "met4.drawing",
returns names of layers that likely contain text labels for its
geometries. Searches by name suffix conventions used across PDKs:
IHP SG13G2/Interposer: .text (dt 25), .pin (dt 2)
sky130: .label (dt 5), .pin (dt 16)
General: TEXT.drawing (63/0), text.drawing
Returns list of layer names that exist in the LYP file.
"""
candidates = []
# Extract base name (e.g. "TopMetal2" from "TopMetal2.drawing")
base_name = drawing_layer_name.split('.')[0] if '.' in drawing_layer_name else drawing_layer_name
# Check all common text/label/pin suffixes (PDK-agnostic)
for suffix in ['.text', '.label', '.pin']:
name = f"{base_name}{suffix}"
if name in self.layers:
candidates.append(name)
# Check for global text layers
for global_name in ["TEXT.drawing", "text.drawing"]:
if global_name in self.layers and global_name not in candidates:
candidates.append(global_name)
return candidates
def __repr__(self):
return f"LYPParser({len(self.layers)} layers from {self.lyp_path})"