pyedb 0.57.0__py3-none-any.whl → 0.59.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.

Files changed (46) hide show
  1. pyedb/__init__.py +1 -1
  2. pyedb/configuration/cfg_pin_groups.py +2 -0
  3. pyedb/dotnet/database/cell/hierarchy/component.py +2 -8
  4. pyedb/dotnet/database/cell/layout.py +1 -1
  5. pyedb/dotnet/database/components.py +38 -42
  6. pyedb/dotnet/database/edb_data/control_file.py +13 -5
  7. pyedb/dotnet/database/edb_data/padstacks_data.py +34 -12
  8. pyedb/dotnet/database/edb_data/sources.py +21 -2
  9. pyedb/dotnet/database/general.py +4 -8
  10. pyedb/dotnet/database/layout_validation.py +8 -0
  11. pyedb/dotnet/database/sim_setup_data/data/settings.py +2 -2
  12. pyedb/dotnet/database/sim_setup_data/io/siwave.py +53 -0
  13. pyedb/dotnet/database/stackup.py +5 -32
  14. pyedb/dotnet/database/utilities/hfss_simulation_setup.py +81 -0
  15. pyedb/dotnet/database/utilities/siwave_simulation_setup.py +259 -11
  16. pyedb/dotnet/edb.py +26 -13
  17. pyedb/extensions/create_cell_array.py +48 -44
  18. pyedb/generic/general_methods.py +24 -36
  19. pyedb/generic/plot.py +8 -23
  20. pyedb/generic/process.py +78 -10
  21. pyedb/grpc/database/components.py +7 -5
  22. pyedb/grpc/database/control_file.py +13 -5
  23. pyedb/grpc/database/definition/padstack_def.py +10 -5
  24. pyedb/grpc/database/hierarchy/component.py +2 -9
  25. pyedb/grpc/database/modeler.py +28 -8
  26. pyedb/grpc/database/padstacks.py +62 -103
  27. pyedb/grpc/database/primitive/padstack_instance.py +41 -12
  28. pyedb/grpc/database/primitive/path.py +13 -13
  29. pyedb/grpc/database/simulation_setup/hfss_simulation_setup.py +79 -0
  30. pyedb/grpc/database/source_excitations.py +7 -7
  31. pyedb/grpc/database/stackup.py +5 -33
  32. pyedb/grpc/database/terminal/padstack_instance_terminal.py +9 -11
  33. pyedb/grpc/database/terminal/point_terminal.py +30 -0
  34. pyedb/grpc/database/terminal/terminal.py +16 -2
  35. pyedb/grpc/database/utility/xml_control_file.py +13 -5
  36. pyedb/grpc/edb.py +46 -20
  37. pyedb/grpc/edb_init.py +7 -19
  38. pyedb/misc/aedtlib_personalib_install.py +2 -2
  39. pyedb/misc/downloads.py +18 -3
  40. pyedb/misc/siw_feature_config/emc_rule_checker_settings.py +2 -1
  41. pyedb/misc/siw_feature_config/xtalk_scan/scan_config.py +0 -1
  42. pyedb/workflows/sipi/hfss_auto_configuration.py +711 -0
  43. {pyedb-0.57.0.dist-info → pyedb-0.59.0.dist-info}/METADATA +4 -5
  44. {pyedb-0.57.0.dist-info → pyedb-0.59.0.dist-info}/RECORD +46 -45
  45. {pyedb-0.57.0.dist-info → pyedb-0.59.0.dist-info}/WHEEL +0 -0
  46. {pyedb-0.57.0.dist-info → pyedb-0.59.0.dist-info}/licenses/LICENSE +0 -0
@@ -185,6 +185,7 @@ class SiwaveSimulationSetup(SimulationSetup):
185
185
 
186
186
  @enabled.setter
187
187
  def enabled(self, value: bool):
188
+ """Set the enabled flag for the setup."""
188
189
  self.sim_setup_info.simulation_settings.Enabled = value
189
190
 
190
191
  @property
@@ -324,6 +325,21 @@ class SiwaveDCSimulationSetup(SimulationSetup):
324
325
  self.set_dc_slider(1)
325
326
  return self
326
327
 
328
+ @property
329
+ def enabled(self):
330
+ """Flag indicating if the setup is enabled.
331
+
332
+ .. deprecated:: 0.57.0
333
+ Use :property:`settings.enabled` property instead.
334
+
335
+ Returns
336
+ -------
337
+ bool
338
+ """
339
+
340
+ warnings.warn("`enabled` property is deprecated. Use `settings.enabled` property instead.", DeprecationWarning)
341
+ return self.settings.enabled
342
+
327
343
  @property
328
344
  def sim_setup_info(self):
329
345
  """Overrides the default sim_setup_info object."""
@@ -346,7 +362,7 @@ class SiwaveDCSimulationSetup(SimulationSetup):
346
362
  @property
347
363
  def dc_ir_settings(self):
348
364
  """DC IR settings."""
349
- return SiwaveDCIRSettings(self)
365
+ return self.settings
350
366
 
351
367
  def get_configurations(self):
352
368
  """Get SIwave DC simulation settings.
@@ -370,36 +386,270 @@ class SiwaveDCSimulationSetup(SimulationSetup):
370
386
  - ``1``: Balanced
371
387
  - ``2``: Optimal accuracy
372
388
  """
373
- self.use_custom_settings = False
374
- self.dc_settings.dc_slider_position = value
375
- self.dc_advanced_settings.set_dc_slider(value)
389
+ self.settings.use_custom_settings = False
390
+ self.settings.dc.dc_slider_position = value
391
+ self.settings.dc_advanced.set_dc_slider(value)
376
392
 
377
393
  @property
378
394
  def dc_settings(self):
379
- """SIwave DC setting."""
380
- return DCSettings(self)
395
+ """SIwave DC setting.
396
+
397
+ deprecated:: 0.57.0
398
+ Use :property:`settings` property instead.
399
+
400
+ """
401
+ warnings.warn("`dc_settings` is deprecated. Use `settings.dc` property instead.", DeprecationWarning)
402
+ return self.settings.dc
403
+
404
+ @property
405
+ def settings(self):
406
+ """Get the settings interface for SIwave DC simulation.
407
+
408
+ Returns
409
+ -------
410
+ Settings
411
+ An instance of the Settings class providing access to SIwave DC simulation settings.
412
+ """
413
+ return Settings(self, self.sim_setup_info)
381
414
 
382
415
  @property
383
416
  def dc_advanced_settings(self):
384
417
  """Siwave DC advanced settings.
385
418
 
419
+ .. deprecated :: 0.57.0
420
+ Use :property:`settings` property instead.
421
+
386
422
  Returns
387
423
  -------
388
424
  :class:`pyedb.dotnet.database.edb_data.siwave_simulation_setup_data.SiwaveDCAdvancedSettings`
389
425
  """
390
- return DCAdvancedSettings(self)
426
+ warnings.warn(
427
+ "`dc_advanced_settings` is deprecated. Use `settings.dc_advanced` property instead.", DeprecationWarning
428
+ )
429
+ return self.settings.dc_advanced
391
430
 
392
431
  @property
393
432
  def source_terms_to_ground(self):
394
433
  """Dictionary of grounded terminals.
395
434
 
435
+ .. deprecated:: 0.57.0
436
+ Use :property:`settings.source_terms_to_ground` property instead.
437
+
396
438
  Returns
397
439
  -------
398
440
  Dictionary
399
441
  {str, int}, keys is source name, value int 0 unspecified, 1 negative node, 2 positive one.
400
442
 
401
443
  """
402
- return convert_netdict_to_pydict(self.get_sim_setup_info.simulation_settings.DCIRSettings.SourceTermsToGround)
444
+ warnings.warn(
445
+ "`source_terms_to_ground` is deprecated. Use `settings.source_terms_to_ground` property instead.",
446
+ DeprecationWarning,
447
+ )
448
+ return self.settings.source_terms_to_ground
449
+
450
+ def add_source_terminal_to_ground(self, source_name, terminal=0):
451
+ """Add a source terminal to ground.
452
+
453
+ .. deprecated:: 0.57.0
454
+ Use :method:`settings.add_source_terminal_to_ground` method instead.
455
+
456
+ Parameters
457
+ ----------
458
+ source_name : str,
459
+ Source name.
460
+ terminal : int, optional
461
+ Terminal to assign. Options are:
462
+
463
+ - 0=Unspecified
464
+ - 1=Negative node
465
+ - 2=Positive none
466
+
467
+ Returns
468
+ -------
469
+ bool
470
+
471
+ """
472
+ warnings.warn(
473
+ "`add_source_terminal_to_ground` is deprecated. Use "
474
+ "`settings.add_source_terminal_to_ground` method instead.",
475
+ DeprecationWarning,
476
+ )
477
+ return self.settings.add_source_terminal_to_ground(source_name, terminal)
478
+
479
+
480
+ class General:
481
+ """Class to manage global settings for the Siwave simulation setup module.
482
+ Added to be compliant with ansys-edbe-core settings structure."""
483
+
484
+ def __init__(self, parent):
485
+ self._parent = parent
486
+
487
+ @property
488
+ def pi_slider_pos(self):
489
+ return self._parent.dc_slider_position
490
+
491
+ @property
492
+ def si_slider_pos(self):
493
+ return self._parent.si_slider_position
494
+
495
+ @property
496
+ def use_custom_settings(self):
497
+ return self._parent.use_dc_custom_settings
498
+
499
+ @property
500
+ def use_si_settings(self):
501
+ return self._parent.use_si_settings
502
+
503
+
504
+ class SIwaveSParameterSettings:
505
+ def __init__(self, parent):
506
+ self._parent = parent
507
+
508
+ @property
509
+ def dc_behavior(self):
510
+ return
511
+
512
+ @property
513
+ def extrapolation(self):
514
+ return
515
+
516
+ @property
517
+ def interpolation(self):
518
+ return
519
+
520
+ @property
521
+ def use_state_space(self):
522
+ return True
523
+
524
+
525
+ class Settings(SimulationSetup, SiwaveDCIRSettings):
526
+ """Class to manage global settings for the Siwave simulation setup module.
527
+ Added to be compliant with ansys-edbe-core settings structure."""
528
+
529
+ def __init__(self, parent, sim_setup_info):
530
+ SimulationSetup.__init__(self, pedb=parent._pedb, edb_object=parent._edb_object)
531
+ SiwaveDCIRSettings.__init__(self, parent)
532
+ self._parent = parent
533
+ self._sim_setup_info = sim_setup_info
534
+
535
+ @property
536
+ def advanced(self):
537
+ return True
538
+
539
+ @property
540
+ def dc(self):
541
+ return DCSettings(self._parent)
542
+
543
+ @property
544
+ def dc_advanced(self):
545
+ return DCAdvancedSettings(self._parent)
546
+
547
+ @property
548
+ def general(self):
549
+ return DCSettings(self._parent)
550
+
551
+ @property
552
+ def dc_report_config_file(self) -> str:
553
+ """Path to the DC report configuration file."""
554
+ # return self._sim_setup_info._edb_object.SimulationSettings.DCIRSettings.DCReportConfigFile
555
+ return super().dc_report_config_file
556
+
557
+ @dc_report_config_file.setter
558
+ def dc_report_config_file(self, value: str):
559
+ SiwaveDCIRSettings.dc_report_config_file = value
560
+
561
+ @property
562
+ def dc_report_show_active_devices(self) -> bool:
563
+ """Flag to show active devices in the DC report."""
564
+ return super().dc_report_show_active_devices
565
+
566
+ @dc_report_show_active_devices.setter
567
+ def dc_report_show_active_devices(self, value: bool):
568
+ SiwaveDCIRSettings.dc_report_show_active_devices = value
569
+
570
+ @property
571
+ def enabled(self) -> bool:
572
+ """Flag indicating if the setup is enabled."""
573
+ return self._sim_setup_info.simulation_settings.Enabled
574
+
575
+ @enabled.setter
576
+ def enabled(self, value: bool):
577
+ self._sim_setup_info.simulation_settings.Enabled = value
578
+
579
+ @property
580
+ def export_dc_thermal_data(self) -> bool:
581
+ """Flag to export DC thermal data."""
582
+ return SiwaveDCIRSettings.export_dc_thermal_data
583
+
584
+ @export_dc_thermal_data.setter
585
+ def export_dc_thermal_data(self, value: bool):
586
+ SiwaveDCIRSettings.export_dc_thermal_data = value
587
+
588
+ @property
589
+ def full_dc_report_path(self) -> str:
590
+ """Full path to the DC report."""
591
+ return SiwaveDCIRSettings.full_dc_report_path
592
+
593
+ @full_dc_report_path.setter
594
+ def full_dc_report_path(self, value: str):
595
+ SiwaveDCIRSettings.full_dc_report_path = value
596
+
597
+ @property
598
+ def icepak_temp_file_path(self) -> str:
599
+ """Path to the Icepak temporary file."""
600
+ return SiwaveDCIRSettings.icepak_temp_file_path
601
+
602
+ @icepak_temp_file_path.setter
603
+ def icepak_temp_file_path(self, value: str):
604
+ SiwaveDCIRSettings.icepak_temp_file_path = value
605
+
606
+ @property
607
+ def import_thermal_data(self) -> bool:
608
+ """Flag to import thermal data."""
609
+ return super().import_thermal_data
610
+
611
+ @import_thermal_data.setter
612
+ def import_thermal_data(self, value: bool):
613
+ SiwaveDCIRSettings.import_thermal_data = value
614
+
615
+ @property
616
+ def s_parameter(self) -> SIwaveSParameterSettings:
617
+ """S-parameter settings."""
618
+ return SIwaveSParameterSettings(self._parent)
619
+
620
+ @property
621
+ def source_terms_to_ground(self) -> dict[str, int]:
622
+ """Dictionary of grounded terminals.
623
+
624
+ Returns
625
+ -------
626
+ Dictionary
627
+ {str, int}, keys is source name, value int 0 unspecified, 1 negative node, 2 positive one.
628
+
629
+ """
630
+ return convert_netdict_to_pydict(super().source_terms_to_ground)
631
+
632
+ @source_terms_to_ground.setter
633
+ def source_terms_to_ground(self, value: dict[str, int]):
634
+ SiwaveDCIRSettings.source_terms_to_ground = convert_pydict_to_netdict(value)
635
+
636
+ @property
637
+ def use_loop_res_for_per_pin(self):
638
+ """Flag to use loop resistance for per-pin calculations."""
639
+ return super().use_loop_res_for_per_pin
640
+
641
+ @use_loop_res_for_per_pin.setter
642
+ def use_loop_res_for_per_pin(self, value: bool):
643
+ SiwaveDCIRSettings.use_loop_res_for_per_pin = value
644
+
645
+ @property
646
+ def via_report_path(self) -> str:
647
+ """Path to the via report."""
648
+ return super().via_report_path
649
+
650
+ @via_report_path.setter
651
+ def via_report_path(self, value: str):
652
+ SiwaveDCIRSettings.via_report_path = value
403
653
 
404
654
  def add_source_terminal_to_ground(self, source_name, terminal=0):
405
655
  """Add a source terminal to ground.
@@ -422,7 +672,5 @@ class SiwaveDCSimulationSetup(SimulationSetup):
422
672
  """
423
673
  terminals = self.source_terms_to_ground
424
674
  terminals[source_name] = terminal
425
- self.get_sim_setup_info.simulation_settings.DCIRSettings.SourceTermsToGround = convert_pydict_to_netdict(
426
- terminals
427
- )
675
+ self._sim_setup_info.simulation_settings.DCIRSettings.SourceTermsToGround = convert_pydict_to_netdict(terminals)
428
676
  return self._update_setup()
pyedb/dotnet/edb.py CHANGED
@@ -32,7 +32,7 @@ import os
32
32
  from pathlib import Path
33
33
  import re
34
34
  import shutil
35
- import subprocess
35
+ import subprocess # nosec B404
36
36
  import sys
37
37
  import time
38
38
  import traceback
@@ -754,6 +754,11 @@ class Edb:
754
754
 
755
755
  This function supports all AEDT formats, including DXF, GDS, SML (IPC2581), BRD, MCM, SIP, ZIP and TGZ.
756
756
 
757
+ .. warning::
758
+ Do not execute this function with untrusted function argument, environment
759
+ variables or pyedb global settings.
760
+ See the :ref:`security guide<ref_security_consideration>` for details.
761
+
757
762
  Parameters
758
763
  ----------
759
764
  input_file : str
@@ -790,16 +795,16 @@ class Edb:
790
795
  self._nets = None
791
796
  aedb_name = os.path.splitext(os.path.basename(input_file))[0] + ".aedb"
792
797
  if anstranslator_full_path and os.path.exists(anstranslator_full_path):
793
- command = anstranslator_full_path
798
+ executable_path = anstranslator_full_path
794
799
  else:
795
- command = os.path.join(self.base_path, "anstranslator")
800
+ executable_path = os.path.join(self.base_path, "anstranslator")
796
801
  if is_windows:
797
- command += ".exe"
802
+ executable_path += ".exe"
798
803
 
799
804
  if not working_dir:
800
805
  working_dir = os.path.dirname(input_file)
801
806
  cmd_translator = [
802
- command,
807
+ executable_path,
803
808
  input_file,
804
809
  os.path.join(working_dir, aedb_name),
805
810
  "-l={}".format(os.path.join(working_dir, "Translator.log")),
@@ -817,7 +822,10 @@ class Edb:
817
822
  cmd_translator.append('-t="{}"'.format(tech_file))
818
823
  if layer_filter:
819
824
  cmd_translator.append('-f="{}"'.format(layer_filter))
820
- subprocess.run(cmd_translator)
825
+ try:
826
+ subprocess.run(cmd_translator, check=True) # nosec
827
+ except subprocess.CalledProcessError as e: # nosec
828
+ raise RuntimeError("An error occurred while translating board file to ``edb.def`` file") from e
821
829
  if not os.path.exists(os.path.join(working_dir, aedb_name)):
822
830
  raise RuntimeWarning(f"Translator failed. command : {' '.join(cmd_translator)}")
823
831
  else:
@@ -4837,6 +4845,11 @@ class Edb:
4837
4845
  def compare(self, input_file, results=""):
4838
4846
  """Compares current open database with another one.
4839
4847
 
4848
+ .. warning::
4849
+ Do not execute this function with untrusted function argument, environment
4850
+ variables or pyedb global settings.
4851
+ See the :ref:`security guide<ref_security_consideration>` for details.
4852
+
4840
4853
  Parameters
4841
4854
  ----------
4842
4855
  input_file : str
@@ -4852,16 +4865,16 @@ class Edb:
4852
4865
  if not results:
4853
4866
  results = self.edbpath[:-5] + "_compare_results"
4854
4867
  os.mkdir(results)
4855
- command = os.path.join(self.base_path, "EDBDiff.exe")
4868
+ executable_path = os.path.join(self.base_path, "EDBDiff.exe")
4856
4869
  if is_linux:
4857
4870
  mono_path = os.path.join(self.base_path, "common/mono/Linux64/bin/mono")
4858
- cmd_input = [mono_path, command, input_file, self.edbpath, results]
4871
+ command = [mono_path, executable_path, input_file, self.edbpath, results]
4859
4872
  else:
4860
- cmd_input = [command, input_file, self.edbpath, results]
4861
- p = subprocess.run(cmd_input)
4862
- if p.returncode == 0:
4873
+ command = [executable_path, input_file, self.edbpath, results]
4874
+ try:
4875
+ subprocess.run(command, check=True) # nosec
4863
4876
  return str(Path(self.base_path).joinpath("EDBDiff.exe"))
4864
- else:
4877
+ except subprocess.CalledProcessError as e: # nosec
4865
4878
  raise RuntimeError(
4866
4879
  "EDBDiff.exe execution failed. Please check if the executable is present in the base path."
4867
- )
4880
+ ) from e
@@ -26,12 +26,15 @@ This module contains the array building feature from unit cell.
26
26
 
27
27
  import itertools
28
28
  from typing import Optional, Union
29
+ import warnings
29
30
 
30
31
  from pyedb import Edb
32
+ from pyedb.grpc.database.primitive.padstack_instance import PadstackInstance
33
+ from pyedb.misc.decorators import execution_timer
31
34
 
32
35
 
33
36
  # ----------------------
34
- # Public façade function
37
+ # Public api
35
38
  # ----------------------
36
39
  def create_array_from_unit_cell(
37
40
  edb: Edb,
@@ -94,12 +97,15 @@ def create_array_from_unit_cell(
94
97
  adapter = _GrpcAdapter(edb)
95
98
  else:
96
99
  adapter = _DotNetAdapter(edb)
100
+ warnings.warn(".NET back-end is deprecated and will be removed in future releases.", UserWarning)
101
+ warnings.warn("Consider moving to PyEDB gRPC (ANSYS 2025R2) for better performances", UserWarning)
97
102
  return __create_array_from_unit_cell_impl(edb, adapter, x_number, y_number, offset_x, offset_y)
98
103
 
99
104
 
100
105
  # ------------------------------------------------------------------
101
106
  # Implementation (technology-agnostic)
102
107
  # ------------------------------------------------------------------
108
+ @execution_timer("create_array_from_unit_cell")
103
109
  def __create_array_from_unit_cell_impl(
104
110
  edb: Edb,
105
111
  adapter: "_BaseAdapter",
@@ -156,34 +162,44 @@ def __create_array_from_unit_cell_impl(
156
162
  paths = list(edb.modeler.paths)
157
163
  vias = list(edb.padstacks.vias.values())
158
164
  components = list(edb.components.instances.values())
165
+ pingroups = edb.layout.pin_groups
166
+ if edb.grpc:
167
+ pg_dict = {
168
+ pad.edb_uid: pg.name # edb_uid → PinGroup.name
169
+ for pg in pingroups # for every PinGroup
170
+ for pad in pg.pins.values() # for every PadstackInstance in its pin-dict
171
+ }
172
+ else:
173
+ pg_dict = {
174
+ pad.id: pg.name # edb_uid → PinGroup.name
175
+ for pg in pingroups # for every PinGroup
176
+ for pad in pg.pins.values() # for every PadstackInstance in its pin-dict
177
+ }
159
178
 
160
179
  # ---------- Replication loops ----------
161
180
  edb.logger.info(f"Starting array replication {x_number}×{y_number}")
181
+ total_number = x_number * y_number - 1 # minus original
182
+ cell_count = 0
162
183
  for i, j in itertools.product(range(x_number), range(y_number)):
163
184
  if i == 0 and j == 0:
164
185
  continue # original already exists
165
-
166
186
  dx = edb.value(offset_x * i)
167
187
  dy = edb.value(offset_y * j)
168
-
188
+ # Components
189
+ for comp in components:
190
+ adapter.duplicate_component(comp, dx, dy, i, j, pin_groups=pg_dict)
169
191
  # Primitives & voids
170
192
  for prim in primitives:
171
- new_poly = adapter.duplicate_primitive(prim, dx, dy, i, j)
172
- for void in prim.voids:
173
- adapter.duplicate_void(new_poly, void, dx, dy)
174
-
193
+ if not prim.is_void:
194
+ adapter.duplicate_primitive(prim, dx, dy, i, j)
175
195
  # Paths
176
196
  for path in paths:
177
197
  adapter.duplicate_path(path, dx, dy, i, j)
178
-
179
198
  # Stand-alone vias
180
199
  for via in (v for v in vias if not v.component):
181
200
  adapter.duplicate_standalone_via(via, dx, dy, i, j)
182
-
183
- # Components
184
- for comp in components:
185
- adapter.duplicate_component(comp, dx, dy, i, j)
186
-
201
+ cell_count += 1
202
+ edb.logger.info(f"Replicated cell {cell_count} of {total_number} ({(cell_count / total_number) * 100:.1f}%)")
187
203
  edb.logger.info("Array replication finished successfully")
188
204
  return True
189
205
 
@@ -196,6 +212,8 @@ class _BaseAdapter:
196
212
 
197
213
  def __init__(self, edb: Edb):
198
214
  self.edb = edb
215
+ self.layers = edb.stackup.layers
216
+ self.active_layout = edb.active_layout
199
217
 
200
218
  # ---- Outline helpers ----
201
219
  def is_supported_outline(self, outline) -> bool:
@@ -222,10 +240,6 @@ class _BaseAdapter:
222
240
  """Return a new primitive translated by (dx, dy)."""
223
241
  raise NotImplementedError
224
242
 
225
- def duplicate_void(self, new_poly, void, dx, dy):
226
- """Add a translated copy of *void* to *new_poly*."""
227
- raise NotImplementedError
228
-
229
243
  def duplicate_path(self, path, dx, dy, i, j):
230
244
  """Create a translated copy of *path*."""
231
245
  raise NotImplementedError
@@ -234,7 +248,7 @@ class _BaseAdapter:
234
248
  """Create a translated copy of a stand-alone via."""
235
249
  raise NotImplementedError
236
250
 
237
- def duplicate_component(self, comp, dx, dy, i, j):
251
+ def duplicate_component(self, comp, dx, dy, i, j, pin_groups=None):
238
252
  """Create a translated copy of *comp* (including its pins)."""
239
253
  raise NotImplementedError
240
254
 
@@ -254,15 +268,11 @@ class _GrpcAdapter(_BaseAdapter):
254
268
 
255
269
  def duplicate_primitive(self, prim, dx, dy, i, j):
256
270
  moved_pd = prim.polygon_data.move((dx, dy))
271
+ voids = [voids.polygon_data.move((dx, dy)) for voids in prim.voids]
257
272
  return self.edb.modeler.create_polygon(
258
- moved_pd,
259
- layer_name=prim.layer.name,
260
- net_name=prim.net.name,
273
+ moved_pd, layer_name=prim.layer.name, net_name=prim.net.name, voids=voids
261
274
  )
262
275
 
263
- def duplicate_void(self, new_poly, void, dx, dy):
264
- new_poly.add_void(void.polygon_data.move((dx, dy)))
265
-
266
276
  def duplicate_path(self, path, dx, dy, i, j):
267
277
  moved_line = path.cast().center_line.move((dx, dy))
268
278
  self.edb.modeler.create_trace(
@@ -276,26 +286,24 @@ class _GrpcAdapter(_BaseAdapter):
276
286
  )
277
287
 
278
288
  def duplicate_standalone_via(self, via, dx, dy, i, j):
279
- from pyedb.grpc.database.primitive.padstack_instance import PadstackInstance
280
-
281
289
  pos = via.position
282
290
  PadstackInstance.create(
283
- self.edb.active_layout,
291
+ self.active_layout,
284
292
  net=via.net,
285
- name=f"{via.name}_i{i}_j{j}",
286
- padstack_def=self.edb.padstacks.definitions[via.padstack_definition],
293
+ name=f"{via.name}_X{i}_Y{j}",
294
+ padstack_def=via.definition,
287
295
  position_x=pos[0] + dx,
288
296
  position_y=pos[1] + dy,
289
297
  rotation=0.0,
290
- top_layer=self.edb.stackup.layers[via.start_layer],
291
- bottom_layer=self.edb.stackup.layers[via.stop_layer],
298
+ top_layer=self.layers[via.start_layer],
299
+ bottom_layer=self.layers[via.stop_layer],
292
300
  )
293
301
 
294
- def duplicate_component(self, comp, dx, dy, i, j):
295
- from pyedb.grpc.database.primitive.padstack_instance import PadstackInstance
296
-
302
+ def duplicate_component(self, comp, dx, dy, i, j, pin_groups=None):
297
303
  new_pins = []
304
+ _pg = {}
298
305
  for pin in comp.pins.values():
306
+ pg_name = pin_groups.get(pin.edb_uid, None) if pin_groups else None
299
307
  pos = pin.position
300
308
  new_pin = PadstackInstance.create(
301
309
  self.edb.active_layout,
@@ -309,6 +317,8 @@ class _GrpcAdapter(_BaseAdapter):
309
317
  bottom_layer=self.edb.stackup.layers[pin.stop_layer],
310
318
  )
311
319
  new_pins.append(new_pin)
320
+ if pg_name:
321
+ _pg.setdefault(pg_name, []).append(new_pin)
312
322
 
313
323
  if new_pins:
314
324
  res = self.edb.value(comp.res_value) if hasattr(comp, "res_value") and comp.res_value else None
@@ -323,8 +333,11 @@ class _GrpcAdapter(_BaseAdapter):
323
333
  l_value=ind,
324
334
  c_value=cap,
325
335
  )
336
+ new_comp.type = comp.type
326
337
  if hasattr(comp, "component_property") and comp.component_property:
327
338
  new_comp.component_property = comp.component_property
339
+ for pg_name, pins in _pg.items():
340
+ self.edb.components.create_pingroup_from_pins(pins=pins, group_name=f"{pg_name}_{i}_{j}")
328
341
 
329
342
 
330
343
  class _DotNetAdapter(_BaseAdapter):
@@ -352,14 +365,6 @@ class _DotNetAdapter(_BaseAdapter):
352
365
  net_name=prim.net.name,
353
366
  )
354
367
 
355
- def duplicate_void(self, new_poly, void, dx, dy):
356
- from pyedb.dotnet.database.geometry.point_data import PointData
357
-
358
- vector = PointData.create_from_xy(self.edb, x=dx, y=dy)
359
- void_polygon_data = void.polygon_data
360
- void_polygon_data._edb_object.Move(vector._edb_object)
361
- new_poly.add_void(void_polygon_data.points)
362
-
363
368
  def duplicate_path(self, path, dx, dy, i, j):
364
369
  from pyedb.dotnet.database.geometry.point_data import PointData
365
370
 
@@ -386,7 +391,7 @@ class _DotNetAdapter(_BaseAdapter):
386
391
  via_name=f"{via.aedt_name}_i{i}_j{j}",
387
392
  )
388
393
 
389
- def duplicate_component(self, comp, dx, dy, i, j):
394
+ def duplicate_component(self, comp, dx, dy, i, j, pin_groups=None):
390
395
  new_pins = []
391
396
  for pin in comp.pins.values():
392
397
  pos = pin.position
@@ -396,7 +401,6 @@ class _DotNetAdapter(_BaseAdapter):
396
401
  via_name=f"{pin.aedt_name}_i{i}_j{j}",
397
402
  )
398
403
  new_pins.append(new_pin)
399
-
400
404
  if new_pins:
401
405
  new_comp = self.edb.components.create(
402
406
  pins=new_pins,