Coverage for plugins/klayout-pex-plugin-gmsh/src/klayout_pex_plugins/gmsh/options.py: 97%
38 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: 2026 Martin Jan Köhler and Harald Pretl
4# Johannes Kepler University, Institute for Integrated Circuits.
5#
6# This file is part of klayout-pex-plugin-gmsh
7# (see https://github.com/iic-jku/klayout-pex-plugin-gmsh).
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#
25"""Mesh options, shared by the exporters built on this mesh (Elmer, Palace)."""
27from __future__ import annotations
29from dataclasses import dataclass, fields
30from typing import Any, Mapping, Optional, Type, TypeVar
32from klayout_pex.plugin_api.v1 import ExportError
34_TYPES = {
35 'bool': (bool,),
36 'int': (int,),
37 'float': (int, float),
38 'str': (str,),
39}
42@dataclass
43class GmshMeshOptions:
44 """Domain and mesh settings."""
46 field_margin_um: float = 8.0
47 """
48 How far the domain reaches beyond the finite geometry, sideways and above the
49 stack. Ignored when the scene carries a domain, which says it outright. Same
50 name and default as the FasterCap exporters' option.
51 """
53 mesh_size_edge_um: Optional[float] = None
54 """
55 Element size at conductor edges, where the field concentrates; elements grow
56 away from them. Unset: the thickness of the thinnest conductor layer.
57 """
59 mesh_size_max_um: Optional[float] = None
60 """Largest element size. Unset: 20 times the size at conductor edges."""
62 mesh_size_min_um: Optional[float] = None
63 """Smallest element size; unset leaves it to gmsh."""
65 verbose: bool = False
66 """Show gmsh's own output."""
68 def __post_init__(self):
69 for field in fields(self):
70 value = getattr(self, field.name)
71 optional = field.type.startswith('Optional[')
72 type_name = field.type[len('Optional['):-1] if optional else field.type
73 if value is None and optional:
74 continue
75 allowed = _TYPES[type_name]
76 if (isinstance(value, bool) and bool not in allowed) \
77 or not isinstance(value, allowed):
78 raise TypeError(f"'{field.name}' must be {type_name}"
79 f"{' or None' if optional else ''}, not {value!r}")
80 if self.field_margin_um < 0:
81 raise ValueError("'field_margin_um' must not be negative")
82 for name in ('mesh_size_edge_um', 'mesh_size_max_um', 'mesh_size_min_um'):
83 if getattr(self, name) is not None and getattr(self, name) <= 0:
84 raise ValueError(f"'{name}' must be positive")
87Options = TypeVar('Options', bound=GmshMeshOptions)
90def parse_options(cls: Type[Options], options: Optional[Mapping[str, Any]],
91 exporter_name: str) -> Options:
92 """Options as ``cls``; unknown names and wrong types are an ExportError."""
93 try:
94 return cls(**(options or {}))
95 except (TypeError, ValueError) as exc:
96 raise ExportError(f"Invalid options for '{exporter_name}': {exc}") from exc