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.
- pyedb/__init__.py +1 -1
- pyedb/configuration/cfg_pin_groups.py +2 -0
- pyedb/dotnet/database/cell/hierarchy/component.py +2 -8
- pyedb/dotnet/database/cell/layout.py +1 -1
- pyedb/dotnet/database/components.py +38 -42
- pyedb/dotnet/database/edb_data/control_file.py +13 -5
- pyedb/dotnet/database/edb_data/padstacks_data.py +34 -12
- pyedb/dotnet/database/edb_data/sources.py +21 -2
- pyedb/dotnet/database/general.py +4 -8
- pyedb/dotnet/database/layout_validation.py +8 -0
- pyedb/dotnet/database/sim_setup_data/data/settings.py +2 -2
- pyedb/dotnet/database/sim_setup_data/io/siwave.py +53 -0
- pyedb/dotnet/database/stackup.py +5 -32
- pyedb/dotnet/database/utilities/hfss_simulation_setup.py +81 -0
- pyedb/dotnet/database/utilities/siwave_simulation_setup.py +259 -11
- pyedb/dotnet/edb.py +26 -13
- pyedb/extensions/create_cell_array.py +48 -44
- pyedb/generic/general_methods.py +24 -36
- pyedb/generic/plot.py +8 -23
- pyedb/generic/process.py +78 -10
- pyedb/grpc/database/components.py +7 -5
- pyedb/grpc/database/control_file.py +13 -5
- pyedb/grpc/database/definition/padstack_def.py +10 -5
- pyedb/grpc/database/hierarchy/component.py +2 -9
- pyedb/grpc/database/modeler.py +28 -8
- pyedb/grpc/database/padstacks.py +62 -103
- pyedb/grpc/database/primitive/padstack_instance.py +41 -12
- pyedb/grpc/database/primitive/path.py +13 -13
- pyedb/grpc/database/simulation_setup/hfss_simulation_setup.py +79 -0
- pyedb/grpc/database/source_excitations.py +7 -7
- pyedb/grpc/database/stackup.py +5 -33
- pyedb/grpc/database/terminal/padstack_instance_terminal.py +9 -11
- pyedb/grpc/database/terminal/point_terminal.py +30 -0
- pyedb/grpc/database/terminal/terminal.py +16 -2
- pyedb/grpc/database/utility/xml_control_file.py +13 -5
- pyedb/grpc/edb.py +46 -20
- pyedb/grpc/edb_init.py +7 -19
- pyedb/misc/aedtlib_personalib_install.py +2 -2
- pyedb/misc/downloads.py +18 -3
- pyedb/misc/siw_feature_config/emc_rule_checker_settings.py +2 -1
- pyedb/misc/siw_feature_config/xtalk_scan/scan_config.py +0 -1
- pyedb/workflows/sipi/hfss_auto_configuration.py +711 -0
- {pyedb-0.57.0.dist-info → pyedb-0.59.0.dist-info}/METADATA +4 -5
- {pyedb-0.57.0.dist-info → pyedb-0.59.0.dist-info}/RECORD +46 -45
- {pyedb-0.57.0.dist-info → pyedb-0.59.0.dist-info}/WHEEL +0 -0
- {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
|
|
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.
|
|
375
|
-
self.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
798
|
+
executable_path = anstranslator_full_path
|
|
794
799
|
else:
|
|
795
|
-
|
|
800
|
+
executable_path = os.path.join(self.base_path, "anstranslator")
|
|
796
801
|
if is_windows:
|
|
797
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4871
|
+
command = [mono_path, executable_path, input_file, self.edbpath, results]
|
|
4859
4872
|
else:
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
172
|
-
|
|
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
|
-
|
|
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.
|
|
291
|
+
self.active_layout,
|
|
284
292
|
net=via.net,
|
|
285
|
-
name=f"{via.name}
|
|
286
|
-
padstack_def=
|
|
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.
|
|
291
|
-
bottom_layer=self.
|
|
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,
|