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

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# 

24 

25"""Mesh options, shared by the exporters built on this mesh (Elmer, Palace).""" 

26 

27from __future__ import annotations 

28 

29from dataclasses import dataclass, fields 

30from typing import Any, Mapping, Optional, Type, TypeVar 

31 

32from klayout_pex.plugin_api.v1 import ExportError 

33 

34_TYPES = { 

35 'bool': (bool,), 

36 'int': (int,), 

37 'float': (int, float), 

38 'str': (str,), 

39} 

40 

41 

42@dataclass 

43class GmshMeshOptions: 

44 """Domain and mesh settings.""" 

45 

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 """ 

52 

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 """ 

58 

59 mesh_size_max_um: Optional[float] = None 

60 """Largest element size. Unset: 20 times the size at conductor edges.""" 

61 

62 mesh_size_min_um: Optional[float] = None 

63 """Smallest element size; unset leaves it to gmsh.""" 

64 

65 verbose: bool = False 

66 """Show gmsh's own output.""" 

67 

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") 

85 

86 

87Options = TypeVar('Options', bound=GmshMeshOptions) 

88 

89 

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