pyedb 0.51.2__py3-none-any.whl → 0.53.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pyedb might be problematic. Click here for more details.
- pyedb/__init__.py +1 -1
- pyedb/configuration/cfg_common.py +12 -15
- pyedb/configuration/cfg_data.py +2 -2
- pyedb/configuration/cfg_modeler.py +163 -234
- pyedb/configuration/cfg_stackup.py +62 -249
- pyedb/configuration/configuration.py +356 -409
- pyedb/dotnet/database/components.py +9 -3
- pyedb/dotnet/database/dotnet/database.py +4 -0
- pyedb/dotnet/database/edb_data/layer_data.py +3 -1
- pyedb/dotnet/database/edb_data/padstacks_data.py +8 -2
- pyedb/dotnet/database/layout_validation.py +3 -13
- pyedb/dotnet/database/siwave.py +14 -0
- pyedb/dotnet/database/stackup.py +8 -61
- pyedb/dotnet/database/utilities/simulation_setup.py +1 -1
- pyedb/dotnet/database/utilities/siwave_cpa_simulation_setup.py +894 -0
- pyedb/dotnet/database/utilities/siwave_simulation_setup.py +15 -0
- pyedb/dotnet/edb.py +50 -3
- pyedb/generic/design_types.py +29 -0
- pyedb/generic/grpc_warnings.py +5 -0
- pyedb/grpc/database/__init__.py +0 -1
- pyedb/grpc/database/components.py +102 -81
- pyedb/grpc/database/control_file.py +240 -193
- pyedb/grpc/database/definition/materials.py +7 -7
- pyedb/grpc/database/definitions.py +7 -5
- pyedb/grpc/database/hierarchy/pin_pair_model.py +1 -1
- pyedb/grpc/database/modeler.py +105 -77
- pyedb/grpc/database/net/differential_pair.py +2 -1
- pyedb/grpc/database/simulation_setup/siwave_cpa_simulation_setup.py +961 -0
- pyedb/grpc/database/siwave.py +14 -0
- pyedb/grpc/database/source_excitations.py +10 -10
- pyedb/grpc/edb.py +156 -180
- pyedb/grpc/edb_init.py +4 -2
- pyedb/siwave_core/cpa/simulation_setup_data_model.py +132 -0
- pyedb/siwave_core/product_properties.py +198 -0
- {pyedb-0.51.2.dist-info → pyedb-0.53.0.dist-info}/METADATA +16 -14
- {pyedb-0.51.2.dist-info → pyedb-0.53.0.dist-info}/RECORD +38 -33
- {pyedb-0.51.2.dist-info → pyedb-0.53.0.dist-info}/WHEEL +1 -1
- {pyedb-0.51.2.dist-info → pyedb-0.53.0.dist-info/licenses}/LICENSE +0 -0
|
@@ -19,362 +19,35 @@
|
|
|
19
19
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
20
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
21
|
# SOFTWARE.
|
|
22
|
-
|
|
23
22
|
from datetime import datetime
|
|
24
23
|
import json
|
|
25
24
|
import os
|
|
26
25
|
from pathlib import Path
|
|
26
|
+
import warnings
|
|
27
27
|
|
|
28
28
|
import toml
|
|
29
29
|
|
|
30
|
+
from pyedb import Edb
|
|
30
31
|
from pyedb.configuration.cfg_data import CfgData
|
|
31
|
-
from pyedb.dotnet.database.definition.package_def import PackageDef
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
class Configuration:
|
|
35
35
|
"""Enables export and import of a JSON configuration file that can be applied to a new or existing design."""
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
def __init__(self, parent):
|
|
39
|
-
self.parent = parent
|
|
40
|
-
self._pedb = parent._pedb
|
|
41
|
-
|
|
42
|
-
def configuration_stackup(self, kwargs):
|
|
43
|
-
if kwargs.get("fix_padstack_def"):
|
|
44
|
-
from pyedb.configuration.cfg_padstacks import CfgPadstackDefinition
|
|
45
|
-
|
|
46
|
-
pedb_defs = self._pedb.padstacks.definitions
|
|
47
|
-
temp = []
|
|
48
|
-
for _, pdef in pedb_defs.items():
|
|
49
|
-
cfg_def = CfgPadstackDefinition(self._pedb, pdef)
|
|
50
|
-
cfg_def.api.retrieve_parameters_from_edb()
|
|
51
|
-
temp.append(cfg_def)
|
|
52
|
-
self.parent.cfg_data.stackup.apply()
|
|
53
|
-
for cfg_pdef in temp:
|
|
54
|
-
cfg_pdef.api.set_parameters_to_edb()
|
|
55
|
-
else:
|
|
56
|
-
temp_pdef_data = {}
|
|
57
|
-
from ansys.edb.core.definition.padstack_def_data import (
|
|
58
|
-
PadType as GrpcPadType,
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
for pdef_name, pdef in self._pedb.padstacks.definitions.items():
|
|
62
|
-
for layer in pdef.data.layer_names:
|
|
63
|
-
result = pdef.data.get_pad_parameters(layer, GrpcPadType.REGULAR_PAD)
|
|
64
|
-
if len(result) == 4:
|
|
65
|
-
# polygon based
|
|
66
|
-
temp_pdef_data[pdef_name] = pdef.data
|
|
67
|
-
break
|
|
68
|
-
self.parent.cfg_data.stackup.apply()
|
|
69
|
-
for pdef_name, pdef_data in temp_pdef_data.items():
|
|
70
|
-
pdef = self._pedb.padstacks.definitions[pdef_name]
|
|
71
|
-
pdef._padstack_def_data = pdef_data
|
|
72
|
-
|
|
73
|
-
def _load_stackup(self):
|
|
74
|
-
"""Imports stackup information from json."""
|
|
75
|
-
data = self.data["stackup"]
|
|
76
|
-
materials = data.get("materials")
|
|
77
|
-
|
|
78
|
-
if materials:
|
|
79
|
-
edb_materials = {i.lower(): i for i, _ in self._pedb.materials.materials.items()}
|
|
80
|
-
for mat in materials:
|
|
81
|
-
name = mat["name"].lower()
|
|
82
|
-
if name in edb_materials:
|
|
83
|
-
self._pedb.materials.delete_material(edb_materials[name])
|
|
84
|
-
for mat in materials:
|
|
85
|
-
self._pedb.materials.add_material(**mat)
|
|
86
|
-
|
|
87
|
-
layers = data.get("layers")
|
|
88
|
-
|
|
89
|
-
if layers:
|
|
90
|
-
input_signal_layers = [i for i in layers if i["type"].lower() == "signal"]
|
|
91
|
-
if not len(input_signal_layers) == len(self._pedb.stackup.signal_layers):
|
|
92
|
-
self._pedb.logger.error("Input signal layer count do not match.")
|
|
93
|
-
return False
|
|
94
|
-
|
|
95
|
-
removal_list = []
|
|
96
|
-
lc_signal_layers = []
|
|
97
|
-
for name, obj in self._pedb.stackup.all_layers.items():
|
|
98
|
-
if obj.type == "dielectric":
|
|
99
|
-
removal_list.append(name)
|
|
100
|
-
elif obj.type == "signal":
|
|
101
|
-
lc_signal_layers.append(obj.id)
|
|
102
|
-
for l in removal_list:
|
|
103
|
-
self._pedb.stackup.remove_layer(l)
|
|
104
|
-
|
|
105
|
-
# update all signal layers
|
|
106
|
-
id_name = {i[0]: i[1] for i in self._pedb.stackup.layers_by_id}
|
|
107
|
-
signal_idx = 0
|
|
108
|
-
for l in layers:
|
|
109
|
-
if l["type"] == "signal":
|
|
110
|
-
layer_id = lc_signal_layers[signal_idx]
|
|
111
|
-
layer_name = id_name[layer_id]
|
|
112
|
-
self._pedb.stackup.layers[layer_name].update(**l)
|
|
113
|
-
signal_idx = signal_idx + 1
|
|
114
|
-
|
|
115
|
-
# add all dielectric layers. Dielectric layers must be added last. Otherwise,
|
|
116
|
-
# dielectric layer will occupy signal and document layer id.
|
|
117
|
-
prev_layer_clone = None
|
|
118
|
-
l = layers.pop(0)
|
|
119
|
-
if l["type"] == "signal":
|
|
120
|
-
prev_layer_clone = self._pedb.stackup.layers[l["name"]]
|
|
121
|
-
else:
|
|
122
|
-
prev_layer_clone = self._pedb.stackup.add_layer_top(**l)
|
|
123
|
-
for idx, l in enumerate(layers):
|
|
124
|
-
if l["type"] == "dielectric":
|
|
125
|
-
prev_layer_clone = self._pedb.stackup.add_layer_below(
|
|
126
|
-
base_layer_name=prev_layer_clone.name, **l
|
|
127
|
-
)
|
|
128
|
-
elif l["type"] == "signal":
|
|
129
|
-
prev_layer_clone = self._pedb.stackup.layers[l["name"]]
|
|
130
|
-
|
|
131
|
-
def _load_package_def(self):
|
|
132
|
-
"""Imports package definition information from JSON."""
|
|
133
|
-
comps = self._pedb.components.instances
|
|
134
|
-
for pkgd in self.parent.data["package_definitions"]:
|
|
135
|
-
name = pkgd["name"]
|
|
136
|
-
if name in self._pedb.definitions.package:
|
|
137
|
-
self._pedb.definitions.package[name].delete()
|
|
138
|
-
extent_bounding_box = pkgd.get("extent_bounding_box", None)
|
|
139
|
-
if extent_bounding_box:
|
|
140
|
-
package_def = PackageDef(self._pedb, name=name, extent_bounding_box=extent_bounding_box)
|
|
141
|
-
else:
|
|
142
|
-
package_def = PackageDef(self._pedb, name=name, component_part_name=pkgd["component_definition"])
|
|
143
|
-
package_def.maximum_power = pkgd["maximum_power"]
|
|
144
|
-
package_def.therm_cond = pkgd["therm_cond"]
|
|
145
|
-
package_def.theta_jb = pkgd["theta_jb"]
|
|
146
|
-
package_def.theta_jc = pkgd["theta_jc"]
|
|
147
|
-
package_def.height = pkgd["height"]
|
|
148
|
-
|
|
149
|
-
heatsink = pkgd.get("heatsink", None)
|
|
150
|
-
if heatsink:
|
|
151
|
-
package_def.set_heatsink(
|
|
152
|
-
heatsink["fin_base_height"],
|
|
153
|
-
heatsink["fin_height"],
|
|
154
|
-
heatsink["fin_orientation"],
|
|
155
|
-
heatsink["fin_spacing"],
|
|
156
|
-
heatsink["fin_thickness"],
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
comp_def_name = pkgd["component_definition"]
|
|
160
|
-
comp_def = self._pedb.definitions.component[comp_def_name]
|
|
161
|
-
|
|
162
|
-
comp_list = dict()
|
|
163
|
-
if pkgd["apply_to_all"]:
|
|
164
|
-
comp_list.update(
|
|
165
|
-
{
|
|
166
|
-
refdes: comp
|
|
167
|
-
for refdes, comp in comp_def.components.items()
|
|
168
|
-
if refdes not in pkgd["components"]
|
|
169
|
-
}
|
|
170
|
-
)
|
|
171
|
-
else:
|
|
172
|
-
comp_list.update(
|
|
173
|
-
{refdes: comp for refdes, comp in comp_def.components.items() if refdes in pkgd["components"]}
|
|
174
|
-
)
|
|
175
|
-
for _, i in comp_list.items():
|
|
176
|
-
i.package_def = name
|
|
177
|
-
|
|
178
|
-
def get_data_from_db(self, **kwargs):
|
|
179
|
-
"""Get configuration data from layout.
|
|
180
|
-
|
|
181
|
-
Parameters
|
|
182
|
-
----------
|
|
183
|
-
stackup
|
|
184
|
-
|
|
185
|
-
Returns
|
|
186
|
-
-------
|
|
187
|
-
|
|
188
|
-
"""
|
|
189
|
-
self._pedb.logger.info("Getting data from layout database.")
|
|
190
|
-
data = {}
|
|
191
|
-
if kwargs.get("general", False):
|
|
192
|
-
data["general"] = self.parent.cfg_data.general.get_data_from_db()
|
|
193
|
-
if kwargs.get("stackup", False):
|
|
194
|
-
data["stackup"] = self.parent.cfg_data.stackup.get_data_from_db()
|
|
195
|
-
if kwargs.get("package_definitions", False):
|
|
196
|
-
data["package_definitions"] = self.parent.cfg_data.package_definitions.get_data_from_db()
|
|
197
|
-
if kwargs.get("setups", False):
|
|
198
|
-
setups = self.parent.cfg_data.setups
|
|
199
|
-
setups.retrieve_parameters_from_edb()
|
|
200
|
-
data["setups"] = setups.to_dict()
|
|
201
|
-
if kwargs.get("sources", False):
|
|
202
|
-
data["sources"] = self.parent.cfg_data.sources.get_data_from_db()
|
|
203
|
-
if kwargs.get("ports", False):
|
|
204
|
-
data["ports"] = self.parent.cfg_data.ports.get_data_from_db()
|
|
205
|
-
if kwargs.get("components", False) or kwargs.get("s_parameters", False):
|
|
206
|
-
self.parent.cfg_data.components.retrieve_parameters_from_edb()
|
|
207
|
-
components = []
|
|
208
|
-
for i in self.parent.cfg_data.components.components:
|
|
209
|
-
if i.type == "io":
|
|
210
|
-
components.append(i.get_attributes())
|
|
211
|
-
components.append(i.get_attributes())
|
|
212
|
-
|
|
213
|
-
if kwargs.get("components", False):
|
|
214
|
-
data["components"] = components
|
|
215
|
-
elif kwargs.get("s_parameters", False):
|
|
216
|
-
data["s_parameters"] = self.parent.cfg_data.s_parameters.get_data_from_db(components)
|
|
217
|
-
if kwargs.get("nets", False):
|
|
218
|
-
data["nets"] = self.parent.cfg_data.nets.get_data_from_db()
|
|
219
|
-
if kwargs.get("pin_groups", False):
|
|
220
|
-
data["pin_groups"] = self.parent.cfg_data.pin_groups.get_data_from_db()
|
|
221
|
-
if kwargs.get("operations", False):
|
|
222
|
-
data["operations"] = self.parent.cfg_data.operations.get_data_from_db()
|
|
223
|
-
if kwargs.get("padstacks", False):
|
|
224
|
-
self.parent.cfg_data.padstacks.retrieve_parameters_from_edb()
|
|
225
|
-
definitions = []
|
|
226
|
-
for i in self.parent.cfg_data.padstacks.definitions:
|
|
227
|
-
definitions.append(i.get_attributes())
|
|
228
|
-
instances = []
|
|
229
|
-
for i in self.parent.cfg_data.padstacks.instances:
|
|
230
|
-
instances.append(i.get_attributes())
|
|
231
|
-
data["padstacks"] = dict()
|
|
232
|
-
data["padstacks"]["definitions"] = definitions
|
|
233
|
-
data["padstacks"]["instances"] = instances
|
|
234
|
-
|
|
235
|
-
if kwargs.get("boundaries", False):
|
|
236
|
-
data["boundaries"] = self.parent.cfg_data.boundaries.get_data_from_db()
|
|
237
|
-
|
|
238
|
-
return data
|
|
239
|
-
|
|
240
|
-
def export(
|
|
241
|
-
self,
|
|
242
|
-
file_path,
|
|
243
|
-
stackup=True,
|
|
244
|
-
package_definitions=False,
|
|
245
|
-
setups=True,
|
|
246
|
-
sources=True,
|
|
247
|
-
ports=True,
|
|
248
|
-
nets=True,
|
|
249
|
-
pin_groups=True,
|
|
250
|
-
operations=True,
|
|
251
|
-
components=True,
|
|
252
|
-
boundaries=True,
|
|
253
|
-
s_parameters=True,
|
|
254
|
-
padstacks=True,
|
|
255
|
-
general=True,
|
|
256
|
-
):
|
|
257
|
-
"""Export the configuration data from layout to a file.
|
|
258
|
-
|
|
259
|
-
Parameters
|
|
260
|
-
----------
|
|
261
|
-
file_path : str, Path
|
|
262
|
-
File path to export the configuration data.
|
|
263
|
-
stackup : bool
|
|
264
|
-
Whether to export stackup or not.
|
|
265
|
-
package_definitions : bool
|
|
266
|
-
Whether to export package definitions or not.
|
|
267
|
-
setups : bool
|
|
268
|
-
Whether to export setups or not.
|
|
269
|
-
sources : bool
|
|
270
|
-
Whether to export sources or not.
|
|
271
|
-
ports : bool
|
|
272
|
-
Whether to export ports or not.
|
|
273
|
-
nets : bool
|
|
274
|
-
Whether to export nets.
|
|
275
|
-
pin_groups : bool
|
|
276
|
-
Whether to export pin groups.
|
|
277
|
-
operations : bool
|
|
278
|
-
Whether to export operations.
|
|
279
|
-
components : bool
|
|
280
|
-
Whether to export component.
|
|
281
|
-
boundaries : bool
|
|
282
|
-
Whether to export boundaries.
|
|
283
|
-
s_parameters : bool
|
|
284
|
-
Whether to export s_parameters.
|
|
285
|
-
padstacks : bool
|
|
286
|
-
Whether to export padstacks.
|
|
287
|
-
general : bool
|
|
288
|
-
Whether to export general information.
|
|
289
|
-
Returns
|
|
290
|
-
-------
|
|
291
|
-
bool
|
|
292
|
-
"""
|
|
293
|
-
data = self.get_data_from_db(
|
|
294
|
-
stackup=stackup,
|
|
295
|
-
package_definitions=package_definitions,
|
|
296
|
-
setups=setups,
|
|
297
|
-
sources=sources,
|
|
298
|
-
ports=ports,
|
|
299
|
-
nets=nets,
|
|
300
|
-
pin_groups=pin_groups,
|
|
301
|
-
operations=operations,
|
|
302
|
-
components=components,
|
|
303
|
-
boundaries=boundaries,
|
|
304
|
-
s_parameters=s_parameters,
|
|
305
|
-
padstacks=padstacks,
|
|
306
|
-
general=general,
|
|
307
|
-
)
|
|
308
|
-
|
|
309
|
-
file_path = file_path if isinstance(file_path, Path) else Path(file_path)
|
|
310
|
-
file_path = file_path.with_suffix(".json") if file_path.suffix == "" else file_path
|
|
311
|
-
|
|
312
|
-
for comp in data["components"]:
|
|
313
|
-
for key, value in comp.items():
|
|
314
|
-
try:
|
|
315
|
-
json.dumps(value)
|
|
316
|
-
print(f"Key '{key}' is serializable.")
|
|
317
|
-
except TypeError as e:
|
|
318
|
-
print(f"Key '{key}' failed: {e}")
|
|
319
|
-
|
|
320
|
-
with open(file_path, "w") as f:
|
|
321
|
-
if file_path.suffix == ".json":
|
|
322
|
-
json.dump(data, f, ensure_ascii=False, indent=4)
|
|
323
|
-
else:
|
|
324
|
-
toml.dump(data, f)
|
|
325
|
-
return True if os.path.isfile(file_path) else False
|
|
326
|
-
|
|
327
|
-
class DotNet(Grpc):
|
|
328
|
-
def __init__(self, parent):
|
|
329
|
-
super().__init__(parent)
|
|
330
|
-
|
|
331
|
-
def configuration_stackup(self, kwargs):
|
|
332
|
-
if kwargs.get("fix_padstack_def"):
|
|
333
|
-
from pyedb.configuration.cfg_padstacks import CfgPadstackDefinition
|
|
334
|
-
|
|
335
|
-
pedb_defs = self._pedb.padstacks.definitions
|
|
336
|
-
temp = []
|
|
337
|
-
for _, pdef in pedb_defs.items():
|
|
338
|
-
cfg_def = CfgPadstackDefinition(self._pedb, pdef)
|
|
339
|
-
cfg_def.api.retrieve_parameters_from_edb()
|
|
340
|
-
temp.append(cfg_def)
|
|
341
|
-
self.parent.cfg_data.stackup.apply()
|
|
342
|
-
for cfg_pdef in temp:
|
|
343
|
-
cfg_pdef.api.set_parameters_to_edb()
|
|
344
|
-
else:
|
|
345
|
-
temp_pdef_data = {}
|
|
346
|
-
for pdef_name, pdef in self._pedb.padstacks.definitions.items():
|
|
347
|
-
pdef_data = pdef._padstack_def_data
|
|
348
|
-
for lyr_name in list(pdef_data.GetLayerNames()):
|
|
349
|
-
result = pdef_data.GetPadParametersValue(
|
|
350
|
-
lyr_name, self._pedb._edb.Definition.PadType.RegularPad
|
|
351
|
-
)
|
|
352
|
-
flag, pad_shape, params, offset_x, offset_y, rotation = result
|
|
353
|
-
if flag is False:
|
|
354
|
-
result = pdef_data.GetPolygonalPadParameters(
|
|
355
|
-
lyr_name, self._pedb._edb.Definition.PadType.RegularPad
|
|
356
|
-
)
|
|
357
|
-
flag, polygon_data, offset_x, offset_y, rotation = result
|
|
358
|
-
if flag:
|
|
359
|
-
temp_pdef_data[pdef_name] = pdef_data
|
|
360
|
-
break
|
|
361
|
-
self.parent.cfg_data.stackup.apply()
|
|
362
|
-
for pdef_name, pdef_data in temp_pdef_data.items():
|
|
363
|
-
pdef = self._pedb.padstacks.definitions[pdef_name]
|
|
364
|
-
pdef._padstack_def_data = pdef_data
|
|
365
|
-
|
|
366
|
-
def __init__(self, pedb):
|
|
37
|
+
def __init__(self, pedb: Edb):
|
|
367
38
|
self._pedb = pedb
|
|
368
|
-
|
|
369
|
-
self.api = self.Grpc(self)
|
|
370
|
-
else:
|
|
371
|
-
self.api = self.DotNet(self)
|
|
39
|
+
|
|
372
40
|
self._components = self._pedb.components.instances
|
|
373
41
|
self.data = {}
|
|
374
42
|
self._s_parameter_library = ""
|
|
375
43
|
self._spice_model_library = ""
|
|
376
44
|
self.cfg_data = CfgData(self._pedb)
|
|
377
45
|
|
|
46
|
+
def __apply_with_logging(self, label: str, func):
|
|
47
|
+
start = datetime.now()
|
|
48
|
+
func()
|
|
49
|
+
self._pedb.logger.info(f"{label} finished. Time lapse {datetime.now() - start}")
|
|
50
|
+
|
|
378
51
|
def load(self, config_file, append=True, apply_file=False, output_file=None, open_at_the_end=True):
|
|
379
52
|
"""Import configuration settings from a configure file.
|
|
380
53
|
|
|
@@ -439,97 +112,295 @@ class Configuration:
|
|
|
439
112
|
|
|
440
113
|
def run(self, **kwargs):
|
|
441
114
|
"""Apply configuration settings to the current design"""
|
|
115
|
+
if kwargs.get("fix_padstack_def"):
|
|
116
|
+
warnings.warn("fix_padstack_def is deprecated.", DeprecationWarning)
|
|
442
117
|
|
|
443
|
-
|
|
444
|
-
self.cfg_data.variables.apply()
|
|
118
|
+
self.apply_variables()
|
|
445
119
|
|
|
446
120
|
if self.cfg_data.general:
|
|
447
121
|
self.cfg_data.general.apply()
|
|
448
122
|
|
|
449
123
|
# Configure boundary settings
|
|
450
|
-
now = datetime.now()
|
|
451
124
|
if self.cfg_data.boundaries:
|
|
452
|
-
self.cfg_data.boundaries.apply
|
|
453
|
-
self._pedb.logger.info(f"Updating boundaries finished. Time lapse {datetime.now() - now}")
|
|
454
|
-
now = datetime.now()
|
|
125
|
+
self.__apply_with_logging("Updating boundaries", self.cfg_data.boundaries.apply)
|
|
455
126
|
|
|
456
|
-
# Configure nets
|
|
457
127
|
if self.cfg_data.nets:
|
|
458
|
-
self.cfg_data.nets.apply
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
self.cfg_data.
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
self.cfg_data.pin_groups.apply()
|
|
469
|
-
self._pedb.logger.info(f"Creating pin groups finished. Time lapse {datetime.now() - now}")
|
|
470
|
-
now = datetime.now()
|
|
471
|
-
|
|
472
|
-
# Configure sources
|
|
473
|
-
self.cfg_data.sources.apply()
|
|
474
|
-
self._pedb.logger.info(f"Placing sources finished. Time lapse {datetime.now() - now}")
|
|
475
|
-
now = datetime.now()
|
|
476
|
-
|
|
477
|
-
# Configure setup
|
|
478
|
-
self.cfg_data.setups.apply()
|
|
479
|
-
self._pedb.logger.info(f"Creating setups finished. Time lapse {datetime.now() - now}")
|
|
480
|
-
now = datetime.now()
|
|
481
|
-
|
|
482
|
-
# Configure stackup
|
|
483
|
-
self.api.configuration_stackup(kwargs)
|
|
484
|
-
self._pedb.logger.info(f"Updating stackup finished. Time lapse {datetime.now() - now}")
|
|
485
|
-
now = datetime.now()
|
|
486
|
-
|
|
487
|
-
# Configure padstacks
|
|
128
|
+
self.__apply_with_logging("Updating nets", self.cfg_data.nets.apply)
|
|
129
|
+
|
|
130
|
+
self.__apply_with_logging("Updating components", self.cfg_data.components.apply)
|
|
131
|
+
self.__apply_with_logging("Creating pin groups", self.cfg_data.pin_groups.apply)
|
|
132
|
+
self.__apply_with_logging("Placing sources", self.cfg_data.sources.apply)
|
|
133
|
+
self.__apply_with_logging("Creating setups", self.cfg_data.setups.apply)
|
|
134
|
+
|
|
135
|
+
self.__apply_with_logging("Applying materials", self.apply_materials)
|
|
136
|
+
self.__apply_with_logging("Updating stackup", self.apply_stackup)
|
|
137
|
+
|
|
488
138
|
if self.cfg_data.padstacks:
|
|
489
|
-
self.cfg_data.padstacks.apply
|
|
490
|
-
self._pedb.logger.info(f"Applying padstacks finished. Time lapse {datetime.now() - now}")
|
|
491
|
-
now = datetime.now()
|
|
139
|
+
self.__apply_with_logging("Applying padstacks", self.cfg_data.padstacks.apply)
|
|
492
140
|
|
|
493
|
-
|
|
494
|
-
self.cfg_data.s_parameters.apply()
|
|
495
|
-
self._pedb.logger.info(f"Applying S-parameters finished. Time lapse {datetime.now() - now}")
|
|
496
|
-
now = datetime.now()
|
|
141
|
+
self.__apply_with_logging("Applying S-parameters", self.cfg_data.s_parameters.apply)
|
|
497
142
|
|
|
498
|
-
# Configure SPICE models
|
|
499
143
|
for spice_model in self.cfg_data.spice_models:
|
|
500
|
-
spice_model.apply
|
|
501
|
-
self._pedb.logger.info(f"Assigning Spice models finished. Time lapse {datetime.now() - now}")
|
|
502
|
-
now = datetime.now()
|
|
144
|
+
self.__apply_with_logging(f"Assigning Spice model {spice_model}", spice_model.apply)
|
|
503
145
|
|
|
504
|
-
|
|
505
|
-
self.
|
|
506
|
-
self.
|
|
507
|
-
|
|
146
|
+
self.__apply_with_logging("Applying package definitions", self.cfg_data.package_definitions.apply)
|
|
147
|
+
self.__apply_with_logging("Applying modeler", self.apply_modeler)
|
|
148
|
+
self.__apply_with_logging("Placing ports", self.cfg_data.ports.apply)
|
|
149
|
+
self.__apply_with_logging("Placing probes", self.cfg_data.probes.apply)
|
|
150
|
+
self.__apply_with_logging("Applying operations", self.cfg_data.operations.apply)
|
|
508
151
|
|
|
509
|
-
|
|
510
|
-
self.cfg_data.modeler.apply()
|
|
152
|
+
return True
|
|
511
153
|
|
|
512
|
-
|
|
513
|
-
self.cfg_data.
|
|
514
|
-
|
|
515
|
-
|
|
154
|
+
def apply_modeler(self):
|
|
155
|
+
modeler = self.cfg_data.modeler
|
|
156
|
+
if modeler.traces:
|
|
157
|
+
for t in modeler.traces:
|
|
158
|
+
if t.path:
|
|
159
|
+
obj = self._pedb.modeler.create_trace(
|
|
160
|
+
path_list=t.path,
|
|
161
|
+
layer_name=t.layer,
|
|
162
|
+
net_name=t.net_name,
|
|
163
|
+
width=t.width,
|
|
164
|
+
start_cap_style=t.start_cap_style,
|
|
165
|
+
end_cap_style=t.end_cap_style,
|
|
166
|
+
corner_style=t.corner_style,
|
|
167
|
+
)
|
|
168
|
+
obj.aedt_name = t.name
|
|
169
|
+
else:
|
|
170
|
+
obj = self._pedb.modeler.create_trace(
|
|
171
|
+
path_list=[t.incremental_path[0]],
|
|
172
|
+
layer_name=t.layer,
|
|
173
|
+
net_name=t.net_name,
|
|
174
|
+
width=t.width,
|
|
175
|
+
start_cap_style=t.start_cap_style,
|
|
176
|
+
end_cap_style=t.end_cap_style,
|
|
177
|
+
corner_style=t.corner_style,
|
|
178
|
+
)
|
|
179
|
+
obj.aedt_name = t.name
|
|
180
|
+
for x, y in t.incremental_path[1:]:
|
|
181
|
+
obj.add_point(x, y, True)
|
|
182
|
+
|
|
183
|
+
if modeler.padstack_defs:
|
|
184
|
+
for p in modeler.padstack_defs:
|
|
185
|
+
pdata = self._pedb._edb.Definition.PadstackDefData.Create()
|
|
186
|
+
pdef = self._pedb._edb.Definition.PadstackDef.Create(self._pedb.active_db, p.name)
|
|
187
|
+
pdef.SetData(pdata)
|
|
188
|
+
pdef = self._pedb.pedb_class.database.edb_data.padstacks_data.EDBPadstack(pdef, self._pedb.padstacks)
|
|
189
|
+
p.pyedb_obj = pdef
|
|
190
|
+
p.api.set_parameters_to_edb()
|
|
191
|
+
|
|
192
|
+
if modeler.padstack_instances:
|
|
193
|
+
for p in modeler.padstack_instances:
|
|
194
|
+
p_inst = self._pedb.padstacks.place(
|
|
195
|
+
via_name=p.name,
|
|
196
|
+
net_name=p.net_name,
|
|
197
|
+
position=p.position,
|
|
198
|
+
definition_name=p.definition,
|
|
199
|
+
rotation=p.rotation if p.rotation is not None else 0,
|
|
200
|
+
)
|
|
201
|
+
p.pyedb_obj = p_inst
|
|
202
|
+
p.api.set_parameters_to_edb()
|
|
203
|
+
|
|
204
|
+
if modeler.planes:
|
|
205
|
+
for p in modeler.planes:
|
|
206
|
+
if p.type == "rectangle":
|
|
207
|
+
obj = self._pedb.modeler.create_rectangle(
|
|
208
|
+
layer_name=p.layer,
|
|
209
|
+
net_name=p.net_name,
|
|
210
|
+
lower_left_point=p.lower_left_point,
|
|
211
|
+
upper_right_point=p.upper_right_point,
|
|
212
|
+
corner_radius=p.corner_radius,
|
|
213
|
+
rotation=p.rotation,
|
|
214
|
+
)
|
|
215
|
+
obj.aedt_name = p.name
|
|
216
|
+
elif p.type == "polygon":
|
|
217
|
+
obj = self._pedb.modeler.create_polygon(
|
|
218
|
+
main_shape=p.points, layer_name=p.layer, net_name=p.net_name
|
|
219
|
+
)
|
|
220
|
+
obj.aedt_name = p.name
|
|
221
|
+
elif p.type == "circle":
|
|
222
|
+
obj = self._pedb.modeler.create_circle(
|
|
223
|
+
layer_name=p.layer,
|
|
224
|
+
net_name=p.net_name,
|
|
225
|
+
x=p.position[0],
|
|
226
|
+
y=p.position[1],
|
|
227
|
+
radius=p.radius,
|
|
228
|
+
)
|
|
229
|
+
obj.aedt_name = p.name
|
|
230
|
+
else:
|
|
231
|
+
raise RuntimeError(f"Plane type {p.type} not supported")
|
|
232
|
+
|
|
233
|
+
for v in p.voids:
|
|
234
|
+
for i in self._pedb.layout.primitives:
|
|
235
|
+
if i.aedt_name == v:
|
|
236
|
+
self._pedb.modeler.add_void(obj, i)
|
|
237
|
+
|
|
238
|
+
if modeler.components:
|
|
239
|
+
for c in modeler.components:
|
|
240
|
+
obj = self._pedb.components.create(
|
|
241
|
+
c.pins,
|
|
242
|
+
component_name=c.reference_designator,
|
|
243
|
+
placement_layer=c.placement_layer,
|
|
244
|
+
component_part_name=c.definition,
|
|
245
|
+
)
|
|
246
|
+
c.pyedb_obj = obj
|
|
247
|
+
c.api.set_parameters_to_edb()
|
|
248
|
+
|
|
249
|
+
primitives = self._pedb.layout.find_primitive(**modeler.primitives_to_delete)
|
|
250
|
+
for i in primitives:
|
|
251
|
+
i.delete()
|
|
252
|
+
|
|
253
|
+
def apply_variables(self):
|
|
254
|
+
"""Set variables into database."""
|
|
255
|
+
inst = self.cfg_data.variables
|
|
256
|
+
for i in inst.variables:
|
|
257
|
+
if i.name.startswith("$"):
|
|
258
|
+
self._pedb.add_project_variable(i.name, i.value, i.description)
|
|
259
|
+
else:
|
|
260
|
+
self._pedb.add_design_variable(i.name, i.value, description=i.description)
|
|
516
261
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
self._pedb.
|
|
262
|
+
def get_variables(self):
|
|
263
|
+
"""Retrieve variables from database."""
|
|
264
|
+
for name, obj in self._pedb.design_variables.items():
|
|
265
|
+
self.cfg_data.variables.add_variable(name, obj.value_string, obj.description)
|
|
266
|
+
for name, obj in self._pedb.project_variables.items():
|
|
267
|
+
self.cfg_data.variables.add_variable(name, obj.value_string, obj.description)
|
|
520
268
|
|
|
521
|
-
|
|
522
|
-
|
|
269
|
+
def apply_materials(self):
|
|
270
|
+
"""Apply material settings to the current design"""
|
|
271
|
+
cfg_stackup = self.cfg_data.stackup
|
|
272
|
+
if len(cfg_stackup.materials):
|
|
273
|
+
materials_in_db = {i.lower(): i for i, _ in self._pedb.materials.materials.items()}
|
|
274
|
+
for mat_in_cfg in cfg_stackup.materials:
|
|
275
|
+
if mat_in_cfg.name.lower() in materials_in_db:
|
|
276
|
+
self._pedb.materials.delete_material(materials_in_db[mat_in_cfg.name.lower()])
|
|
523
277
|
|
|
524
|
-
|
|
278
|
+
attrs = mat_in_cfg.model_dump(exclude_none=True)
|
|
279
|
+
mat = self._pedb.materials.add_material(**attrs)
|
|
280
|
+
|
|
281
|
+
for i in attrs.get("thermal_modifiers", []):
|
|
282
|
+
mat.set_thermal_modifier(**i.to_dict())
|
|
283
|
+
|
|
284
|
+
def get_materials(self):
|
|
285
|
+
"""Retrieve materials from the current design.
|
|
286
|
+
|
|
287
|
+
Parameters
|
|
288
|
+
----------
|
|
289
|
+
append: bool, optional
|
|
290
|
+
If `True`, append materials to the current material list.
|
|
291
|
+
"""
|
|
525
292
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
293
|
+
self.cfg_data.stackup.materials = []
|
|
294
|
+
for name, mat in self._pedb.materials.materials.items():
|
|
295
|
+
self.cfg_data.stackup.add_material(**mat.to_dict())
|
|
296
|
+
|
|
297
|
+
def apply_stackup(self):
|
|
298
|
+
layers = self.cfg_data.stackup.layers
|
|
299
|
+
input_signal_layers = [i for i in layers if i.type.lower() == "signal"]
|
|
300
|
+
if len(input_signal_layers) == 0:
|
|
301
|
+
return
|
|
302
|
+
else: # Create materials with default properties used in stackup but not defined
|
|
303
|
+
materials = [m.name for m in self.cfg_data.stackup.materials]
|
|
304
|
+
for i in self.cfg_data.stackup.layers:
|
|
305
|
+
if i.type == "signal":
|
|
306
|
+
if i.material not in materials:
|
|
307
|
+
self.cfg_data.stackup.add_material(
|
|
308
|
+
name=i.material, **self._pedb.materials.default_conductor_property_values
|
|
309
|
+
)
|
|
529
310
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
311
|
+
if i.fill_material not in materials:
|
|
312
|
+
self.cfg_data.stackup.add_material(
|
|
313
|
+
name=i.material, **self._pedb.materials.default_dielectric_property_values
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
elif i.type == "dielectric":
|
|
317
|
+
if i.material not in materials:
|
|
318
|
+
self.cfg_data.stackup.add_material(
|
|
319
|
+
name=i.material, **self._pedb.materials.default_dielectric_property_values
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
if len(self._pedb.stackup.signal_layers) == 0:
|
|
323
|
+
self.__create_stackup()
|
|
324
|
+
elif not len(input_signal_layers) == len(self._pedb.stackup.signal_layers):
|
|
325
|
+
raise Exception(f"Input signal layer count do not match.")
|
|
326
|
+
else:
|
|
327
|
+
self.__update_stackup()
|
|
328
|
+
|
|
329
|
+
def __create_stackup(self):
|
|
330
|
+
layers_ = list()
|
|
331
|
+
layers_.extend(self.cfg_data.stackup.layers)
|
|
332
|
+
for l_attrs in layers_:
|
|
333
|
+
attrs = l_attrs.model_dump(exclude_none=True)
|
|
334
|
+
self._pedb.stackup.add_layer_bottom(**attrs)
|
|
335
|
+
|
|
336
|
+
def __update_stackup(self):
|
|
337
|
+
"""Apply layer settings to the current design"""
|
|
338
|
+
|
|
339
|
+
# After import stackup, padstacks lose their definitions. They need to be fixed after loading stackup
|
|
340
|
+
# step 1, archive padstack definitions
|
|
341
|
+
temp_pdef_data = {}
|
|
342
|
+
for pdef_name, pdef in self._pedb.padstacks.definitions.items():
|
|
343
|
+
pdef_edb_object = pdef._padstack_def_data
|
|
344
|
+
temp_pdef_data[pdef_name] = pdef_edb_object
|
|
345
|
+
# step 2, archive padstack instance layer map
|
|
346
|
+
temp_p_inst_layer_map = {}
|
|
347
|
+
for p_inst in self._pedb.layout.padstack_instances:
|
|
348
|
+
temp_p_inst_layer_map[p_inst.id] = p_inst._edb_object.GetLayerMap()
|
|
349
|
+
|
|
350
|
+
# ----------------------------------------------------------------------
|
|
351
|
+
# Apply stackup
|
|
352
|
+
layers = list()
|
|
353
|
+
layers.extend(self.cfg_data.stackup.layers)
|
|
354
|
+
|
|
355
|
+
removal_list = []
|
|
356
|
+
lc_signal_layers = []
|
|
357
|
+
for name, obj in self._pedb.stackup.all_layers.items():
|
|
358
|
+
if obj.type == "dielectric":
|
|
359
|
+
removal_list.append(name)
|
|
360
|
+
elif obj.type == "signal":
|
|
361
|
+
lc_signal_layers.append(obj.id)
|
|
362
|
+
for l in removal_list:
|
|
363
|
+
self._pedb.stackup.remove_layer(l)
|
|
364
|
+
|
|
365
|
+
# update all signal layers
|
|
366
|
+
id_name = {i[0]: i[1] for i in self._pedb.stackup.layers_by_id}
|
|
367
|
+
signal_idx = 0
|
|
368
|
+
for l in layers:
|
|
369
|
+
if l.type == "signal":
|
|
370
|
+
layer_id = lc_signal_layers[signal_idx]
|
|
371
|
+
layer_name = id_name[layer_id]
|
|
372
|
+
attrs = l.model_dump(exclude_none=True)
|
|
373
|
+
self._pedb.stackup.layers[layer_name].update(**attrs)
|
|
374
|
+
signal_idx = signal_idx + 1
|
|
375
|
+
|
|
376
|
+
# add all dielectric layers. Dielectric layers must be added last. Otherwise,
|
|
377
|
+
# dielectric layer will occupy signal and document layer id.
|
|
378
|
+
l = layers.pop(0)
|
|
379
|
+
if l.type == "signal":
|
|
380
|
+
prev_layer_clone = self._pedb.stackup.layers[l.name]
|
|
381
|
+
else:
|
|
382
|
+
attrs = l.model_dump(exclude_none=True)
|
|
383
|
+
prev_layer_clone = self._pedb.stackup.add_layer_top(**attrs)
|
|
384
|
+
for idx, l in enumerate(layers):
|
|
385
|
+
if l.type == "dielectric":
|
|
386
|
+
attrs = l.model_dump(exclude_none=True)
|
|
387
|
+
prev_layer_clone = self._pedb.stackup.add_layer_below(base_layer_name=prev_layer_clone.name, **attrs)
|
|
388
|
+
elif l.type == "signal":
|
|
389
|
+
prev_layer_clone = self._pedb.stackup.layers[l.name]
|
|
390
|
+
|
|
391
|
+
# ----------------------------------------------------------------------
|
|
392
|
+
# restore padstack definitions
|
|
393
|
+
for pdef_name, pdef_data in temp_pdef_data.items():
|
|
394
|
+
pdef = self._pedb.padstacks.definitions[pdef_name]
|
|
395
|
+
pdef._padstack_def_data = pdef_data
|
|
396
|
+
# restore padstack instance layer map
|
|
397
|
+
for p_inst in self._pedb.layout.padstack_instances:
|
|
398
|
+
p_inst._edb_object.SetLayerMap(temp_p_inst_layer_map[p_inst.id])
|
|
399
|
+
|
|
400
|
+
def get_stackup(self):
|
|
401
|
+
self.cfg_data.stackup.layers = []
|
|
402
|
+
for name, obj in self._pedb.stackup.all_layers.items():
|
|
403
|
+
self.cfg_data.stackup.add_layer_at_bottom(**obj.properties)
|
|
533
404
|
|
|
534
405
|
def get_data_from_db(self, **kwargs):
|
|
535
406
|
"""Get configuration data from layout.
|
|
@@ -542,7 +413,62 @@ class Configuration:
|
|
|
542
413
|
-------
|
|
543
414
|
|
|
544
415
|
"""
|
|
545
|
-
|
|
416
|
+
self._pedb.logger.info("Getting data from layout database.")
|
|
417
|
+
self.get_variables()
|
|
418
|
+
self.get_materials()
|
|
419
|
+
self.get_stackup()
|
|
420
|
+
|
|
421
|
+
data = {}
|
|
422
|
+
if kwargs.get("general", False):
|
|
423
|
+
data["general"] = self.cfg_data.general.get_data_from_db()
|
|
424
|
+
if kwargs.get("variables", False):
|
|
425
|
+
data["variables"] = self.cfg_data.variables.model_dump(exclude_none=True)
|
|
426
|
+
if kwargs.get("stackup", False):
|
|
427
|
+
data["stackup"] = self.cfg_data.stackup.model_dump(exclude_none=True)
|
|
428
|
+
if kwargs.get("package_definitions", False):
|
|
429
|
+
data["package_definitions"] = self.cfg_data.package_definitions.get_data_from_db()
|
|
430
|
+
if kwargs.get("setups", False):
|
|
431
|
+
setups = self.cfg_data.setups
|
|
432
|
+
setups.retrieve_parameters_from_edb()
|
|
433
|
+
data["setups"] = setups.to_dict()
|
|
434
|
+
if kwargs.get("sources", False):
|
|
435
|
+
data["sources"] = self.cfg_data.sources.get_data_from_db()
|
|
436
|
+
if kwargs.get("ports", False):
|
|
437
|
+
data["ports"] = self.cfg_data.ports.get_data_from_db()
|
|
438
|
+
if kwargs.get("components", False) or kwargs.get("s_parameters", False):
|
|
439
|
+
self.cfg_data.components.retrieve_parameters_from_edb()
|
|
440
|
+
components = []
|
|
441
|
+
for i in self.cfg_data.components.components:
|
|
442
|
+
if i.type == "io":
|
|
443
|
+
components.append(i.get_attributes())
|
|
444
|
+
components.append(i.get_attributes())
|
|
445
|
+
|
|
446
|
+
if kwargs.get("components", False):
|
|
447
|
+
data["components"] = components
|
|
448
|
+
elif kwargs.get("s_parameters", False):
|
|
449
|
+
data["s_parameters"] = self.cfg_data.s_parameters.get_data_from_db(components)
|
|
450
|
+
if kwargs.get("nets", False):
|
|
451
|
+
data["nets"] = self.cfg_data.nets.get_data_from_db()
|
|
452
|
+
if kwargs.get("pin_groups", False):
|
|
453
|
+
data["pin_groups"] = self.cfg_data.pin_groups.get_data_from_db()
|
|
454
|
+
if kwargs.get("operations", False):
|
|
455
|
+
data["operations"] = self.cfg_data.operations.get_data_from_db()
|
|
456
|
+
if kwargs.get("padstacks", False):
|
|
457
|
+
self.cfg_data.padstacks.retrieve_parameters_from_edb()
|
|
458
|
+
definitions = []
|
|
459
|
+
for i in self.cfg_data.padstacks.definitions:
|
|
460
|
+
definitions.append(i.get_attributes())
|
|
461
|
+
instances = []
|
|
462
|
+
for i in self.cfg_data.padstacks.instances:
|
|
463
|
+
instances.append(i.get_attributes())
|
|
464
|
+
data["padstacks"] = dict()
|
|
465
|
+
data["padstacks"]["definitions"] = definitions
|
|
466
|
+
data["padstacks"]["instances"] = instances
|
|
467
|
+
|
|
468
|
+
if kwargs.get("boundaries", False):
|
|
469
|
+
data["boundaries"] = self.cfg_data.boundaries.get_data_from_db()
|
|
470
|
+
|
|
471
|
+
return data
|
|
546
472
|
|
|
547
473
|
def export(
|
|
548
474
|
self,
|
|
@@ -560,6 +486,7 @@ class Configuration:
|
|
|
560
486
|
s_parameters=True,
|
|
561
487
|
padstacks=True,
|
|
562
488
|
general=True,
|
|
489
|
+
variables=True,
|
|
563
490
|
):
|
|
564
491
|
"""Export the configuration data from layout to a file.
|
|
565
492
|
|
|
@@ -593,12 +520,13 @@ class Configuration:
|
|
|
593
520
|
Whether to export padstacks.
|
|
594
521
|
general : bool
|
|
595
522
|
Whether to export general information.
|
|
523
|
+
variables : bool
|
|
524
|
+
Whether to export variable.
|
|
596
525
|
Returns
|
|
597
526
|
-------
|
|
598
527
|
bool
|
|
599
528
|
"""
|
|
600
|
-
|
|
601
|
-
file_path,
|
|
529
|
+
data = self.get_data_from_db(
|
|
602
530
|
stackup=stackup,
|
|
603
531
|
package_definitions=package_definitions,
|
|
604
532
|
setups=setups,
|
|
@@ -612,4 +540,23 @@ class Configuration:
|
|
|
612
540
|
s_parameters=s_parameters,
|
|
613
541
|
padstacks=padstacks,
|
|
614
542
|
general=general,
|
|
543
|
+
variables=variables,
|
|
615
544
|
)
|
|
545
|
+
|
|
546
|
+
file_path = file_path if isinstance(file_path, Path) else Path(file_path)
|
|
547
|
+
file_path = file_path.with_suffix(".json") if file_path.suffix == "" else file_path
|
|
548
|
+
|
|
549
|
+
for comp in data["components"]:
|
|
550
|
+
for key, value in comp.items():
|
|
551
|
+
try:
|
|
552
|
+
json.dumps(value)
|
|
553
|
+
print(f"Key '{key}' is serializable.")
|
|
554
|
+
except TypeError as e:
|
|
555
|
+
print(f"Key '{key}' failed: {e}")
|
|
556
|
+
|
|
557
|
+
with open(file_path, "w") as f:
|
|
558
|
+
if file_path.suffix == ".json":
|
|
559
|
+
json.dump(data, f, ensure_ascii=False, indent=4)
|
|
560
|
+
else:
|
|
561
|
+
toml.dump(data, f)
|
|
562
|
+
return True if os.path.isfile(file_path) else False
|