Coverage for klayout_pex/pex25d/__init__.py: 98%
50 statements
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-17 19:08 +0000
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-17 19:08 +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 ExporterOptions,
90 ExporterUnavailable,
91 SolverTarget,
92 export,
93)
94from .format_version import (
95 FORMAT_VERSION_MAJOR,
96 FORMAT_VERSION_MINOR,
97 FORMAT_VERSION_SUFFIX,
98)
99from .protobuf import ProtobufNotGeneratedError, kind_for_message
100from .reader import ReadError, read_pex25d_text
101from .resolver import ResolveError, resolve
102from .show import show
103from .validator import validate
104from .writer import WriteError, write_pex25d_text
106read_text = read_pex25d_text
107"""Parse the bytes of the PEX25D text format into a ``PEX25DFile``."""
109write_text = write_pex25d_text
110"""Render a ``PEX25DFile`` or ``PEX25DScene`` as PEX25D text bytes."""
113class _Proto:
114 """
115 The generated PEX25D protobuf modules and their top-level messages.
117 Nothing is listed here: the modules come from
118 :func:`protobuf.schema_names` and the messages from
119 :func:`protobuf.message_class_for_kind`, so a schema change that adds,
120 renames or removes a ``.proto`` needs no edit. Attribute access imports on
121 demand, so that a tree without the generated modules still imports this
122 package.
123 """
125 def _message_classes(self) -> Dict[str, Any]:
126 classes: Dict[str, Any] = {}
127 for kind in ArtifactKind:
128 try:
129 message_class = protobuf.message_class_for_kind(kind)
130 except ValueError: # a kind that describes no message, i.e. AUTO
131 continue
132 classes[message_class.DESCRIPTOR.name] = message_class
133 return classes
135 def __getattr__(self, name: str) -> Any:
136 if name.startswith('_'):
137 raise AttributeError(name)
139 if name in protobuf.schema_names():
140 return protobuf.schema_module(name)
142 message_classes = self._message_classes()
143 if name in message_classes:
144 return message_classes[name]
146 raise AttributeError(f"PEX25D has no protobuf module or message "
147 f"'{name}', expected one of {', '.join(dir(self))}")
149 def __dir__(self) -> List[str]:
150 return sorted([*protobuf.schema_names(), *self._message_classes()])
153proto = _Proto()
154"""The generated messages, e.g. ``proto.PEX25DFile()`` or ``proto.dielectric``."""
157kind_of = kind_for_message
158"""The :class:`ArtifactKind` a generated message holds."""
161def read(path: str,
162 kind: ArtifactKind = ArtifactKind.AUTO,
163 format: ArtifactFormat = ArtifactFormat.AUTO,
164 report: Optional[DiagnosticsReport] = None,
165 with_source_refs: bool = False) -> Any:
166 """
167 Read a PEX25D artifact and return the message it holds.
169 Kind and encoding come from the path's suffix, which is why the
170 conventional suffixes matter: ``.pex25d.scene.pb`` is a scene, ``.pex25d``
171 a file in text format. State them explicitly for a path that has none
172 (``-`` is stdin).
173 """
174 spec = infer_artifact_spec(path, kind=kind, format=format)
175 return load_artifact(spec, report=report, with_source_refs=with_source_refs)
178def write(message: Any,
179 path: str,
180 kind: ArtifactKind = ArtifactKind.AUTO,
181 format: ArtifactFormat = ArtifactFormat.AUTO,
182 comments: bool = False) -> None:
183 """
184 Write a ``PEX25DFile`` or ``PEX25DScene`` to ``path``.
186 The encoding comes from the suffix; the kind is the message's own, so that
187 a scene is never written under a spec that says file (``-`` is stdout).
189 :param comments: emit the specification's syntax hints. Text format only.
190 """
191 spec = infer_artifact_spec(path, kind=kind, format=format,
192 default_kind=kind_of(message))
193 save_artifact(message, spec, comments=comments)
196__all__ = [
197 # verbs
198 'read',
199 'read_text',
200 'write',
201 'write_text',
202 'resolve',
203 'validate',
204 'export',
205 'show',
206 # artifacts
207 'ArtifactFormat',
208 'ArtifactKind',
209 'ArtifactSpec',
210 'STDIO_PATH',
211 'derive_path',
212 'infer_artifact_spec',
213 'kind_of',
214 'load_artifact',
215 'save_artifact',
216 # diagnostics
217 'Diagnostic',
218 'DiagnosticsFormat',
219 'DiagnosticsReport',
220 'ExitCode',
221 'Severity',
222 'SourceRef',
223 'Tier',
224 # errors
225 'ArtifactNamingError',
226 'ExportError',
227 'ExporterUnavailable',
228 'ProtobufNotGeneratedError',
229 'ReadError',
230 'ResolveError',
231 'WriteError',
232 # the rest
233 'ExporterOptions',
234 'SolverTarget',
235 'FORMAT_VERSION_MAJOR',
236 'FORMAT_VERSION_MINOR',
237 'FORMAT_VERSION_SUFFIX',
238 'proto',
239 # deprecated aliases, use read_text / write_text
240 'read_pex25d_text',
241 'write_pex25d_text',
242]