All metadata + render SVG for a single library symbol.
| 212 | |
| 213 | |
| 214 | class Symbol: |
| 215 | """All metadata + render SVG for a single library symbol.""" |
| 216 | |
| 217 | __slots__ = ("name", "prefix", "nodes", "pins", "model", "model_show", |
| 218 | "params", "param_defaults", "param_display", |
| 219 | "refs", "description", "info", "show_pinnames", |
| 220 | "select_box", "svg", "g_xml") |
| 221 | |
| 222 | def __init__(self, g_element, source: str): |
| 223 | name = g_element.get("id") |
| 224 | if not name: |
| 225 | raise SymbolError(f"A <g> in {source} has no id= (symbol name).") |
| 226 | self.name = name |
| 227 | self.prefix = g_element.get("data-prefix", "") |
| 228 | self.nodes = (g_element.get("data-nodes") or "").split() |
| 229 | self.refs = (g_element.get("data-refs") or "").split() |
| 230 | self.model, self.model_show = _parse_model_field( |
| 231 | g_element.get("data-model", ""), source, name) |
| 232 | self.param_defaults, self.param_display = _parse_params_field( |
| 233 | g_element.get("data-params", ""), source, name) |
| 234 | self.params = list(self.param_defaults.keys()) |
| 235 | self.description = g_element.get("data-description", "") |
| 236 | self.info = g_element.get("data-info", "") |
| 237 | # Whether to draw the SLiCAP node names on the canvas. Block symbols |
| 238 | # (auto-generated subcircuit boxes) set this true because their shape |
| 239 | # carries no pin meaning; hand-drawn symbols leave it unset (false), as |
| 240 | # their artwork already identifies each pin. |
| 241 | self.show_pinnames = (g_element.get("data-show-pinnames", "") |
| 242 | .strip().lower() in ("true", "1", "yes")) |
| 243 | self.pins = self._read_pins(g_element, source) |
| 244 | |
| 245 | box = _geometry_bbox(g_element) |
| 246 | if box is None: |
| 247 | raise SymbolError( |
| 248 | f"Symbol '{name}' in {source} has no drawable geometry; its " |
| 249 | f"extent cannot be computed." |
| 250 | ) |
| 251 | x0, y0, x1, y1 = (float(v) for v in box) |
| 252 | self.select_box = (x0 - _BBOX_PAD, y0 - _BBOX_PAD, |
| 253 | (x1 - x0) + 2 * _BBOX_PAD, (y1 - y0) + 2 * _BBOX_PAD) |
| 254 | self.svg = self._render(g_element) |
| 255 | # Raw <g> source, kept so the symbol can be re-exported verbatim into a |
| 256 | # schematic's frozen <name>.symbols bundle. |
| 257 | self.g_xml = ET.tostring(g_element, encoding="unicode") |
| 258 | |
| 259 | def _read_pins(self, g_element, source: str) -> list[tuple[float, float]]: |
| 260 | """Pin coordinates in data-nodes order, from the node markers.""" |
| 261 | markers: dict[str, tuple[float, float]] = {} |
| 262 | for el in g_element.iter(): |
| 263 | if _local(el.tag) == "circle" and el.get("class") == "node": |
| 264 | node = el.get("data-node") |
| 265 | if node is not None: |
| 266 | markers[node] = (_f(el, "cx"), _f(el, "cy")) |
| 267 | pins = [] |
| 268 | for node in self.nodes: |
| 269 | if node not in markers: |
| 270 | raise SymbolError( |
| 271 | f"Symbol '{self.name}' in {source}: data-nodes lists '{node}' " |
no outgoing calls
no test coverage detected