pyedb 0.52.0__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.

@@ -27,14 +27,14 @@ 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
- def __init__(self, pedb):
37
+ def __init__(self, pedb: Edb):
38
38
  self._pedb = pedb
39
39
 
40
40
  self._components = self._pedb.components.instances
@@ -43,6 +43,11 @@ class Configuration:
43
43
  self._spice_model_library = ""
44
44
  self.cfg_data = CfgData(self._pedb)
45
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
+
46
51
  def load(self, config_file, append=True, apply_file=False, output_file=None, open_at_the_end=True):
47
52
  """Import configuration settings from a configure file.
48
53
 
@@ -110,206 +115,292 @@ class Configuration:
110
115
  if kwargs.get("fix_padstack_def"):
111
116
  warnings.warn("fix_padstack_def is deprecated.", DeprecationWarning)
112
117
 
113
- if self.cfg_data.variables:
114
- self.cfg_data.variables.apply()
118
+ self.apply_variables()
115
119
 
116
120
  if self.cfg_data.general:
117
121
  self.cfg_data.general.apply()
118
122
 
119
123
  # Configure boundary settings
120
- now = datetime.now()
121
124
  if self.cfg_data.boundaries:
122
- self.cfg_data.boundaries.apply()
123
- self._pedb.logger.info(f"Updating boundaries finished. Time lapse {datetime.now() - now}")
124
- now = datetime.now()
125
+ self.__apply_with_logging("Updating boundaries", self.cfg_data.boundaries.apply)
125
126
 
126
- # Configure nets
127
127
  if self.cfg_data.nets:
128
- self.cfg_data.nets.apply()
129
- self._pedb.logger.info(f"Updating nets finished. Time lapse {datetime.now() - now}")
130
- now = datetime.now()
131
-
132
- # Configure components
133
- self.cfg_data.components.apply()
134
- self._pedb.logger.info(f"Updating components finished. Time lapse {datetime.now() - now}")
135
- now = datetime.now()
136
-
137
- # Configure pin groups
138
- self.cfg_data.pin_groups.apply()
139
- self._pedb.logger.info(f"Creating pin groups finished. Time lapse {datetime.now() - now}")
140
- now = datetime.now()
141
-
142
- # Configure sources
143
- self.cfg_data.sources.apply()
144
- self._pedb.logger.info(f"Placing sources finished. Time lapse {datetime.now() - now}")
145
- now = datetime.now()
146
-
147
- # Configure setup
148
- self.cfg_data.setups.apply()
149
- self._pedb.logger.info(f"Creating setups finished. Time lapse {datetime.now() - now}")
150
- now = datetime.now()
151
-
152
- # Configure stackup
153
- self.configuration_stackup()
154
- self._pedb.logger.info(f"Updating stackup finished. Time lapse {datetime.now() - now}")
155
- now = datetime.now()
156
-
157
- # 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
+
158
138
  if self.cfg_data.padstacks:
159
- self.cfg_data.padstacks.apply()
160
- self._pedb.logger.info(f"Applying padstacks finished. Time lapse {datetime.now() - now}")
161
- now = datetime.now()
139
+ self.__apply_with_logging("Applying padstacks", self.cfg_data.padstacks.apply)
162
140
 
163
- # Configure S-parameter
164
- self.cfg_data.s_parameters.apply()
165
- self._pedb.logger.info(f"Applying S-parameters finished. Time lapse {datetime.now() - now}")
166
- now = datetime.now()
141
+ self.__apply_with_logging("Applying S-parameters", self.cfg_data.s_parameters.apply)
167
142
 
168
- # Configure SPICE models
169
143
  for spice_model in self.cfg_data.spice_models:
170
- spice_model.apply()
171
- self._pedb.logger.info(f"Assigning Spice models finished. Time lapse {datetime.now() - now}")
172
- now = datetime.now()
144
+ self.__apply_with_logging(f"Assigning Spice model {spice_model}", spice_model.apply)
145
+
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)
151
+
152
+ return True
153
+
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)
173
261
 
174
- # Configure package definitions
175
- self.cfg_data.package_definitions.apply()
176
- self._pedb.logger.info(f"Applying package definitions finished. Time lapse {datetime.now() - now}")
177
- now = datetime.now()
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)
178
268
 
179
- # Modeler
180
- self.cfg_data.modeler.apply()
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()])
181
277
 
182
- # Configure ports
183
- self.cfg_data.ports.apply()
184
- self._pedb.logger.info(f"Placing ports finished. Time lapse {datetime.now() - now}")
185
- now = datetime.now()
278
+ attrs = mat_in_cfg.model_dump(exclude_none=True)
279
+ mat = self._pedb.materials.add_material(**attrs)
186
280
 
187
- # Configure probes
188
- self.cfg_data.probes.apply()
189
- self._pedb.logger.info(f"Placing probes finished. Time lapse {datetime.now() - now}")
281
+ for i in attrs.get("thermal_modifiers", []):
282
+ mat.set_thermal_modifier(**i.to_dict())
190
283
 
191
- # Configure operations
192
- self.cfg_data.operations.apply()
284
+ def get_materials(self):
285
+ """Retrieve materials from the current design.
193
286
 
194
- return True
287
+ Parameters
288
+ ----------
289
+ append: bool, optional
290
+ If `True`, append materials to the current material list.
291
+ """
292
+
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
+ )
310
+
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)
195
335
 
196
- def configuration_stackup(self):
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
197
341
  temp_pdef_data = {}
198
342
  for pdef_name, pdef in self._pedb.padstacks.definitions.items():
199
343
  pdef_edb_object = pdef._padstack_def_data
200
344
  temp_pdef_data[pdef_name] = pdef_edb_object
201
-
345
+ # step 2, archive padstack instance layer map
202
346
  temp_p_inst_layer_map = {}
203
347
  for p_inst in self._pedb.layout.padstack_instances:
204
348
  temp_p_inst_layer_map[p_inst.id] = p_inst._edb_object.GetLayerMap()
205
349
 
206
- self.cfg_data.stackup.apply()
207
-
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
208
393
  for pdef_name, pdef_data in temp_pdef_data.items():
209
394
  pdef = self._pedb.padstacks.definitions[pdef_name]
210
395
  pdef._padstack_def_data = pdef_data
211
-
396
+ # restore padstack instance layer map
212
397
  for p_inst in self._pedb.layout.padstack_instances:
213
398
  p_inst._edb_object.SetLayerMap(temp_p_inst_layer_map[p_inst.id])
214
399
 
215
- def _load_stackup(self):
216
- """Imports stackup information from json."""
217
- data = self.data["stackup"]
218
- materials = data.get("materials")
219
-
220
- if materials:
221
- edb_materials = {i.lower(): i for i, _ in self._pedb.materials.materials.items()}
222
- for mat in materials:
223
- name = mat["name"].lower()
224
- if name in edb_materials:
225
- self._pedb.materials.delete_material(edb_materials[name])
226
- for mat in materials:
227
- self._pedb.materials.add_material(**mat)
228
-
229
- layers = data.get("layers")
230
-
231
- if layers:
232
- input_signal_layers = [i for i in layers if i["type"].lower() == "signal"]
233
- if not len(input_signal_layers) == len(self._pedb.stackup.signal_layers):
234
- self._pedb.logger.error("Input signal layer count do not match.")
235
- return False
236
-
237
- removal_list = []
238
- lc_signal_layers = []
239
- for name, obj in self._pedb.stackup.all_layers.items():
240
- if obj.type == "dielectric":
241
- removal_list.append(name)
242
- elif obj.type == "signal":
243
- lc_signal_layers.append(obj.id)
244
- for l in removal_list:
245
- self._pedb.stackup.remove_layer(l)
246
-
247
- # update all signal layers
248
- id_name = {i[0]: i[1] for i in self._pedb.stackup.layers_by_id}
249
- signal_idx = 0
250
- for l in layers:
251
- if l["type"] == "signal":
252
- layer_id = lc_signal_layers[signal_idx]
253
- layer_name = id_name[layer_id]
254
- self._pedb.stackup.layers[layer_name].update(**l)
255
- signal_idx = signal_idx + 1
256
-
257
- # add all dielectric layers. Dielectric layers must be added last. Otherwise,
258
- # dielectric layer will occupy signal and document layer id.
259
- prev_layer_clone = None
260
- l = layers.pop(0)
261
- if l["type"] == "signal":
262
- prev_layer_clone = self._pedb.stackup.layers[l["name"]]
263
- else:
264
- prev_layer_clone = self._pedb.stackup.add_layer_top(**l)
265
- for idx, l in enumerate(layers):
266
- if l["type"] == "dielectric":
267
- prev_layer_clone = self._pedb.stackup.add_layer_below(base_layer_name=prev_layer_clone.name, **l)
268
- elif l["type"] == "signal":
269
- prev_layer_clone = self._pedb.stackup.layers[l["name"]]
270
-
271
- def _load_package_def(self):
272
- """Imports package definition information from JSON."""
273
- comps = self._pedb.components.instances
274
- for pkgd in self.data["package_definitions"]:
275
- name = pkgd["name"]
276
- if name in self._pedb.definitions.package:
277
- self._pedb.definitions.package[name].delete()
278
- extent_bounding_box = pkgd.get("extent_bounding_box", None)
279
- if extent_bounding_box:
280
- package_def = PackageDef(self._pedb, name=name, extent_bounding_box=extent_bounding_box)
281
- else:
282
- package_def = PackageDef(self._pedb, name=name, component_part_name=pkgd["component_definition"])
283
- package_def.maximum_power = pkgd["maximum_power"]
284
- package_def.therm_cond = pkgd["therm_cond"]
285
- package_def.theta_jb = pkgd["theta_jb"]
286
- package_def.theta_jc = pkgd["theta_jc"]
287
- package_def.height = pkgd["height"]
288
-
289
- heatsink = pkgd.get("heatsink", None)
290
- if heatsink:
291
- package_def.set_heatsink(
292
- heatsink["fin_base_height"],
293
- heatsink["fin_height"],
294
- heatsink["fin_orientation"],
295
- heatsink["fin_spacing"],
296
- heatsink["fin_thickness"],
297
- )
298
-
299
- comp_def_name = pkgd["component_definition"]
300
- comp_def = self._pedb.definitions.component[comp_def_name]
301
-
302
- comp_list = dict()
303
- if pkgd["apply_to_all"]:
304
- comp_list.update(
305
- {refdes: comp for refdes, comp in comp_def.components.items() if refdes not in pkgd["components"]}
306
- )
307
- else:
308
- comp_list.update(
309
- {refdes: comp for refdes, comp in comp_def.components.items() if refdes in pkgd["components"]}
310
- )
311
- for _, i in comp_list.items():
312
- i.package_def = name
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)
313
404
 
314
405
  def get_data_from_db(self, **kwargs):
315
406
  """Get configuration data from layout.
@@ -323,11 +414,17 @@ class Configuration:
323
414
 
324
415
  """
325
416
  self._pedb.logger.info("Getting data from layout database.")
417
+ self.get_variables()
418
+ self.get_materials()
419
+ self.get_stackup()
420
+
326
421
  data = {}
327
422
  if kwargs.get("general", False):
328
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)
329
426
  if kwargs.get("stackup", False):
330
- data["stackup"] = self.cfg_data.stackup.get_data_from_db()
427
+ data["stackup"] = self.cfg_data.stackup.model_dump(exclude_none=True)
331
428
  if kwargs.get("package_definitions", False):
332
429
  data["package_definitions"] = self.cfg_data.package_definitions.get_data_from_db()
333
430
  if kwargs.get("setups", False):
@@ -389,6 +486,7 @@ class Configuration:
389
486
  s_parameters=True,
390
487
  padstacks=True,
391
488
  general=True,
489
+ variables=True,
392
490
  ):
393
491
  """Export the configuration data from layout to a file.
394
492
 
@@ -422,6 +520,8 @@ class Configuration:
422
520
  Whether to export padstacks.
423
521
  general : bool
424
522
  Whether to export general information.
523
+ variables : bool
524
+ Whether to export variable.
425
525
  Returns
426
526
  -------
427
527
  bool
@@ -440,6 +540,7 @@ class Configuration:
440
540
  s_parameters=s_parameters,
441
541
  padstacks=padstacks,
442
542
  general=general,
543
+ variables=variables,
443
544
  )
444
545
 
445
546
  file_path = file_path if isinstance(file_path, Path) else Path(file_path)
@@ -1597,9 +1597,15 @@ class Components(object):
1597
1597
  >>> edbapp.components.create(pins, "A1New")
1598
1598
 
1599
1599
  """
1600
- _pins = [p._edb_object for p in pins if isinstance(p, EDBPadstackInstance)]
1601
- if _pins:
1602
- pins = _pins
1600
+ _pins = []
1601
+ for p in pins:
1602
+ if isinstance(p, EDBPadstackInstance):
1603
+ _pins.append(p._edb_object)
1604
+ elif isinstance(p, str):
1605
+ _pins.append(self._pedb.padstacks.instances_by_name[p]._edb_object)
1606
+ else:
1607
+ _pins.append(p)
1608
+ pins = _pins
1603
1609
  if not component_name:
1604
1610
  component_name = generate_unique_name("Comp_")
1605
1611
  if component_part_name:
@@ -24,6 +24,7 @@
24
24
  import os
25
25
  import re
26
26
  import sys
27
+ import warnings
27
28
 
28
29
  from pyedb import __version__
29
30
  from pyedb.dotnet.database.general import convert_py_list_to_net_list
@@ -34,6 +35,7 @@ from pyedb.generic.general_methods import (
34
35
  is_linux,
35
36
  settings,
36
37
  )
38
+ from pyedb.generic.grpc_warnings import GRPC_GENERAL_WARNING
37
39
  from pyedb.misc.misc import list_installed_ansysem
38
40
 
39
41
 
@@ -710,6 +712,8 @@ class EdbDotNet(object):
710
712
  except IndexError:
711
713
  raise Exception("No ANSYSEM_ROOTxxx is found.")
712
714
  self.edbversion = edbversion
715
+ if float(self.edbversion) >= 2025.2:
716
+ warnings.warn(GRPC_GENERAL_WARNING, UserWarning)
713
717
  self.student_version = student_version
714
718
  """Initialize DLLs."""
715
719
  from pyedb.dotnet.clr_module import _clr, edb_initialized
@@ -76,7 +76,9 @@ class LayerEdbClass(object):
76
76
 
77
77
  @fill_material.setter
78
78
  def fill_material(self, value):
79
- self._edb_object.SetFillMaterial(value)
79
+ layer_clone = self._edb_layer
80
+ layer_clone.SetFillMaterial(value)
81
+ self._pedb.stackup._set_layout_stackup(layer_clone, "change_attribute")
80
82
 
81
83
  @property
82
84
  def _stackup_layer_mapping(self):
@@ -40,6 +40,9 @@ from pyedb.dotnet.database.edb_data.sources import (
40
40
  VoltageSource,
41
41
  )
42
42
  from pyedb.dotnet.database.general import convert_py_list_to_net_list
43
+ from pyedb.dotnet.database.utilities.siwave_cpa_simulation_setup import (
44
+ SIWaveCPASimulationSetup,
45
+ )
43
46
  from pyedb.generic.constants import SolverType, SweepType
44
47
  from pyedb.generic.general_methods import _retry_ntimes, generate_unique_name
45
48
  from pyedb.misc.siw_feature_config.xtalk_scan.scan_config import SiwaveScanConfig
@@ -1507,6 +1510,17 @@ class EdbSiwave(object):
1507
1510
  """
1508
1511
  return SiwaveScanConfig(self._pedb, scan_type)
1509
1512
 
1513
+ def add_cpa_analysis(self, name=None, siwave_cpa_setup_class=None):
1514
+ if not name:
1515
+ from pyedb.generic.general_methods import generate_unique_name
1516
+
1517
+ if not siwave_cpa_setup_class:
1518
+ name = generate_unique_name("cpa_setup")
1519
+ else:
1520
+ name = siwave_cpa_setup_class.name
1521
+ cpa_setup = SIWaveCPASimulationSetup(self._pedb, name=name, siwave_cpa_setup_class=siwave_cpa_setup_class)
1522
+ return cpa_setup
1523
+
1510
1524
  @icepak_use_minimal_comp_defaults.setter
1511
1525
  def icepak_use_minimal_comp_defaults(self, value):
1512
1526
  value = "True" if bool(value) else ""