Coverage for klayout_pex/fastercap/pex25d_exporter.py: 96%

53 statements  

« 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 

25"""Protocol implementations for PEX25D FasterCap and FastCap2 exports. 

26 

27Both solvers use the same input format. Exporter instances hold no per-scene 

28state; each export builds its own model. Geometry dependencies load on export. 

29The STL exporter builds its solids with :func:`build_fastercap_model` for now. 

30""" 

31 

32from __future__ import annotations 

33 

34from dataclasses import dataclass, fields 

35import os 

36from typing import TYPE_CHECKING, Any, ClassVar, List, Mapping, Optional 

37 

38from ..plugin_api.v1 import ExportError, ExporterUnavailable, PEX25DSceneExporter 

39 

40if TYPE_CHECKING: 

41 from klayout_pex_protobuf.kpex.pex25d.pex25d_scene_pb2 import PEX25DScene 

42 

43 from .fastercap_model_generator import FasterCapModelGenerator 

44 

45 

46@dataclass 

47class FasterCapModelOptions: 

48 """Geometry and meshing settings of the FasterCap model.""" 

49 

50 delaunay_amax: float = 0.0 

51 """Maximum triangle area; 0 leaves it to the mesher.""" 

52 

53 delaunay_b: float = 1.0 

54 """Minimum mesh angle as b = 2·sin(angle); 1.0 is 30 degrees.""" 

55 

56 field_margin_um: float = 8.0 

57 """ 

58 How far the laterally unbounded materials — the simple bands, the films that 

59 cover the field, the background, the ground plane — are drawn beyond the 

60 geometry. Ignored when the scene carries a DOMAIN_BOX, which says it 

61 outright. 

62 """ 

63 

64 geometry_check: bool = False 

65 """Run the generator's own geometry validation before writing.""" 

66 

67 def __post_init__(self): 

68 for field in fields(self): 

69 value = getattr(self, field.name) 

70 if field.type == 'bool' and not isinstance(value, bool): 

71 raise TypeError(f"'{field.name}' must be a bool") 

72 if field.type == 'float' and (isinstance(value, bool) 

73 or not isinstance(value, (int, float))): 

74 raise TypeError(f"'{field.name}' must be an int or float (not bool)") 

75 

76 

77@dataclass 

78class FasterCapExporterOptions(FasterCapModelOptions): 

79 """FasterCap and FastCap2 settings: the model's, and STL alongside the solver input.""" 

80 

81 write_stl: bool = False 

82 """Also dump the generated solids as STL, for looking at.""" 

83 

84 

85def build_fastercap_model(scene: PEX25DScene, 

86 settings: FasterCapModelOptions, 

87 exporter_name: str) -> FasterCapModelGenerator: 

88 """Build the FasterCap model of ``scene``; KLayout loads here.""" 

89 try: 

90 from .pex25d_model_builder import PEX25DFasterCapModelBuilder 

91 except ImportError as exc: 

92 raise ExporterUnavailable( 

93 f"The {exporter_name} exporter needs KLayout and its geometry dependencies. " 

94 f"Original error: {exc}") from exc 

95 

96 generator = PEX25DFasterCapModelBuilder(scene, settings).build() 

97 if settings.geometry_check: 

98 generator.check() 

99 return generator 

100 

101 

102class FasterCapSceneExporter(PEX25DSceneExporter): 

103 """Write a FasterCap solver deck from a resolved PEX25D scene.""" 

104 

105 name: ClassVar[str] = 'fastercap' 

106 default_prefix: ClassVar[str] = 'FasterCap_Input_' 

107 

108 def export(self, 

109 scene: PEX25DScene, 

110 *, 

111 output_dir_path: str, 

112 prefix: str = '', 

113 options: Optional[Mapping[str, Any]] = None) -> List[str]: 

114 """Build and write solver inputs, returning the primary list file first.""" 

115 try: 

116 settings = FasterCapExporterOptions(**(options or {})) 

117 except TypeError as exc: 

118 raise ExportError(f"Invalid options for '{self.name}': {exc}") from exc 

119 

120 generator = build_fastercap_model(scene, settings, self.name) 

121 

122 os.makedirs(output_dir_path, exist_ok=True) 

123 effective_prefix = prefix or self.default_prefix 

124 written = generator.write_fastcap(output_dir_path=output_dir_path, 

125 prefix=effective_prefix) 

126 if settings.write_stl: 

127 written.extend(generator.dump_stl(output_dir_path=output_dir_path, 

128 prefix=effective_prefix)) 

129 return written 

130 

131 

132class FastCap2SceneExporter(FasterCapSceneExporter): 

133 """Write the shared solver input format with FastCap2 defaults.""" 

134 

135 name: ClassVar[str] = 'fastcap2' 

136 default_prefix: ClassVar[str] = 'FastCap_Input_'