Coverage for klayout_pex/pex25d/__init__.py: 98%
51 statements
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-06 13:14 +0000
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-06 13:14 +0000
1#
2# --------------------------------------------------------------------------------
3# SPDX-FileCopyrightText: 2024-2026 Martin Jan Köhler and Harald Pretl
4# Johannes Kepler University, Institute for Integrated Circuits.
5#
6# This file is part of KPEX
7# (see https://github.com/iic-jku/klayout-pex).
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <http://www.gnu.org/licenses/>.
21# SPDX-License-Identifier: GPL-3.0-or-later
22# --------------------------------------------------------------------------------
23#
24"""
25The PEX25D Module
26-----------------
28Support for the **PEX25D** interchange format for 2.5D parasitic extraction
29(see https://github.com/iic-jku/klayout-pex/issues/184).
31The pipeline is::
33 PEX25D (text) –[reader]→ PEX25DFile (protobuf) –[resolver]→ PEX25DScene (protobuf) –[adapter]→ solver
35One import covers it::
37 from klayout_pex import pex25d
39 file = pex25d.read('cell.pex25d')
40 scene = pex25d.resolve(file)
41 report = pex25d.validate(file, scene=scene)
42 pex25d.write(scene, 'cell.pex25d.scene.pb')
44``read`` and ``write`` take a path in any of the encodings the format defines,
45``read_text`` and ``write_text`` the bytes of the text format. Everything named
46in ``__all__`` is the supported surface; the submodules stay importable for the
47classes behind the verbs (``resolver.Resolver``, ``validator.Validator``).
49Dependency rule
50~~~~~~~~~~~~~~~
52This package depends on ``protobuf`` only — no ``klayout`` import, directly or
53transitively. That is what lets the standalone ``pex25d`` tool be installed and
54run by other groups as a reference validator, without dragging in the whole LVS
55machinery. Generating PEX25D from a layout does need that machinery, and lives
56in :mod:`klayout_pex.klayout.pex25d_builder` instead.
58The generated ``*_pb2`` modules are build output, so nothing here imports one
59while this module is imported: :data:`proto` and the verbs reach them on demand
60and report a missing build as :class:`ProtobufNotGeneratedError`.
61"""
63from __future__ import annotations
65from typing import *
67from . import protobuf
68from .artifact import (
69 ArtifactFormat,
70 ArtifactKind,
71 ArtifactNamingError,
72 ArtifactSpec,
73 STDIO_PATH,
74 derive_path,
75 infer_artifact_spec,
76)
77from .codec import load_artifact, save_artifact
78from .diagnostics import (
79 Diagnostic,
80 DiagnosticsFormat,
81 DiagnosticsReport,
82 ExitCode,
83 Severity,
84 SourceRef,
85 Tier,
86)
87from .exporters import (
88 ExportError,
89 ExporterExecutionError,
90 ExporterUnavailable,
91 export,
92 export_with_backend,
93 exporter_registry,
94)
95from .importers import (
96 ImporterError,
97 ImporterExecutionError,
98 ImporterUnavailable,
99 import_file,
100 importer_registry,
101)
102from .format_version import (
103 FORMAT_VERSION_MAJOR,
104 FORMAT_VERSION_MINOR,
105 FORMAT_VERSION_SUFFIX,
106)
107from .protobuf import ProtobufNotGeneratedError, kind_for_message
108from .reader import ReadError, read_pex25d_text
109from .resolver import ResolveError, resolve
110from .show import show
111from .validator import validate
112from .writer import WriteError, write_pex25d_text
114read_text = read_pex25d_text
115"""Parse the bytes of the PEX25D text format into a ``PEX25DFile``."""
117write_text = write_pex25d_text
118"""Render a ``PEX25DFile`` or ``PEX25DScene`` as PEX25D text bytes."""
121class _Proto:
122 """
123 The generated PEX25D protobuf modules and their top-level messages.
125 Nothing is listed here: the modules come from
126 :func:`protobuf.schema_names` and the messages from
127 :func:`protobuf.message_class_for_kind`, so a schema change that adds,
128 renames or removes a ``.proto`` needs no edit. Attribute access imports on
129 demand, so that a tree without the generated modules still imports this
130 package.
131 """
133 def _message_classes(self) -> Dict[str, Any]:
134 classes: Dict[str, Any] = {}
135 for kind in ArtifactKind:
136 try:
137 message_class = protobuf.message_class_for_kind(kind)
138 except ValueError: # a kind that describes no message, i.e. AUTO
139 continue
140 classes[message_class.DESCRIPTOR.name] = message_class
141 return classes
143 def __getattr__(self, name: str) -> Any:
144 if name.startswith('_'):
145 raise AttributeError(name)
147 if name in protobuf.schema_names():
148 return protobuf.schema_module(name)
150 message_classes = self._message_classes()
151 if name in message_classes:
152 return message_classes[name]
154 raise AttributeError(f"PEX25D has no protobuf module or message "
155 f"'{name}', expected one of {', '.join(dir(self))}")
157 def __dir__(self) -> List[str]:
158 return sorted([*protobuf.schema_names(), *self._message_classes()])
161proto = _Proto()
162"""The generated messages, e.g. ``proto.PEX25DFile()`` or ``proto.dielectric``."""
165kind_of = kind_for_message
166"""The :class:`ArtifactKind` a generated message holds."""
169def read(path: str,
170 kind: ArtifactKind = ArtifactKind.AUTO,
171 format: ArtifactFormat = ArtifactFormat.AUTO,
172 report: Optional[DiagnosticsReport] = None,
173 with_source_refs: bool = False) -> Any:
174 """
175 Read a PEX25D artifact and return the message it holds.
177 Kind and encoding come from the path's suffix, which is why the
178 conventional suffixes matter: ``.pex25d.scene.pb`` is a scene, ``.pex25d``
179 a file in text format. State them explicitly for a path that has none
180 (``-`` is stdin).
181 """
182 spec = infer_artifact_spec(path, kind=kind, format=format)
183 return load_artifact(spec, report=report, with_source_refs=with_source_refs)
186def write(message: Any,
187 path: str,
188 kind: ArtifactKind = ArtifactKind.AUTO,
189 format: ArtifactFormat = ArtifactFormat.AUTO,
190 comments: bool = False) -> None:
191 """
192 Write a ``PEX25DFile`` or ``PEX25DScene`` to ``path``.
194 The encoding comes from the suffix; the kind is the message's own, so that
195 a scene is never written under a spec that says file (``-`` is stdout).
197 :param comments: emit the specification's syntax hints. Text format only.
198 """
199 spec = infer_artifact_spec(path, kind=kind, format=format,
200 default_kind=kind_of(message))
201 save_artifact(message, spec, comments=comments)
204__all__ = [
205 # verbs
206 'read',
207 'read_text',
208 'write',
209 'write_text',
210 'resolve',
211 'validate',
212 'export',
213 'export_with_backend',
214 'exporter_registry',
215 'import_file',
216 'importer_registry',
217 'show',
218 # artifacts
219 'ArtifactFormat',
220 'ArtifactKind',
221 'ArtifactSpec',
222 'STDIO_PATH',
223 'derive_path',
224 'infer_artifact_spec',
225 'kind_of',
226 'load_artifact',
227 'save_artifact',
228 # diagnostics
229 'Diagnostic',
230 'DiagnosticsFormat',
231 'DiagnosticsReport',
232 'ExitCode',
233 'Severity',
234 'SourceRef',
235 'Tier',
236 # errors
237 'ArtifactNamingError',
238 'ExportError',
239 'ExporterExecutionError',
240 'ExporterUnavailable',
241 'ImporterError',
242 'ImporterExecutionError',
243 'ImporterUnavailable',
244 'ProtobufNotGeneratedError',
245 'ReadError',
246 'ResolveError',
247 'WriteError',
248 # the rest
249 'FORMAT_VERSION_MAJOR',
250 'FORMAT_VERSION_MINOR',
251 'FORMAT_VERSION_SUFFIX',
252 'proto',
253 # deprecated aliases, use read_text / write_text
254 'read_pex25d_text',
255 'write_pex25d_text',
256]