diff --git a/src/pyedb/configuration/data_model/cfg_boundaries_data.py b/src/pyedb/configuration/data_model/cfg_boundaries_data.py new file mode 100644 index 0000000000..05f7150f58 --- /dev/null +++ b/src/pyedb/configuration/data_model/cfg_boundaries_data.py @@ -0,0 +1,23 @@ +from dataclasses import dataclass + +from dataclasses_json import dataclass_json + + +@dataclass_json +@dataclass +class CfgBoundaries: + open_region: bool = True + open_region_type: str = "radiation" + pml_visible: bool = False + pml_operation_frequency: str = "5ghz" + pml_radiation_factor: int = 10 + dielectric_extents_type: str = "bounding_box" + dielectric_base_polygon: str = "" + horizontal_padding: float = 0 + honor_primitives_on_dielectric_layers: bool = True + air_box_extents_type: str = "bounding_box" + air_box_base_polygon: str = "" + air_box_truncate_model_ground_layers: bool = False + air_box_horizontal_padding: float = 0.15 + air_box_positive_vertical_padding: float = 1 + air_box_negative_vertical_padding: float = 1 diff --git a/src/pyedb/configuration/data_model/cfg_components_data.py b/src/pyedb/configuration/data_model/cfg_components_data.py new file mode 100644 index 0000000000..36c1d0eadf --- /dev/null +++ b/src/pyedb/configuration/data_model/cfg_components_data.py @@ -0,0 +1,55 @@ +from dataclasses import dataclass, field +from typing import Optional + +from dataclasses_json import dataclass_json + + +@dataclass_json +@dataclass +class CfgPinPair: + type: str = "series" + p1: str = "1" + p2: str = "2" + capacitance: Optional[str] = None + inductance: Optional[str] = None + resistance: Optional[str] = None + + +@dataclass_json +@dataclass +class CfgPinPairs: + pin_pairs: list[CfgPinPair] = field(default_factory=list) + + +@dataclass_json +@dataclass +class CfgSolderBallProperties: + shape: Optional[str] = "None" + diameter: Optional[str] = "0um" + height: Optional[str] = "0um" + + +@dataclass_json +@dataclass +class CfgPortProperties: + reference_offset: float = 0 + reference_size_auto: bool = True + reference_size_x: float = 0.0 + reference_size_y: float = 0.0 + + +@dataclass_json +@dataclass +class CfgComponent: + reference_designator: str = "" + part_type: str = "" + enabled: Optional[bool] = True + rlc_model: Optional[CfgPinPairs] = None + solder_ball_properties: Optional[CfgSolderBallProperties] = None + port_properties: Optional[CfgPortProperties] = None + + +@dataclass_json +@dataclass +class CfgComponents: + components: list[CfgComponent] diff --git a/src/pyedb/configuration/data_model/cfg_general_data.py b/src/pyedb/configuration/data_model/cfg_general_data.py new file mode 100644 index 0000000000..79e8523e2b --- /dev/null +++ b/src/pyedb/configuration/data_model/cfg_general_data.py @@ -0,0 +1,11 @@ +from dataclasses import dataclass +from typing import Optional + +from dataclasses_json import dataclass_json + + +@dataclass_json +@dataclass +class CfgGeneral: + spice_model_library: Optional[str] = None + s_parameter_library: Optional[str] = None diff --git a/src/pyedb/configuration/data_model/cfg_nets_data.py b/src/pyedb/configuration/data_model/cfg_nets_data.py new file mode 100644 index 0000000000..b00a4ca626 --- /dev/null +++ b/src/pyedb/configuration/data_model/cfg_nets_data.py @@ -0,0 +1,10 @@ +from dataclasses import dataclass, field + +from dataclasses_json import dataclass_json + + +@dataclass_json +@dataclass +class CfgNets: + power_ground_nets: list[str] = field(default_factory=list) + signal_nets: list[str] = field(default_factory=list) diff --git a/src/pyedb/configuration/data_model/cfg_operations_data.py b/src/pyedb/configuration/data_model/cfg_operations_data.py new file mode 100644 index 0000000000..f8aac826fc --- /dev/null +++ b/src/pyedb/configuration/data_model/cfg_operations_data.py @@ -0,0 +1,38 @@ +from dataclasses import dataclass, field +from typing import Optional + +from dataclasses_json import dataclass_json + + +@dataclass_json +@dataclass +class CfgCutout: + signal_list: list[str] = field(default_factory=list) + reference_list: list[str] = field(default_factory=list) + extent_type: str = "ConvexHull" + expansion_size: float = 0.002 + use_round_corner: bool = False + output_aedb_path: str = "" + open_cutout_at_end: bool = True + use_pyaedt_cutout: bool = True + number_of_threads: int = 4 + use_pyaedt_extent_computing: bool = True + extent_defeature: float = 0.0 + remove_single_pin_components: bool = False + custom_extent: str = "" + custom_extent_units: str = "mm" + include_partial_instances: bool = False + keep_voids: bool = True + check_terminals: bool = False + include_pingroups: bool = False + expansion_factor: float = 0.0 + maximum_iterations: int = 30 + preserve_components_with_model: bool = False + simple_pad_check: bool = True + keep_lines_as_path: bool = False + + +@dataclass_json +@dataclass +class CfgOperations: + cutout: Optional[CfgCutout] = None diff --git a/src/pyedb/configuration/data_model/cfg_package_definition_data.py b/src/pyedb/configuration/data_model/cfg_package_definition_data.py new file mode 100644 index 0000000000..65f2d165ef --- /dev/null +++ b/src/pyedb/configuration/data_model/cfg_package_definition_data.py @@ -0,0 +1,26 @@ +from dataclasses import dataclass + +from dataclasses_json import dataclass_json + + +@dataclass_json +@dataclass +class CfgHeatSink: + fin_base_height: float = 0.0 + fin_height: float = 0.0 + fin_orientation: str = str + fin_spacing: float = 0.0 + fin_thickness: float = 0.0 + + +@dataclass_json +@dataclass +class CfgPackageDefinition: + name: str = "" + component_definition: str = "" + maximum_power: float = 0.0 + therm_cond: float = 0.0 + theta_jb: float = 0.0 + theta_jc: float = 0.0 + height: float = 0.0 + heatsink: CfgHeatSink = None diff --git a/src/pyedb/configuration/data_model/cfg_padsatck_data.py b/src/pyedb/configuration/data_model/cfg_padsatck_data.py new file mode 100644 index 0000000000..22e7ff707b --- /dev/null +++ b/src/pyedb/configuration/data_model/cfg_padsatck_data.py @@ -0,0 +1,44 @@ +from dataclasses import dataclass, field + +from dataclasses_json import dataclass_json + + +@dataclass_json +@dataclass +class CfgDefinition: + name: str = "" + hole_diameter: str = "" + hole_plating_thickness: str = "" + hole_material: str = "" + hole_range: str = "through" + + +@dataclass_json +@dataclass +class CfgBackDrillTop: + drill_to_layer: str = "" + drill_diameter: str = "" + stub_length: str = "" + + +@dataclass_json +@dataclass +class CfgBackDrillBottom: + drill_to_layer: str = "" + drill_diameter: str = "" + stub_length: str = "" + + +@dataclass_json +@dataclass +class CfgInstance: + name: str = "" + backdrill_top: CfgBackDrillTop = None + backdrill_bottom: CfgBackDrillBottom = None + + +@dataclass_json +@dataclass +class CfgPadStacks: + definitions: list[CfgDefinition] = field(default_factory=list) + instances: list[CfgInstance] = field(default_factory=list) diff --git a/src/pyedb/configuration/data_model/cfg_pingroup_data.py b/src/pyedb/configuration/data_model/cfg_pingroup_data.py new file mode 100644 index 0000000000..9ed8cd69d0 --- /dev/null +++ b/src/pyedb/configuration/data_model/cfg_pingroup_data.py @@ -0,0 +1,20 @@ +from dataclasses import dataclass, field +from typing import Union + +from dataclasses_json import dataclass_json + + +@dataclass_json() +@dataclass +class CfgPinGroup: + name: str = "" + reference_designator: str = "" + pins: list[str] = field(default_factory=list) + net: Union[str, list[str]] = field(default_factory=list) + pingroup: str = "" + + +@dataclass_json +@dataclass +class CfgTerminal: + pingroup: str = "" diff --git a/src/pyedb/configuration/data_model/cfg_ports_sources_data.py b/src/pyedb/configuration/data_model/cfg_ports_sources_data.py new file mode 100644 index 0000000000..82dc81389d --- /dev/null +++ b/src/pyedb/configuration/data_model/cfg_ports_sources_data.py @@ -0,0 +1,33 @@ +from dataclasses import dataclass +from typing import Optional + +from dataclasses_json import dataclass_json + + +@dataclass_json +@dataclass +class CfgTerminal: + pin: str = "" + net: str = "" + pin_group: str = "" + + +@dataclass_json +@dataclass +class CfgPort: + name: str = "" + reference_designator: str = "" + type: str = "" + positive_terminal: Optional[CfgTerminal] = None + negative_terminal: Optional[CfgTerminal] = None + + +@dataclass_json +@dataclass +class CfgSource: + name: str = "" + reference_designator: str = "" + type: str = "" + magnitude: float = 0.0 + positive_terminal: CfgTerminal = None + negative_terminal: CfgTerminal = None diff --git a/src/pyedb/configuration/data_model/cfg_s_parameter_models_data.py b/src/pyedb/configuration/data_model/cfg_s_parameter_models_data.py new file mode 100644 index 0000000000..5ffa8cb57a --- /dev/null +++ b/src/pyedb/configuration/data_model/cfg_s_parameter_models_data.py @@ -0,0 +1,15 @@ +from dataclasses import dataclass, field + +from dataclasses_json import dataclass_json + + +@dataclass_json +@dataclass +class CfgSparameter: + name: str = "" + component_definition: str = "" + file_path: str = "" + apply_to_all: bool = True + components: list[str] = field(default_factory=list) + reference_net: str = "" + reference_net_per_component: dict[str, str] = field(default_factory=dict) diff --git a/src/pyedb/configuration/data_model/cfg_setup_data.py b/src/pyedb/configuration/data_model/cfg_setup_data.py new file mode 100644 index 0000000000..cdc24d5353 --- /dev/null +++ b/src/pyedb/configuration/data_model/cfg_setup_data.py @@ -0,0 +1,42 @@ +from dataclasses import dataclass, field +from typing import Optional, Union + +from dataclasses_json import dataclass_json + + +@dataclass_json +@dataclass +class CfgFrequency: + distribution: str = "" + start: Union[str, float] = 0.0 + stop: Union[str, float] = 0.0 + step: Union[str, float] = 0.0 + points: int = 0 + samples: int = 0 + + +@dataclass_json +@dataclass +class CfgFrequencySweep: + name: str = "" + type: str = "" + frequencies: list[CfgFrequency] = field(default_factory=list) + + +@dataclass_json +@dataclass +class CfgDcIrSettings: + export_dc_thermal_data: bool = False + + +@dataclass_json +@dataclass +class CfgSetup: + name: str = "" + type: str = "" + f_adapt: Union[str, float] = "" + max_num_passes: int = 20 + max_mag_delta_s: Union[str, float] = 0.02 + dc_slider_position: int = 1 + dc_ir_settings: Optional[CfgDcIrSettings] = None + freq_sweep: Optional[CfgFrequencySweep] = None diff --git a/src/pyedb/configuration/data_model/cfg_spice_models_data.py b/src/pyedb/configuration/data_model/cfg_spice_models_data.py new file mode 100644 index 0000000000..cd9c8d5791 --- /dev/null +++ b/src/pyedb/configuration/data_model/cfg_spice_models_data.py @@ -0,0 +1,20 @@ +from dataclasses import dataclass, field + +from dataclasses_json import dataclass_json + + +@dataclass_json +@dataclass +class CfgSpiceModelLib: + spice_model_library: str = "" + + +@dataclass_json +@dataclass +class CfgSpiceModel: + name: str = "" + component_definition: str = "" + file_path: str = "" + sub_circuit_name: str = "" + apply_to_all: bool = True + components: [str] = field(default_factory=list) diff --git a/src/pyedb/configuration/data_model/cfg_stackup_data.py b/src/pyedb/configuration/data_model/cfg_stackup_data.py new file mode 100644 index 0000000000..f6c6f64683 --- /dev/null +++ b/src/pyedb/configuration/data_model/cfg_stackup_data.py @@ -0,0 +1,30 @@ +from dataclasses import dataclass, field +from typing import Union + +from dataclasses_json import dataclass_json + + +@dataclass_json +@dataclass +class CfgMaterial: + name: str = "" + conductivity: float = 0.0 + permittivity: float = 0.0 + dielectric_loss_tangent: float = 0.0 + + +@dataclass_json +@dataclass +class CfgLayer: + fill_material: str = "" + material: str = "" + name: str = "" + thickness: Union[str, float] = "" + type: str = "" + + +@dataclass_json +@dataclass +class CfgStackup: + materials: list[CfgMaterial] = field(default_factory=list) + layers: list = field(default_factory=list) diff --git a/src/pyedb/configuration/data_model/configuration_data.py b/src/pyedb/configuration/data_model/configuration_data.py new file mode 100644 index 0000000000..3d082146d7 --- /dev/null +++ b/src/pyedb/configuration/data_model/configuration_data.py @@ -0,0 +1,110 @@ +from dataclasses import dataclass +import json + +from dataclasses_json import dataclass_json + +from pyedb.configuration.data_model.cfg_boundaries_data import CfgBoundaries +from pyedb.configuration.data_model.cfg_components_data import CfgComponent +from pyedb.configuration.data_model.cfg_general_data import CfgGeneral +from pyedb.configuration.data_model.cfg_nets_data import CfgNets +from pyedb.configuration.data_model.cfg_operations_data import CfgOperations +from pyedb.configuration.data_model.cfg_package_definition_data import ( + CfgPackageDefinition, +) +from pyedb.configuration.data_model.cfg_padsatck_data import CfgPadStacks +from pyedb.configuration.data_model.cfg_pingroup_data import CfgPinGroup +from pyedb.configuration.data_model.cfg_ports_sources_data import CfgPort, CfgSource +from pyedb.configuration.data_model.cfg_s_parameter_models_data import CfgSparameter +from pyedb.configuration.data_model.cfg_setup_data import CfgSetup +from pyedb.configuration.data_model.cfg_spice_models_data import CfgSpiceModel +from pyedb.configuration.data_model.cfg_stackup_data import CfgStackup + + +@dataclass_json +@dataclass +class Configuration: + def __init__(self, pedb): + self._pedb = pedb + + general: CfgGeneral = None + boundaries: CfgBoundaries = None + nets: CfgNets = None + components: list[CfgComponent] = None + pin_groups: list[CfgPinGroup] = None + sources: list[CfgSource] = None + ports: list[CfgPort] = None + setups: list[CfgSetup] = None + stackup: CfgStackup = None + padstacks: CfgPadStacks = None + s_parameters: list[CfgSparameter] = None + spice_models: list[CfgSpiceModel] = None + package_definitions: list[CfgPackageDefinition] = None + operations: CfgOperations = None + + # TODO check for variables + # TODO modeler + # TODO probes + + def load_file(self, file_path): + with open(file_path, "r") as file: + data = json.load(file) + self._pedb.configuration.general = CfgGeneral().from_dict(data) + self._pedb.configuration.boundaries = CfgBoundaries().from_dict(data) + self._pedb.configuration.nets = CfgNets.from_dict(data) + self._pedb.configuration.components = [CfgComponent().from_dict(cmp) for cmp in data.get("components", [])] + self._pedb.configuration.pin_groups = [CfgPinGroup().from_dict(pg) for pg in data.get("pin_groups", [])] + self._pedb.configuration.sources = [CfgSource().from_dict(src) for src in data.get("sources", [])] + self._pedb.configuration.ports = [CfgPort().from_dict(port) for port in data.get("ports", [])] + self._pedb.configuration.setups = [CfgSetup().from_dict(setup) for setup in data.get("setups", [])] + self._pedb.configuration.stackup = CfgStackup().from_dict(data) + self._pedb.configuration.padstacks = CfgPadStacks().from_dict(data) + self._pedb.configuration.s_parameters = [ + CfgSparameter().from_dict(sp) for sp in data.get("s_parameters", []) + ] + self._pedb.configuration.spice_models = [ + CfgSpiceModel().from_dict(sp) for sp in data.get("spice_models", []) + ] + self._pedb.configuration.package_definitions = [ + CfgPackageDefinition().from_dict(pkg) for pkg in data.get("package_definitions", []) + ] + self._pedb.configuration.operations = CfgOperations().from_dict(data) + + def export_configuration_file(self, file_path): + data = self._pedb.configuration.to_dict() + with open(file_path, "w") as file: + json.dump(data, file, indent=4) + + def load_from_layout(self, filter=None): + self._pedb.logger.info("Loading nets") + self._pedb.nets.load_configuration_from_layout(filter=filter) + + self._pedb.logger.info("Loading components") + self._pedb.components.load_configuration_from_layout(filter=filter) + + self._pedb.logger.info("Loading pin groups") + self._pedb.layout.load_pingroup_configuration_from_layout() + + self._pedb.logger.info("Loading sources") + self._pedb.source_excitation.load_sources_configuration_from_layout() + + self._pedb.logger.info("Loading ports") + self._pedb.source_excitation.load_ports_configuration_from_layout() + + self._pedb.logger.info("Loading setups") + self._pedb.load_simulation_setup_configuration_from_layout() + + self._pedb.logger.info("Loading stackup") + self._pedb.stackup.load_configuration_from_layout() + + self._pedb.logger.info("Loading padstacks") + self._pedb.padstacks.load_configuration_from_layout() + + self._pedb.logger.info("Loading s-parameters definitions") + self._pedb.definitions.load_s_parameters_models_from_layout() + + self._pedb.logger.info("Loading spice definitions") + self._pedb.components.load_spice_models_from_layout() + # TODO check bug #556 status for Spice model. + + self._pedb.logger.info("Loading package definitions") + self._pedb.definitions.load_package_definition_from_layout() diff --git a/src/pyedb/grpc/database/components.py b/src/pyedb/grpc/database/components.py index 0156a5fde7..f115bebb50 100644 --- a/src/pyedb/grpc/database/components.py +++ b/src/pyedb/grpc/database/components.py @@ -45,6 +45,14 @@ ComponentPart, Series, ) +from pyedb.configuration.data_model.cfg_components_data import ( + CfgComponent, + CfgPinPair, + CfgPinPairs, + CfgPortProperties, + CfgSolderBallProperties, +) +from pyedb.configuration.data_model.cfg_spice_models_data import CfgSpiceModel from pyedb.generic.general_methods import ( generate_unique_name, get_filename_without_extension, @@ -148,7 +156,7 @@ def _db(self): return self._pedb.active_db @property - def instances(self): + def instances(self) -> dict[str, Component]: """All Cell components objects. Returns @@ -167,7 +175,7 @@ def instances(self): return self._cmp @property - def definitions(self): + def definitions(self) -> dict[str, ComponentDef]: """Retrieve component definition list. Returns @@ -176,11 +184,11 @@ def definitions(self): return {l.name: ComponentDef(self._pedb, l) for l in self._pedb.component_defs} @property - def nport_comp_definition(self): + def nport_comp_definition(self) -> dict[str, ComponentDef]: """Retrieve Nport component definition list.""" return {name: l for name, l in self.definitions.items() if l.reference_file} - def import_definition(self, file_path): + def import_definition(self, file_path) -> bool: """Import component definition from json file. Parameters @@ -214,7 +222,7 @@ def import_definition(self, file_path): pass return True - def export_definition(self, file_path): + def export_definition(self, file_path) -> str: """Export component definitions to json file. Parameters @@ -264,7 +272,7 @@ def export_definition(self, file_path): json.dump(data, f, ensure_ascii=False, indent=4) return file_path - def refresh_components(self): + def refresh_components(self) -> bool: """Refresh the component dictionary.""" self._logger.info("Refreshing the Components dictionary.") self._cmp = {} @@ -299,7 +307,7 @@ def refresh_components(self): return True @property - def resistors(self): + def resistors(self) -> dict[str, Component]: """Resistors. Returns @@ -317,7 +325,7 @@ def resistors(self): return self._res @property - def capacitors(self): + def capacitors(self) -> dict[str, Component]: """Capacitors. Returns @@ -335,7 +343,7 @@ def capacitors(self): return self._cap @property - def inductors(self): + def inductors(self) -> dict[str, Component]: """Inductors. Returns @@ -354,7 +362,7 @@ def inductors(self): return self._ind @property - def ICs(self): + def ICs(self) -> dict[str, Component]: """Integrated circuits. Returns @@ -373,7 +381,7 @@ def ICs(self): return self._ics @property - def IOs(self): + def IOs(self) -> dict[str, Component]: """Circuit inupts and outputs. Returns @@ -392,7 +400,7 @@ def IOs(self): return self._ios @property - def Others(self): + def Others(self) -> dict[str, Component]: """Other core components. Returns @@ -411,7 +419,7 @@ def Others(self): return self._others @property - def components_by_partname(self): + def components_by_partname(self) -> dict[str, Component]: """Components by part name. Returns @@ -435,7 +443,7 @@ def components_by_partname(self): self._comps_by_part[val.partname] = [val] return self._comps_by_part - def get_component_by_name(self, name): + def get_component_by_name(self, name) -> Component: """Retrieve a component by name. Parameters @@ -479,7 +487,7 @@ def get_pin_from_component(self, component, net_name=None, pin_name=None): pins = [pin for pin in pins if pin.name == pin_name] return pins - def get_components_from_nets(self, netlist=None): + def get_components_from_nets(self, netlist=None) -> list[str]: """Retrieve components from a net list. Parameters @@ -609,7 +617,7 @@ def get_component_placement_vector( self._logger.warning("Failed to compute vector.") return False, [0, 0], 0, 0 - def get_solder_ball_height(self, cmp): + def get_solder_ball_height(self, cmp) -> float: """Get component solder ball height. Parameters @@ -627,7 +635,7 @@ def get_solder_ball_height(self, cmp): cmp = self.get_component_by_name(cmp) return cmp.solder_ball_height - def get_vendor_libraries(self): + def get_vendor_libraries(self) -> ComponentLib: """Retrieve all capacitors and inductors libraries from ANSYS installation (used by Siwave). Returns @@ -879,7 +887,7 @@ def _get_closest_pin_from(self, pin, ref_pinlist): closest_pin = ref_pin return closest_pin - def replace_rlc_by_gap_boundaries(self, component=None): + def replace_rlc_by_gap_boundaries(self, component=None) -> bool: """Replace RLC component by RLC gap boundaries. These boundary types are compatible with 3D modeler export. Only 2 pins RLC components are supported in this command. @@ -891,7 +899,7 @@ def replace_rlc_by_gap_boundaries(self, component=None): Returns ------- bool - ``True`` when succeed, ``False`` if it failed. + ``True`` when succeeded, ``False`` if it failed. Examples -------- @@ -915,7 +923,7 @@ def replace_rlc_by_gap_boundaries(self, component=None): component.enabled = False return self._pedb.source_excitation.add_rlc_boundary(component.refdes, False) - def deactivate_rlc_component(self, component=None, create_circuit_port=False, pec_boundary=False): + def deactivate_rlc_component(self, component=None, create_circuit_port=False, pec_boundary=False) -> bool: """Deactivate RLC component with a possibility to convert it to a circuit port. Parameters @@ -1056,7 +1064,7 @@ def _create_pin_group_terminal(self, pingroup, isref=False, term_name=None, term pingroup=pingroup, term_name=term_name, term_type=term_type, isref=isref ) - def _is_top_component(self, cmp): + def _is_top_component(self, cmp) -> bool: """Test the component placement layer. Parameters @@ -1068,8 +1076,6 @@ def _is_top_component(self, cmp): ------- bool ``True`` when component placed on top layer, ``False`` on bottom layer. - - """ top_layer = self._pedb.stackup.signal[0].name if cmp.placement_layer == top_layer: @@ -1112,7 +1118,7 @@ def create( c_value=None, l_value=None, is_parallel=False, - ): + ) -> bool: """Create a component from pins. Parameters @@ -1261,7 +1267,7 @@ def create_component_from_pins( is_rlc=False, ) - def set_component_model(self, componentname, model_type="Spice", modelpath=None, modelname=None): + def set_component_model(self, componentname, model_type="Spice", modelpath=None, modelname=None) -> bool: """Assign a Spice or Touchstone model to a component. Parameters @@ -1312,7 +1318,10 @@ def set_component_model(self, componentname, model_type="Spice", modelpath=None, for pn in pin_names: spice_mod.add_terminal(terminal=str(terminal), pin=pn) terminal += 1 - component.component_property.model = spice_mod + comp_property = component.component_property + comp_property.model = spice_mod + component.component_property = comp_property + self.instances[componentname] = component else: self._logger.error("Wrong number of Pins") return False @@ -1340,7 +1349,7 @@ def set_component_model(self, componentname, model_type="Spice", modelpath=None, component.component_property.model = s_parameter_mod return True - def create_pingroup_from_pins(self, pins, group_name=None): + def create_pingroup_from_pins(self, pins, group_name=None) -> PinGroup: """Create a pin group on a component. Parameters @@ -1395,8 +1404,7 @@ def create_pingroup_from_pins(self, pins, group_name=None): pin_group.net = pins[0].net return pin_group - def delete_single_pin_rlc(self, deactivate_only=False): - # type: (bool) -> list + def delete_single_pin_rlc(self, deactivate_only=False) -> list[str]: """Delete all RLC components with a single pin. Single pin component model type will be reverted to ``"RLC"``. @@ -1435,7 +1443,7 @@ def delete_single_pin_rlc(self, deactivate_only=False): self._pedb.logger.info("Deleted {} components".format(len(deleted_comps))) return deleted_comps - def delete(self, component_name): + def delete(self, component_name) -> bool: """Delete a component. Parameters @@ -1464,7 +1472,7 @@ def delete(self, component_name): return True return False - def disable_rlc_component(self, component_name): + def disable_rlc_component(self, component_name) -> bool: """Disable a RLC component. Parameters @@ -1512,7 +1520,7 @@ def set_solder_ball( reference_size_x=0, reference_size_y=0, reference_height=0, - ): + ) -> bool: """Set cylindrical solder balls on a given component. Parameters @@ -1610,7 +1618,7 @@ def set_component_rlc( ind_value=None, cap_value=None, isparallel=False, - ): + ) -> bool: """Update values for an RLC component. Parameters @@ -1689,7 +1697,7 @@ def update_rlc_from_bom( valuefield="Func des", comptype="Prod name", refdes="Pos / Place", - ): + ) -> bool: """Update the EDC core component values (RLCs) with values coming from a BOM file. Parameters @@ -1759,7 +1767,7 @@ def import_bom( part_name_col=1, comp_type_col=2, value_col=3, - ): + ) -> bool: """Load external BOM file. Parameters @@ -1869,7 +1877,7 @@ def export_bom(self, bom_file, delimiter=","): f.writelines([delimiter.join([refdes, part_name, comp_type, value + "\n"])]) return True - def find_by_reference_designator(self, reference_designator): + def find_by_reference_designator(self, reference_designator) -> list[str]: """Find a component. Parameters @@ -1879,7 +1887,7 @@ def find_by_reference_designator(self, reference_designator): """ return self.instances[reference_designator] - def get_aedt_pin_name(self, pin): + def get_aedt_pin_name(self, pin) -> str: """Retrieve the pin name that is shown in AEDT. .. note:: @@ -1919,7 +1927,7 @@ def get_pins(self, reference_designator, net_name=None, pin_name=None): Returns ------- - + dict[str, PadStackInstance] """ comp = self.find_by_reference_designator(reference_designator) @@ -1932,7 +1940,7 @@ def get_pins(self, reference_designator, net_name=None, pin_name=None): return pins - def get_pin_position(self, pin): + def get_pin_position(self, pin) -> list[float, float]: """Retrieve the pin position in meters. Parameters @@ -1961,7 +1969,7 @@ def get_pin_position(self, pin): transformed_pt_pos = pin.component.transform.transform_point(pt_pos) return [transformed_pt_pos[0].value, transformed_pt_pos[1].value] - def get_pins_name_from_net(self, net_name, pin_list=None): + def get_pins_name_from_net(self, net_name, pin_list=None) -> list[str]: """Retrieve pins belonging to a net. Parameters @@ -1996,7 +2004,7 @@ def get_pins_name_from_net(self, net_name, pin_list=None): pin_names.append(self.get_aedt_pin_name(pin)) return pin_names - def get_nets_from_pin_list(self, pins): + def get_nets_from_pin_list(self, pins) -> list[str]: """Retrieve nets with one or more pins. Parameters @@ -2019,7 +2027,7 @@ def get_nets_from_pin_list(self, pins): """ return list(set([pin.net.name for pin in pins])) - def get_component_net_connection_info(self, refdes): + def get_component_net_connection_info(self, refdes) -> dict[str, list[str]]: """Retrieve net connection information. Parameters @@ -2051,7 +2059,7 @@ def get_component_net_connection_info(self, refdes): data["net_name"].append(net_name) return data - def get_rats(self): + def get_rats(self) -> list[dict[str, str]]: """Retrieve a list of dictionaries of the reference designator, pin names, and net names. Returns @@ -2074,7 +2082,7 @@ def get_rats(self): df_list.append(df) return df_list - def get_through_resistor_list(self, threshold=1): + def get_through_resistor_list(self, threshold=1) -> list[str]: """Retrieve through resistors. Parameters @@ -2108,7 +2116,7 @@ def get_through_resistor_list(self, threshold=1): return through_comp_list - def short_component_pins(self, component_name, pins_to_short=None, width=1e-3): + def short_component_pins(self, component_name, pins_to_short=None, width=1e-3) -> bool: """Short pins of component with a trace. Parameters @@ -2248,7 +2256,7 @@ def short_component_pins(self, component_name, pins_to_short=None, width=1e-3): i += 1 return True - def create_pin_group(self, reference_designator, pin_numbers, group_name=None): + def create_pin_group(self, reference_designator, pin_numbers, group_name=None) -> tuple[str, PinGroup]: """Create pin group on the component. Parameters @@ -2262,7 +2270,8 @@ def create_pin_group(self, reference_designator, pin_numbers, group_name=None): Returns ------- - PinGroup + tuple(str, PinGroup) + (Pingroup name, PinGroup) """ if not isinstance(pin_numbers, list): pin_numbers = [pin_numbers] @@ -2277,7 +2286,6 @@ def create_pin_group(self, reference_designator, pin_numbers, group_name=None): self._pedb.logger.error("No pin found to create pin group") return False pingroup = PinGroup.create(self._active_layout, group_name, pins) - if pingroup.is_null: # pragma: no cover raise RuntimeError(f"Failed to create pin group {group_name}.") else: @@ -2288,7 +2296,7 @@ def create_pin_group(self, reference_designator, pin_numbers, group_name=None): return group_name, PinGroup(self._pedb, pingroup) return False - def create_pin_group_on_net(self, reference_designator, net_name, group_name=None): + def create_pin_group_on_net(self, reference_designator, net_name, group_name=None) -> PinGroup: """Create pin group on component by net name. Parameters @@ -2308,3 +2316,94 @@ def create_pin_group_on_net(self, reference_designator, net_name, group_name=Non pin.name for pin in list(self.instances[reference_designator].pins.values()) if pin.net_name == net_name ] return self.create_pin_group(reference_designator, pins, group_name) + + def load_configuration_from_layout(self, filter=None) -> list[CfgComponent]: + """Returns Components configuration class from layout. + + Parameters + ---------- + filter : list[str], optional + Provide a filter to retrieve only specific components, for instance ["capacitor", "io] will only return + `CfgComponents` object with capacitors and io components. When filter is `None`, no filter is applied and + all components are returned. Default value is `None` + + Returns + ------- + list[CfgComponent. + """ + self._pedb.configuration.components = [] + if filter: + edb_components = [cmp for cmp in list(self.instances.values()) if cmp.type in filter] + else: + edb_components = [cmp for cmp in list(self.instances.values())] + for edb_component in edb_components: + cfg_component = CfgComponent() + cfg_component.reference_designator = edb_component.refdes + cfg_component.enabled = edb_component.enabled + cfg_component.part_type = edb_component.type + if edb_component.type == "io": + cfg_solder_ball_properties = CfgSolderBallProperties() + cfg_solder_ball_properties.shape = edb_component.solder_ball_shape + cfg_solder_ball_properties.height = edb_component.solder_ball_height + cfg_solder_ball_properties.diameter = edb_component.solder_ball_diameter[0] + cfg_component.solder_ball_properties = cfg_solder_ball_properties + + cfg_port_properties = CfgPortProperties() + cfg_port_properties.reference_size_auto = ( + edb_component.component_property.port_property.reference_size_auto + ) + cfg_port_properties.reference_size_x = ( + edb_component.component_property.port_property.get_reference_size()[0].value + ) + cfg_port_properties.reference_size_y = ( + edb_component.component_property.port_property.get_reference_size()[1].value + ) + cfg_port_properties.reference_offset = ( + edb_component.component_property.port_property.reference_height.value + ) + cfg_component.port_properties = cfg_port_properties + elif edb_component.type in ["resistor", "inductor", "capacitor"]: + if isinstance(edb_component.model, PinPairModel): + cfg_component.rlc_model = CfgPinPairs() + for pin_pair in edb_component.model.pin_pairs(): + cfg_pin_pair = CfgPinPair() + cfg_pin_pair.p1 = pin_pair[0] + cfg_pin_pair.p2 = pin_pair[1] + if edb_component.is_parallel_rlc: + cfg_pin_pair.type = "parallel" + else: + cfg_pin_pair.type = "series" + cfg_pin_pair.resistance = round(edb_component.res_value, 15) + cfg_pin_pair.inductance = round(edb_component.ind_value, 15) + cfg_pin_pair.capacitance = round(edb_component.cap_value, 15) + cfg_component.rlc_model.pin_pairs.append(cfg_pin_pair) + self._pedb.configuration.components.append(cfg_component) + return self._pedb.configuration.components + + def load_spice_models_from_layout(self) -> list[CfgSpiceModel]: + """Load Spice model component definition configuration. + + Returns + ------- + list[CfgSpiceModel] + """ + + self._pedb.configuration.spice_models = [] + for spice in [cmp for ref, cmp in self.instances.items() if cmp.model]: + for model in spice.component_def.component_models: + if model.component_model_type.name == "SPICE": + cfg_spice = CfgSpiceModel + cfg_spice.component_definition = spice.name + cfg_spice.name = model.name + cfg_spice.components = list(spice.components.keys()) + cfg_spice.file_path = spice.reference_file + cfg_spice.apply_to_all = True + cfg_spice.reference_net = "" + cfg_spice.sub_circuit_name = model.name + self._pedb.configuration.spice_models.append(cfg_spice) + return self._pedb.configuration.spice_models + + def apply_configuration_to_layout(self) -> bool: + for cfg_cmp in self._pedb.configuration.components.components: + edb_component = self.instances[cfg_cmp.reference_designator] + return True diff --git a/src/pyedb/grpc/database/definitions.py b/src/pyedb/grpc/database/definitions.py index 536313cb22..ddfba665e5 100644 --- a/src/pyedb/grpc/database/definitions.py +++ b/src/pyedb/grpc/database/definitions.py @@ -22,6 +22,10 @@ from ansys.edb.core.geometry.polygon_data import PolygonData as GrpcPolygonData +from pyedb.configuration.data_model.cfg_package_definition_data import ( + CfgPackageDefinition, +) +from pyedb.configuration.data_model.cfg_s_parameter_models_data import CfgSparameter from pyedb.grpc.database.definition.component_def import ComponentDef from pyedb.grpc.database.definition.package_def import PackageDef @@ -68,3 +72,40 @@ def add_package_def(self, name, component_part_name=None, boundary_points=None): package_def.exterior_boundary = GrpcPolygonData(points=boundary_points) return PackageDef(self._pedb, package_def) return False + + def load_s_parameters_models_from_layout(self) -> list[CfgSparameter]: + """Load S-parameter component definition configuration. + + Returns + ------- + list[CfgSparameter] + """ + + self._pedb.configuration.s_parameters = [] + for s_param in [cmp for ref, cmp in self.component.items() if cmp.component_models]: + for model in s_param.component_models: + if model.component_model_type.name == "N_PORT": + cfg_model = CfgSparameter() + cfg_model.component_definition = s_param.name + cfg_model.name = model.name + cfg_model.components = list(s_param.components.keys()) + cfg_model.file_path = s_param.reference_file + cfg_model.apply_to_all = True + cfg_model.reference_net = "" + self._pedb.configuration.s_parameters.append(cfg_model) + return self._pedb.configuration.s_parameters + + def load_package_definition_from_layout(self) -> list[CfgPackageDefinition]: + """Load package definition configuration from layout. + + Returns + ------- + list[CfgPackageDefinition] + """ + self._pedb.configuration.package_definitions = [] + for package in self.package: + cfg_package = CfgPackageDefinition() + cfg_package.name = package.name + cfg_package.height = package.height + # TODO add remaining parameters + return self._pedb.configuration.package_definitions diff --git a/src/pyedb/grpc/database/hfss.py b/src/pyedb/grpc/database/hfss.py index dc0e5d6395..e410327c94 100644 --- a/src/pyedb/grpc/database/hfss.py +++ b/src/pyedb/grpc/database/hfss.py @@ -24,6 +24,7 @@ This module contains the ``EdbHfss`` class. """ import math +from typing import Union import warnings from ansys.edb.core.geometry.polygon_data import PolygonData as GrpcPolygonData @@ -1201,13 +1202,19 @@ def create_rlc_boundary_on_pins(self, positive_pin=None, negative_pin=None, rval def add_setup( self, - name=None, - distribution="linear", - start_freq=0, - stop_freq=20e9, - step_freq=1e6, - discrete_sweep=False, - ): + name: str = "", + distribution: str = "linear", + start_freq: Union[float, str] = "0Hz", + stop_freq: Union[float, str] = "20Ghz", + step_freq: Union[float, str] = "10Mhz", + discrete_sweep: bool = False, + solution_type: str = "single_frequency", + adaptive_frequency: Union[list[float, str], float, str] = 10e9, + max_num_passes: int = 20, + max_delta_s: Union[float, list[float]] = 0.02, + low_frequency: Union[float, str] = "1Ghz", + high_frequency: Union[float, str] = "100Ghz", + ) -> HfssSimulationSetup: """Add a HFSS analysis to EDB. Parameters @@ -1231,6 +1238,23 @@ def add_setup( distribution. Must be integer in that case. discrete_sweep : bool, optional Whether the sweep is discrete. The default is ``False``. + solution_type : str, optional + Give the adaptive solution type. Supported argument `single_frequency`, `multi_frequency`, `broadband`. + Default value is `single_frequency` + adaptive_frequency : Union[list[float, str], float, str], optional + Provide the adaptive solution frequency. Format must be different depending on adaptive solution type. + when `single_frequency` is set argument can be a float or string. If `multi_frequency` is set argument must + be a list[str, float]. if `broadband` is set argument argument must be a float. + max_num_passes : int, optional + Provide the maximum number of adaptive passes. + max_delta_s : Union[float, list[float]], optional + Provide the maximum delta s value for convergence. Format must be different depending on adaptive + solution type. When `single_frequency` is set argument must be float. If `multi_frequency` is set argument + must be a list[float]. if `broadband` is set argument must be a float. + low_frequency : float, optional. + Provides the start meshing frequency when broadband adaptive solution is set. + high_frequency : float, optional. + Provides the high meshing frequency when broadband adaptive solution is set. Returns ------- @@ -1255,9 +1279,43 @@ def add_setup( if name in self._pedb.setups: self._pedb.logger.error(f"HFSS setup {name} already defined.") return False - setup = GrpcHfssSimulationSetup.create(self._pedb.active_cell, name) + setup = HfssSimulationSetup(self._pedb, GrpcHfssSimulationSetup.create(self._pedb.active_cell, name)) start_freq = self._pedb.number_with_units(start_freq, "Hz") stop_freq = self._pedb.number_with_units(stop_freq, "Hz") + if solution_type.lower() == "single_frequency": + if adaptive_frequency: + if isinstance(adaptive_frequency, list): + adaptive_frequency = adaptive_frequency[0] + if max_num_passes: + if isinstance(max_num_passes, list): + max_num_passes = max_num_passes[0] + if max_delta_s: + if isinstance(max_delta_s, list): + max_delta_s = max_delta_s[0] + setup.set_solution_single_frequency( + frequency=str(adaptive_frequency), max_num_passes=max_num_passes, max_delta_s=max_delta_s + ) + if solution_type.lower() == "multi_frequency": + if not isinstance(adaptive_frequency, list): + self._pedb.logger.warning( + "Setting multi frequency adaptive setup requires to pass a list of frequency " "point" + ) + self._pedb.logger.warning("Defaulting to [1e9, 10e9]") + adaptive_frequency = [1e9, 10e9] + if not isinstance(max_delta_s, list): + self._pedb.logger.warning( + "Setting multi frequency adaptive setup requires to pass a list of max " "delta s point" + ) + self._pedb.logger.warning("Defaulting to [0.02, 0.02]") + max_delta_s = [0.02, 0.02] + setup.set_solution_multi_frequencies(frequencies=adaptive_frequency, max_delta_s=max_delta_s) + if solution_type.lower() == "broadband": + setup.set_solution_broadband( + low_frequency=low_frequency, + high_frequency=high_frequency, + max_delta_s=max_delta_s, + max_num_passes=max_num_passes, + ) if distribution.lower() == "linear": distribution = "LIN" elif distribution.lower() == "linear_count": diff --git a/src/pyedb/grpc/database/hierarchy/component.py b/src/pyedb/grpc/database/hierarchy/component.py index e81956d6e3..a538b4c54c 100644 --- a/src/pyedb/grpc/database/hierarchy/component.py +++ b/src/pyedb/grpc/database/hierarchy/component.py @@ -209,18 +209,19 @@ def model(self): """ - if isinstance(self.component_property.model, GrpcSPICEModel): - return SpiceModel(edb_object=self.component_property.model.msg) - elif isinstance(self.component_property.model, GrpcSParameterModel): - return SparamModel(edb_object=self.component_property.model.msg) + if isinstance(self.component_property.model, GrpcSPICEModel) or isinstance( + self.component_property.model, SpiceModel + ): + return SpiceModel(edb_object=self.component_property.model) + elif isinstance(self.component_property.model, GrpcSParameterModel) or isinstance( + self.component_property.model, SparamModel + ): + return SparamModel(edb_object=self.component_property.model) else: return self.component_property.model @model.setter def model(self, value): - if not isinstance(value, PinPairModel): - self._pedb.logger.error("Invalid input. Set model failed.") - comp_prop = self.component_property comp_prop.model = value self.component_property = comp_prop diff --git a/src/pyedb/grpc/database/hierarchy/pingroup.py b/src/pyedb/grpc/database/hierarchy/pingroup.py index 560fe102fb..b16ae5c786 100644 --- a/src/pyedb/grpc/database/hierarchy/pingroup.py +++ b/src/pyedb/grpc/database/hierarchy/pingroup.py @@ -61,7 +61,7 @@ def component(self): :class:`Component ` Pin group component. """ - return Component(self._pedb, super().component) + return Component(self._pedb, next(iter(self.pins.items()))[1].component) @component.setter def component(self, value): diff --git a/src/pyedb/grpc/database/hierarchy/s_parameter_model.py b/src/pyedb/grpc/database/hierarchy/s_parameter_model.py index e48009cf9f..51ea6ebcea 100644 --- a/src/pyedb/grpc/database/hierarchy/s_parameter_model.py +++ b/src/pyedb/grpc/database/hierarchy/s_parameter_model.py @@ -29,5 +29,5 @@ class SparamModel(GrpcSParameterModel): # pragma: no cover """Manage :class:`SParameterModel `""" def __init__(self, edb_object): - super().__init__(self.msg) + super().__init__(edb_object.msg) self._edb_model = edb_object diff --git a/src/pyedb/grpc/database/hierarchy/spice_model.py b/src/pyedb/grpc/database/hierarchy/spice_model.py index f671b1d77a..5dec0c03ec 100644 --- a/src/pyedb/grpc/database/hierarchy/spice_model.py +++ b/src/pyedb/grpc/database/hierarchy/spice_model.py @@ -28,7 +28,7 @@ class SpiceModel(GrpcSpiceModel): # pragma: no cover def __init__(self, edb_object=None, name=None, file_path=None, sub_circuit=None): if edb_object: - super().__init__(edb_object) + super().__init__(edb_object.msg) elif name and file_path: if not sub_circuit: sub_circuit = name diff --git a/src/pyedb/grpc/database/layout/layout.py b/src/pyedb/grpc/database/layout/layout.py index 64c628e73b..a5201695ef 100644 --- a/src/pyedb/grpc/database/layout/layout.py +++ b/src/pyedb/grpc/database/layout/layout.py @@ -34,6 +34,7 @@ import ansys.edb.core.primitive.primitive import ansys.edb.core.primitive.rectangle +from pyedb.configuration.data_model.cfg_pingroup_data import CfgPinGroup from pyedb.grpc.database.hierarchy.component import Component from pyedb.grpc.database.hierarchy.pingroup import PinGroup from pyedb.grpc.database.layout.voltage_regulator import VoltageRegulator @@ -239,3 +240,23 @@ def find_primitive( prims = [i for i in prims if i.layer_name in layer_name] if layer_name is not None else prims prims = [i for i in prims if i.net_name in net_name] if net_name is not None else prims return prims + + def load_pingroup_configuration_from_layout(self) -> list[CfgPinGroup]: + """Load pin group configuration from layout. + + Returns + ------- + list[CfgPinGroup] + """ + try: + self._pedb.configuration.pin_groups = [] + for pin_group in self.pin_groups: + cfg_pin_group = CfgPinGroup() + cfg_pin_group.pins = list(pin_group.pins.keys()) + cfg_pin_group.name = pin_group.name + cfg_pin_group.reference_designator = pin_group.component.name + cfg_pin_group.net = pin_group.net.name + self._pedb.configuration.pin_groups.append(cfg_pin_group) + return self._pedb.configuration.pin_groups + except Exception as e: + raise e.args diff --git a/src/pyedb/grpc/database/nets.py b/src/pyedb/grpc/database/nets.py index 77bf43a9ca..5389ad1641 100644 --- a/src/pyedb/grpc/database/nets.py +++ b/src/pyedb/grpc/database/nets.py @@ -25,6 +25,7 @@ import warnings from pyedb.common.nets import CommonNets +from pyedb.configuration.data_model.cfg_nets_data import CfgNets from pyedb.generic.general_methods import generate_unique_name from pyedb.grpc.database.net.net import Net from pyedb.grpc.database.primitive.bondwire import Bondwire @@ -631,3 +632,28 @@ def merge_nets_polygons(self, net_names_list): if isinstance(net_names_list, str): net_names_list = [net_names_list] return self._pedb.modeler.unite_polygons_on_layer(net_names_list=net_names_list) + + def load_configuration_from_layout(self, filter=None) -> CfgNets: + """Update nets from layout inn configuration. + + Parameters + ---------- + filter : list[str], optional + Provide a filter to retrieve only specific nets, for instance ["GND", "net1"] will only return + The nets included in the list. When filter is `None` no filter is applied and all nets are returned. + Default value is `None` + + Returns + ------- + CfgNets object. + """ + if not self._pedb.configuration.nets: + self._pedb.configuration.nets = CfgNets() + signal_nets = list(self.signal.keys()) + power_nets = list(self.power.keys()) + if filter: + signal_nets = [net for net in signal_nets if net in filter] + power_nets = [net for net in power_nets if net in filter] + self._pedb.configuration.nets.signal_nets = signal_nets + self._pedb.configuration.nets.power_ground_nets = power_nets + return self._pedb.configuration.nets diff --git a/src/pyedb/grpc/database/padstacks.py b/src/pyedb/grpc/database/padstacks.py index 9911090494..37b28c6324 100644 --- a/src/pyedb/grpc/database/padstacks.py +++ b/src/pyedb/grpc/database/padstacks.py @@ -48,6 +48,11 @@ import numpy as np import rtree +from pyedb.configuration.data_model.cfg_padsatck_data import ( + CfgDefinition, + CfgInstance, + CfgPadStacks, +) from pyedb.generic.general_methods import generate_unique_name from pyedb.grpc.database.definition.padstack_def import PadstackDef from pyedb.grpc.database.primitive.padstack_instance import PadstackInstance @@ -1659,3 +1664,31 @@ def reduce_via_in_bounding_box(self, bounding_box, x_samples, y_samples, nets=No if item not in to_keep: all_instances[item].delete() return True + + def load_configuration_from_layout(self) -> CfgPadStacks: + """Load padstack definition configuration. + + Returns + ------- + CfgPadStacks + """ + + range_mapping = {"upper_pad_to_lower_pad": "through", "unknown_range": "unknown"} + self._pedb.configuration.padstacks = CfgPadStacks() + for _, padstack_def in self.definitions.items(): + cfg_def = CfgDefinition( + name=padstack_def.name, + hole_range=range_mapping[padstack_def.hole_range], + hole_material=padstack_def.material, + hole_diameter=padstack_def.hole_diameter, + hole_plating_thickness=padstack_def.hole_plating_thickness, + ) + self._pedb.configuration.padstacks.definitions.append(cfg_def) + for _, padstack_instance in self.instances.items(): + cfg_instance = CfgInstance( + name=padstack_instance.name, + backdrill_bottom=padstack_instance.backdrill_bottom, + backdrill_top=padstack_instance.backdrill_top, + ) + self._pedb.configuration.padstacks.instances.append(cfg_instance) + return self._pedb.configuration.padstacks diff --git a/src/pyedb/grpc/database/simulation_setup/hfss_simulation_setup.py b/src/pyedb/grpc/database/simulation_setup/hfss_simulation_setup.py index a403d8437f..3faad340bf 100644 --- a/src/pyedb/grpc/database/simulation_setup/hfss_simulation_setup.py +++ b/src/pyedb/grpc/database/simulation_setup/hfss_simulation_setup.py @@ -21,6 +21,8 @@ # SOFTWARE. +from typing import Union + from ansys.edb.core.simulation_setup.adaptive_solutions import ( AdaptiveFrequency as GrpcAdaptiveFrequency, ) @@ -75,9 +77,9 @@ def set_solution_multi_frequencies(self, frequencies="5GHz", max_delta_s=0.02): Parameters ---------- - frequencies : str, List[str]. + frequencies : str, list[str]. Adaptive frequencies. - max_delta_s : float, List[float]. + max_delta_s : float, list[float]. Max delta S values. Returns @@ -102,14 +104,20 @@ def set_solution_multi_frequencies(self, frequencies="5GHz", max_delta_s=0.02): except: return False - def set_solution_broadband(self, low_frequency="1GHz", high_frequency="10GHz", max_delta_s=0.02, max_num_passes=10): + def set_solution_broadband( + self, + low_frequency: Union[str, float] = "1GHz", + high_frequency: Union[str, float] = "10GHz", + max_delta_s: float = 0.02, + max_num_passes: int = 10, + ): """Set solution to broadband. Parameters ---------- - low_frequency : str + low_frequency : Union[str, float] Low frequency value. - high_frequency : str + high_frequency : Union[str, float] High frequency value. max_delta_s : float Max delta S value. diff --git a/src/pyedb/grpc/database/source_excitations.py b/src/pyedb/grpc/database/source_excitations.py index dc8c4271d4..3fd969df2a 100644 --- a/src/pyedb/grpc/database/source_excitations.py +++ b/src/pyedb/grpc/database/source_excitations.py @@ -31,6 +31,11 @@ from ansys.edb.core.utility.rlc import Rlc as GrpcRlc from ansys.edb.core.utility.value import Value as GrpcValue +from pyedb.configuration.data_model.cfg_ports_sources_data import ( + CfgPort, + CfgSource, + CfgTerminal, +) from pyedb.generic.general_methods import generate_unique_name from pyedb.grpc.database.components import Component from pyedb.grpc.database.layers.stackup_layer import StackupLayer @@ -1076,7 +1081,7 @@ def create_current_source_on_pin(self, pos_pin, neg_pin, current_value=0, phase_ if not source_name: source_name = ( - f"VSource_{pos_pin.component.name}_{pos_pin.net_name}_{neg_pin.component.name}_{neg_pin.net_name}" + f"ISource_{pos_pin.component.name}_{pos_pin.net_name}_{neg_pin.component.name}_{neg_pin.net_name}" ) return self._create_terminal_on_pins( positive_pin=pos_pin, @@ -2566,3 +2571,66 @@ def place_voltage_probe( ) p_terminal.reference_terminal = n_terminal return self._pedb.create_voltage_probe(p_terminal, n_terminal) + + def load_ports_configuration_from_layout(self) -> list[CfgPort]: + """Load configuration port from layout. + + Returns + ------- + list[CfgPort] + List of configuration ports. + """ + self._pedb.configuration.ports = [] + for _, port in self.excitations.items(): + if not port.is_reference_terminal: + cfg_port = CfgPort() + cfg_port.positive_terminal = CfgTerminal() + if port.terminal_type == "PinGroupTerminal": + cfg_port.name = port.name + cfg_port.type = "circuit" + cfg_port.positive_terminal.pin_group = port._edb_object.pin_group.name + cfg_port.negative_terminal = CfgTerminal() + cfg_port.negative_terminal.pin_group = port.reference_terminal.pin_group.name + elif port.terminal_type == "PadstackInstanceTerminal": + cfg_port.name = port.name + cfg_port.reference_designator = port.component.name + cfg_port.positive_terminal.pin = port._edb_object.name + cfg_port.positive_terminal.net = port._edb_object.net.name + if not port.reference_terminal and not port.is_circuit_port: + cfg_port.type = "coax" + elif port.reference_terminal and port.is_circuit_port: + cfg_port.type = "circuit" + cfg_port.negative_terminal = CfgTerminal() + cfg_port.negative_terminal.pin = port.reference_terminal.name + cfg_port.negative_terminal.pin = port.reference_terminal.net.name + self._pedb.configuration.ports.append(cfg_port) + return self._pedb.configuration.ports + + def load_sources_configuration_from_layout(self) -> list[CfgSource]: + """Load configuration sources from layout. + + Returns + ------- + list[CfgSource] + List of configuration sources. + """ + self._pedb.configuration.sources = [] + for _, source in self.sources.items(): + if not source.is_reference_terminal: + cfg_source = CfgSource() + cfg_source.positive_terminal = CfgTerminal() + cfg_source.type = source.boundary_type + cfg_source.reference_designator = source.component.name + cfg_source.name = source.name + cfg_source.magnitude = source.magnitude.value + cfg_source.negative_terminal = CfgTerminal() + if source.terminal_type == "PinGroupTerminal": + cfg_source.positive_terminal.pin_group = source._edb_object.pin_group.name + cfg_source.negative_terminal.pin_group = source.reference_terminal.pin_group.name + elif source.terminal_type == "PadstackInstanceTerminal": + cfg_source.positive_terminal.pin = source.params[0].name + cfg_source.positive_terminal.net = source.params[0].net.name + cfg_source.negative_terminal.pin = source.reference_terminal.name + cfg_source.negative_terminal.net = source.reference_terminal.net.name + self._pedb.configuration.sources.append(cfg_source) + return self._pedb.configuration.sources diff --git a/src/pyedb/grpc/database/stackup.py b/src/pyedb/grpc/database/stackup.py index 67e124b28a..9041acc4c2 100644 --- a/src/pyedb/grpc/database/stackup.py +++ b/src/pyedb/grpc/database/stackup.py @@ -52,6 +52,11 @@ from ansys.edb.core.utility.transform3d import Transform3D as GrpcTransform3D from ansys.edb.core.utility.value import Value as GrpcValue +from pyedb.configuration.data_model.cfg_stackup_data import ( + CfgLayer, + CfgMaterial, + CfgStackup, +) from pyedb.generic.general_methods import ET, generate_unique_name from pyedb.grpc.database.layers.layer import Layer from pyedb.grpc.database.layers.stackup_layer import StackupLayer @@ -2588,3 +2593,31 @@ def _convert_elevation(el): elif show: plt.show() return plt + + def load_configuration_from_layout(self) -> CfgStackup: + """Load layer stackup configuration from layout. + + Returns + ------- + CfgStackup + Configuration stackup object. + """ + self._pedb.configuration.stackup = CfgStackup() + for _, material in self._pedb.materials.materials.items(): + cfg_material = CfgMaterial( + name=material.name, + permittivity=material.permittivity, + conductivity=material.conductivity, + dielectric_loss_tangent=material.loss_tangent, + ) + self._pedb.configuration.stackup.materials.append(cfg_material) + for _, layer in self.layers.items(): + cfg_layer = CfgLayer( + name=layer.name, + type=layer.type, + material=layer.material, + thickness=layer.thickness, + fill_material=layer.dielectric_fill, + ) + self._pedb.configuration.stackup.layers.append(cfg_layer) + return self._pedb.configuration.stackup diff --git a/src/pyedb/grpc/edb.py b/src/pyedb/grpc/edb.py index 061f2ff8ca..14055314e5 100644 --- a/src/pyedb/grpc/edb.py +++ b/src/pyedb/grpc/edb.py @@ -47,7 +47,15 @@ from ansys.edb.core.utility.value import Value as GrpcValue import rtree -from pyedb.configuration.configuration import Configuration +from pyedb.configuration.data_model.cfg_setup_data import ( + CfgDcIrSettings, + CfgFrequency, + CfgFrequencySweep, + CfgSetup, +) +from pyedb.configuration.data_model.configuration_data import ( + Configuration as ConfigurationData, +) from pyedb.generic.constants import unit_converter from pyedb.generic.general_methods import ( generate_unique_name, @@ -418,17 +426,17 @@ def _init_objects(self): self._extended_nets = ExtendedNets(self) @property - def cell_names(self): + def cell_names(self) -> list[str]: """Cell name container. Returns ------- - list of cell names : List[str] + list of cell names : list[str] """ return [cell.name for cell in self.active_db.top_circuit_cells] @property - def design_variables(self): + def design_variables(self) -> dict[str, float]: """Get all edb design variables. Returns @@ -438,7 +446,7 @@ def design_variables(self): return {i: self.active_cell.get_variable_value(i).value for i in self.active_cell.get_all_variable_names()} @property - def project_variables(self): + def project_variables(self) -> dict[str, float]: """Get all project variables. Returns @@ -449,7 +457,7 @@ def project_variables(self): return {i: self.active_db.get_variable_value(i).value for i in self.active_db.get_all_variable_names()} @property - def layout_validation(self): + def layout_validation(self) -> LayoutValidation: """Return LayoutValidation object. Returns @@ -459,7 +467,7 @@ def layout_validation(self): return LayoutValidation(self) @property - def variables(self): + def variables(self) -> dict[str, float]: """Get all Edb variables. Returns @@ -475,7 +483,7 @@ def variables(self): return all_vars @property - def terminals(self): + def terminals(self) -> dict[str, Terminal]: """Get terminals belonging to active layout. Returns @@ -485,7 +493,7 @@ def terminals(self): return {i.name: i for i in self.layout.terminals} @property - def excitations(self): + def excitations(self) -> dict[str, Union[BundleWavePort, GapPort]]: """Get all layout excitations. Returns @@ -503,7 +511,7 @@ def excitations(self): return temp @property - def ports(self): + def ports(self) -> dict[str, Terminal]: """Get all ports. Returns @@ -537,7 +545,7 @@ def ports(self): return ports @property - def excitations_nets(self): + def excitations_nets(self) -> list[str]: """Get all net names with excitation defined. Returns @@ -548,14 +556,18 @@ def excitations_nets(self): return list(set([i.net.name for i in self.layout.terminals if not i.is_reference_terminal])) @property - def sources(self): + def sources(self) -> dict[str, Terminal]: """Get all layout sources. Returns ------- Dict: Dic[str, :class:`Terminal `] """ - return self.terminals + return { + k: v + for k, v in self.terminals.items() + if v.boundary_type in ["voltage_source", "current_source"] and not v.is_reference_terminal + } @property def voltage_regulator_modules(self): @@ -574,7 +586,7 @@ def voltage_regulator_modules(self): return _vrms @property - def probes(self): + def probes(self) -> dict[str, Terminal]: """Get all layout probes. Returns @@ -585,7 +597,7 @@ def probes(self): terms = [term for term in self.layout.terminals if term.boundary_type.value == 8] return {ter.name: ter for ter in terms} - def open_edb(self, restart_rpc_server=False, kill_all_instances=False): + def open_edb(self, restart_rpc_server=False) -> bool: """Open EDB. Returns @@ -626,7 +638,7 @@ def open_edb(self, restart_rpc_server=False, kill_all_instances=False): self.logger.error("Builder was not initialized.") return True - def create_edb(self, restart_rpc_server=False, kill_all_instances=False): + def create_edb(self, restart_rpc_server=False) -> bool: """Create EDB. Returns @@ -645,8 +657,8 @@ def create_edb(self, restart_rpc_server=False, kill_all_instances=False): except Exception as e: self.logger.error(e.args[0]) if not self.db: - raise ValueError("Failed creating EDB.") self._active_cell = None + raise ValueError("Failed creating EDB.") else: if not self.cellname: self.cellname = generate_unique_name("Cell") @@ -801,7 +813,7 @@ def import_layout_file( self.edbpath = os.path.join(working_dir, aedb_name) return self.open_edb() - def export_to_ipc2581(self, ipc_path=None, units="MILLIMETER"): + def export_to_ipc2581(self, ipc_path=None, units="MILLIMETER") -> str: """Create an XML IPC2581 file from the active EDB. .. note:: @@ -847,15 +859,36 @@ def export_to_ipc2581(self, ipc_path=None, units="MILLIMETER"): return False @property - def configuration(self): + def configuration(self) -> ConfigurationData: """Edb project configuration from a file. Returns ------- :class:`Configuration `. """ + from pyedb.configuration.data_model.cfg_boundaries_data import CfgBoundaries + from pyedb.configuration.data_model.cfg_general_data import CfgGeneral + from pyedb.configuration.data_model.cfg_nets_data import CfgNets + from pyedb.configuration.data_model.cfg_operations_data import CfgOperations + from pyedb.configuration.data_model.cfg_padsatck_data import CfgPadStacks + from pyedb.configuration.data_model.cfg_stackup_data import CfgStackup + if not self._configuration: - self._configuration = Configuration(self) + self._configuration = ConfigurationData(self) + self._configuration.general = CfgGeneral() + self._configuration.boundaries = CfgBoundaries() + self._configuration.nets = CfgNets() + self._configuration.components = [] + self._configuration.pin_groups = [] + self._configuration.sources = [] + self._configuration.ports = [] + self._configuration.setups = [] + self._configuration.stackup = CfgStackup() + self._configuration.padstacks = CfgPadStacks() + self._configuration.s_parameters = [] + self._configuration.spice_models = [] + self._configuration.package_definitions = [] + self._configuration.operations = CfgOperations() return self._configuration def edb_exception(self, ex_value, tb_data): @@ -917,7 +950,7 @@ def active_cell(self, value): raise "No valid design." @property - def components(self): + def components(self) -> Components: """Edb Components methods and properties. Returns @@ -935,7 +968,7 @@ def components(self): return self._components @property - def stackup(self): + def stackup(self) -> Stackup: """Stackup manager. Returns @@ -955,7 +988,7 @@ def stackup(self): return self._stackup @property - def source_excitation(self): + def source_excitation(self) -> SourceExcitation: """Returns layout source excitations. Returns @@ -966,7 +999,7 @@ def source_excitation(self): return self._source_excitation @property - def materials(self): + def materials(self) -> Materials: """Material Database. Returns @@ -986,7 +1019,7 @@ def materials(self): return self._materials @property - def padstacks(self): + def padstacks(self) -> Padstacks: """Returns padstack object. @@ -1009,7 +1042,7 @@ def padstacks(self): return self._padstack @property - def siwave(self): + def siwave(self) -> Siwave: """Returns SIWave object. Returns @@ -1027,7 +1060,7 @@ def siwave(self): return self._siwave @property - def hfss(self): + def hfss(self) -> Hfss: """Returns HFSS object. Returns @@ -1047,7 +1080,7 @@ def hfss(self): return self._hfss @property - def nets(self): + def nets(self) -> Nets: """Returns nets object. Returns @@ -1067,7 +1100,7 @@ def nets(self): return self._nets @property - def net_classes(self): + def net_classes(self) -> NetClass: """Returns net classes object. Returns @@ -1085,7 +1118,7 @@ def net_classes(self): return {net.name: NetClass(self, net) for net in self.active_layout.net_classes} @property - def extended_nets(self): + def extended_nets(self) -> ExtendedNets: """Returns extended nets. Returns @@ -1104,7 +1137,7 @@ def extended_nets(self): return self._extended_nets @property - def differential_pairs(self): + def differential_pairs(self) -> DifferentialPairs: """Returns differential pairs. Returns @@ -1122,7 +1155,7 @@ def differential_pairs(self): return self._differential_pairs @property - def modeler(self): + def modeler(self) -> Modeler: """Returns primitives modeler object. Returns @@ -1140,7 +1173,7 @@ def modeler(self): return self._modeler @property - def layout(self): + def layout(self) -> Layout: """Returns Layout object. Returns @@ -1150,7 +1183,7 @@ def layout(self): return Layout(self) @property - def active_layout(self): + def active_layout(self) -> Layout: """Active layout. Returns @@ -1171,7 +1204,9 @@ def layout_instance(self): self._layout_instance = self.layout.layout_instance return self._layout_instance - def get_connected_objects(self, layout_object_instance): + def get_connected_objects( + self, layout_object_instance + ) -> list[Union[PadstackInstance, Path, Rectangle, Circle, Polygon]]: """Returns connected objects. Returns @@ -1255,7 +1290,7 @@ def point_data(self, x, y=None): return PointData(x, y) @staticmethod - def _is_file_existing_and_released(filename): + def _is_file_existing_and_released(filename) -> bool: if os.path.exists(filename): try: os.rename(filename, filename + "_") @@ -1267,13 +1302,13 @@ def _is_file_existing_and_released(filename): return False @staticmethod - def _is_file_existing(filename): + def _is_file_existing(filename) -> bool: if os.path.exists(filename): return True else: return False - def _wait_for_file_release(self, timeout=30, file_to_release=None): + def _wait_for_file_release(self, timeout=30, file_to_release=None) -> bool: if not file_to_release: file_to_release = os.path.join(self.edbpath) tstart = time.time() @@ -1303,7 +1338,7 @@ def _wait_for_file_exists(self, timeout=30, file_to_release=None, wait_count=4): times = 0 time.sleep(0.250) - def close_edb(self): + def close_edb(self) -> bool: """Close EDB and cleanup variables. Returns @@ -1318,7 +1353,7 @@ def close_edb(self): self._clean_variables() return True - def save_edb(self): + def save_edb(self) -> bool: """Save the EDB file. Returns @@ -1332,7 +1367,7 @@ def save_edb(self): self.logger.info("EDB file save time: {0:.2f}ms".format(elapsed_time * 1000.0)) return True - def save_edb_as(self, fname): + def save_edb_as(self, fname) -> bool: """Save the EDB file as another file. Parameters @@ -1371,7 +1406,7 @@ def execute(self, func): # return self.edb_api.utility.utility.Command.Execute(func) pass - def import_cadence_file(self, inputBrd, WorkDir=None, anstranslator_full_path="", use_ppe=False): + def import_cadence_file(self, inputBrd, WorkDir=None, anstranslator_full_path="", use_ppe=False) -> bool: """Import a board file and generate an ``edb.def`` file in the working directory. Parameters @@ -1410,7 +1445,7 @@ def import_gds_file( tech_file=None, map_file=None, layer_filter=None, - ): + ) -> bool: """Import a GDS file and generate an ``edb.def`` file in the working directory. ..note:: @@ -1503,7 +1538,7 @@ def _create_extent( include_pingroups=True, pins_to_preserve=None, inlcude_voids_in_extents=False, - ): + ) -> GrpcPolygonData: from ansys.edb.core.geometry.polygon_data import ExtentType as GrpcExtentType if extent_type in [ @@ -1582,7 +1617,7 @@ def _create_conformal( reference_list=[], pins_to_preserve=None, inlcude_voids_in_extents=False, - ): + ) -> GrpcPolygonData: names = [] _polys = [] for net in net_signals: @@ -1652,7 +1687,7 @@ def _create_conformal( areas = [i.area() for i in _poly_unite] return _poly_unite[areas.index(max(areas))] - def _smart_cut(self, reference_list=[], expansion_size=1e-12): + def _smart_cut(self, reference_list=[], expansion_size=1e-12) -> list[GrpcPolygonData]: from ansys.edb.core.geometry.point_data import PointData as GrpcPointData _polys = [] @@ -1682,7 +1717,7 @@ def _create_convex_hull( smart_cut=False, reference_list=[], pins_to_preserve=None, - ): + ) -> GrpcPolygonData: names = [] _polys = [] for net in net_signals: @@ -1736,7 +1771,7 @@ def cutout( simple_pad_check=True, keep_lines_as_path=False, include_voids_in_extents=False, - ): + ) -> list[float]: """Create a cutout using an approach entirely based on PyAEDT. This method replaces all legacy cutout methods in PyAEDT. It does in sequence: @@ -1974,7 +2009,7 @@ def _create_cutout_legacy( check_terminals=False, include_pingroups=True, inlcude_voids_in_extents=False, - ): + ) -> list[list[float]]: expansion_size = GrpcValue(expansion_size).value # validate nets in layout @@ -2306,7 +2341,7 @@ def pins_clean(pinst): self.logger.reset_timer() return [[pt.x.value, pt.y.value] for pt in _poly.without_arcs().points] - def get_conformal_polygon_from_netlist(self, netlist=None): + def get_conformal_polygon_from_netlist(self, netlist=None) -> GrpcPolygonData: """Returns conformal polygon data based on a netlist. Parameters @@ -2337,7 +2372,7 @@ def get_conformal_polygon_from_netlist(self, netlist=None): else: return False - def number_with_units(self, value, units=None): + def number_with_units(self, value, units=None) -> str: """Convert a number to a string with units. If value is a string, it's returned as is. Parameters @@ -2523,7 +2558,7 @@ def _create_cutout_on_point_list( return [[pt.x.value, pt.y.value] for pt in polygon_data.without_arcs().points] @staticmethod - def write_export3d_option_config_file(path_to_output, config_dictionaries=None): + def write_export3d_option_config_file(path_to_output, config_dictionaries=None) -> str: """Write the options for a 3D export to a configuration file. Parameters @@ -2534,6 +2569,10 @@ def write_export3d_option_config_file(path_to_output, config_dictionaries=None): config_dictionaries : dict, optional Configuration dictionaries. The default is ``None``. + Returns + ------- + str + Configuration file path. """ option_config = { "UNITE_NETS": 1, @@ -2567,7 +2606,7 @@ def export_hfss( num_cores=None, aedt_file_name=None, hidden=False, - ): + ) -> str: """Export EDB to HFSS. Parameters @@ -2610,7 +2649,7 @@ def export_q3d( num_cores=None, aedt_file_name=None, hidden=False, - ): + ) -> str: """Export EDB to Q3D. Parameters @@ -2660,7 +2699,7 @@ def export_maxwell( num_cores=None, aedt_file_name=None, hidden=False, - ): + ) -> str: """Export EDB to Maxwell 3D. Parameters @@ -2704,7 +2743,7 @@ def export_maxwell( hidden=hidden, ) - def solve_siwave(self): + def solve_siwave(self) -> str: """Close EDB and solve it with Siwave. Returns @@ -2782,7 +2821,7 @@ def export_siwave_dc_results( hidden=True, ) - def variable_exists(self, variable_name): + def variable_exists(self, variable_name) -> bool: """Check if a variable exists or not. Returns @@ -2800,7 +2839,7 @@ def variable_exists(self, variable_name): return True return False - def get_variable(self, variable_name): + def get_variable(self, variable_name) -> float: """Return Variable Value if variable exists. Parameters @@ -2821,7 +2860,7 @@ def get_variable(self, variable_name): self.logger.info(f"Variable {variable_name} doesn't exists.") return False - def add_project_variable(self, variable_name, variable_value, description=None): + def add_project_variable(self, variable_name, variable_value, description=None) -> bool: """Add a variable to database. The variable will have the prefix `$`. Parameters @@ -2850,14 +2889,15 @@ def add_project_variable(self, variable_name, variable_value, description=None): if not variable_name.startswith("$"): variable_name = f"${variable_name}" if not self.variable_exists(variable_name): - var = self.active_db.add_variable(variable_name, variable_value) + self.active_db.add_variable(variable_name, variable_value) if description: self.active_db.set_variable_desc(name=variable_name, desc=description) + return True else: self.logger.error(f"Variable {variable_name} already exists.") return False - def add_design_variable(self, variable_name, variable_value, is_parameter=False, description=None): + def add_design_variable(self, variable_name, variable_value, is_parameter=False, description=None) -> bool: """Add a variable to edb. The variable can be a design one or a project variable (using ``$`` prefix). Parameters @@ -2893,15 +2933,15 @@ def add_design_variable(self, variable_name, variable_value, is_parameter=False, if variable_name.startswith("$"): variable_name = variable_name[1:] if not self.variable_exists(variable_name): - var = self.active_cell.add_variable(variable_name, variable_value) + self.active_cell.add_variable(variable_name, variable_value) if description: self.active_cell.set_variable_desc(name=variable_name, desc=description) - return var + return True else: self.logger.error(f"Variable {variable_name} already exists.") return False - def change_design_variable_value(self, variable_name, variable_value): + def change_design_variable_value(self, variable_name, variable_value) -> bool: """Change a variable value. Parameters @@ -2927,10 +2967,13 @@ def change_design_variable_value(self, variable_name, variable_value): if self.variable_exists(variable_name): if variable_name in self.db.get_all_variable_names(): self.db.set_variable_value(variable_name, GrpcValue(variable_value)) + return True elif variable_name in self.active_cell.get_all_variable_names(): self.active_cell.set_variable_value(variable_name, GrpcValue(variable_value)) + return True + return False - def get_bounding_box(self): + def get_bounding_box(self) -> list[list[float]]: """Get the layout bounding box. Returns @@ -2951,7 +2994,7 @@ def get_statistics(self, compute_area=False): """ return self.modeler.get_layout_statistics(evaluate_area=compute_area, net_list=None) - def are_port_reference_terminals_connected(self, common_reference=None): + def are_port_reference_terminals_connected(self, common_reference=None) -> bool: """Check if all terminal references in design are connected. If the reference nets are different, there is no hope for the terminal references to be connected. After we have identified a common reference net we need to loop the terminals again to get @@ -3039,7 +3082,9 @@ def are_port_reference_terminals_connected(self, common_reference=None): return True if len(iDintersection) > 0 else False @property - def setups(self): + def setups( + self, + ) -> Union[HfssSimulationSetup, SiwaveSimulationSetup, SIWaveDCIRSimulationSetup, RaptorXSimulationSetup]: """Get the dictionary of all EDB HFSS and SIwave setups. Returns @@ -3081,7 +3126,7 @@ def hfss_setups(self): return setups @property - def siwave_dc_setups(self): + def siwave_dc_setups(self) -> dict[str, SIWaveDCIRSimulationSetup]: """Active Siwave DC IR Setups. Returns @@ -3093,7 +3138,7 @@ def siwave_dc_setups(self): return {name: i for name, i in self.setups.items() if isinstance(i, SIWaveDCIRSimulationSetup)} @property - def siwave_ac_setups(self): + def siwave_ac_setups(self) -> dict[str, SiwaveSimulationSetup]: """Active Siwave SYZ setups. Returns @@ -3103,7 +3148,9 @@ def siwave_ac_setups(self): """ return {name: i for name, i in self.setups.items() if isinstance(i, SiwaveSimulationSetup)} - def create_hfss_setup(self, name=None, start_frequency="0GHz", stop_frequency="20GHz", step_frequency="10MHz"): + def create_hfss_setup( + self, name=None, start_frequency="0GHz", stop_frequency="20GHz", step_frequency="10MHz" + ) -> HfssSimulationSetup: """Create an HFSS simulation setup from a template. . deprecated:: pyedb 0.30.0 @@ -3131,7 +3178,7 @@ def create_hfss_setup(self, name=None, start_frequency="0GHz", stop_frequency="2 step_freq=step_frequency, ) - def create_raptorx_setup(self, name=None): + def create_raptorx_setup(self, name=None) -> RaptorXSimulationSetup: """Create an RaptorX simulation setup from a template. Parameters @@ -3185,7 +3232,7 @@ def create_hfsspi_setup(self, name=None): # TODO check HFSS-PI with Grpc. seems to defined at terminal level not setup. pass - def create_siwave_syz_setup(self, name=None, **kwargs): + def create_siwave_syz_setup(self, name=None, **kwargs) -> SiwaveSimulationSetup: """Create a setup from a template. Parameters @@ -3222,7 +3269,7 @@ def create_siwave_syz_setup(self, name=None, **kwargs): setattr(setup, k, v) return self.setups[name] - def create_siwave_dc_setup(self, name=None, **kwargs): + def create_siwave_dc_setup(self, name=None, **kwargs) -> SIWaveDCIRSimulationSetup: """Create a setup from a template. Parameters @@ -3252,7 +3299,7 @@ def create_siwave_dc_setup(self, name=None, **kwargs): setattr(setup, k, v) return setup - def calculate_initial_extent(self, expansion_factor): + def calculate_initial_extent(self, expansion_factor) -> float: """Compute a float representing the larger number between the dielectric thickness or trace width multiplied by the nW factor. The trace width search is limited to nets with ports attached. @@ -3284,7 +3331,7 @@ def calculate_initial_extent(self, expansion_factor): self.logger.info(f"The W factor is {expansion_factor}, The initial extent = {max_width}") return max_width - def copy_zones(self, working_directory=None): + def copy_zones(self, working_directory=None) -> list[str]: """Copy multizone EDB project to one new edb per zone. Parameters @@ -3465,7 +3512,9 @@ def _get_connected_ports_from_multizone_cutout(terminal_info_dict): connected_ports_list.append((port1_connexion, port2_connexion)) return connected_ports_list - def create_port(self, terminal, ref_terminal=None, is_circuit_port=False, name=None): + def create_port( + self, terminal, ref_terminal=None, is_circuit_port=False, name=None + ) -> Union[GapPort, WavePort, CoaxPort, BundleWavePort]: """Create a port. Parameters @@ -3503,7 +3552,7 @@ def create_port(self, terminal, ref_terminal=None, is_circuit_port=False, name=N terminal.name = name return self.ports[terminal.name] - def create_voltage_probe(self, terminal, ref_terminal): + def create_voltage_probe(self, terminal, ref_terminal) -> Terminal: """Create a voltage probe. Parameters @@ -3532,7 +3581,7 @@ def create_voltage_probe(self, terminal, ref_terminal): term.ref_terminal = ref_terminal return term - def create_voltage_source(self, terminal, ref_terminal): + def create_voltage_source(self, terminal, ref_terminal) -> Terminal: """Create a voltage source. Parameters @@ -3561,7 +3610,7 @@ def create_voltage_source(self, terminal, ref_terminal): term.ref_terminal = ref_terminal return term - def create_current_source(self, terminal, ref_terminal): + def create_current_source(self, terminal, ref_terminal) -> Terminal: """Create a current source. Parameters @@ -3631,7 +3680,7 @@ def auto_parametrize_design( expand_polygons_size=0, expand_voids_size=0, via_offset=True, - ): + ) -> list[str]: """Assign automatically design and project variables with current values. Parameters @@ -3902,7 +3951,7 @@ def create_model_for_arbitrary_wave_ports( terminal_diameter=None, output_edb=None, launching_box_thickness="100um", - ): + ) -> bool: """Generate EDB design to be consumed by PyAEDT to generate arbitrary wave ports shapes. This model has to be considered as merged onto another one. The current opened design must have voids surrounding the pad-stacks where wave ports terminal will be created. THe open design won't be edited, only @@ -4069,7 +4118,7 @@ def definitions(self): return Definitions(self) @property - def workflow(self): + def workflow(self) -> Workflow: """Returns workflow class. Returns @@ -4078,7 +4127,7 @@ def workflow(self): """ return Workflow(self) - def export_gds_comp_xml(self, comps_to_export, gds_comps_unit="mm", control_path=None): + def export_gds_comp_xml(self, comps_to_export, gds_comps_unit="mm", control_path=None) -> bool: """Exports an XML file with selected components information for use in a GDS import. Parameters ---------- @@ -4117,3 +4166,44 @@ def export_gds_comp_xml(self, comps_to_export, gds_comps_unit="mm", control_path ET.indent(tree, space="\t", level=0) tree.write(control_path) return True if os.path.exists(control_path) else False + + def load_simulation_setup_configuration_from_layout(self) -> list[CfgSetup]: + """Import setup configuratiomn from layout. + + Returns + ------- + list[CfgSetup] + List of setup configuration. + """ + self.configuration.setups = [] + for _, setup in self.setups.items(): + cfg_setup = CfgSetup() + setup_type_mapping = {"HFSS": "HFSS", "SI_WAVE": "siwave_syz", "SI_WAVE_DCIR": "siwave_dc"} + cfg_setup.name = setup.name + cfg_setup.type = setup_type_mapping[setup.type.name] + if setup.type.name == "HFSS": + cfg_setup.f_adapt = setup.settings.general.single_frequency_adaptive_solution.adaptive_frequency + cfg_setup.max_num_passes = setup.settings.general.single_frequency_adaptive_solution.max_passes + cfg_setup.max_mag_delta_s = setup.settings.general.single_frequency_adaptive_solution.max_delta + if setup.type.name == "SI_WAVE_DCIR": + cfg_setup.dc_ir_settings = CfgDcIrSettings() + cfg_setup.dc_ir_settings.export_dc_thermal_data = setup.export_dc_thermal_data + cfg_setup.dc_slider_position = setup.dc.dc_slider_pos + if setup.type.name == "SI_WAVE": + cfg_setup.dc_slider_position = setup.dc.dc_slider_pos + cfg_setup.freq_sweep = CfgFrequencySweep() + if setup.sweep_data: + cfg_setup.freq_sweep.name = setup.sweep_data[0].name + if setup.sweep_data[0].type.name == "INTERPOLATING_SWEEP": + cfg_setup.freq_sweep.type = "Interpolation" + else: + cfg_setup.freq_sweep.type = "Discrete" + for freq_sweep in setup.sweep_data: + cfg_freq = CfgFrequency() + cfg_freq.distribution = freq_sweep.frequency_data.distribution.name + cfg_freq.start = freq_sweep.frequency_data.start_f + cfg_freq.stop = freq_sweep.frequency_data.end_f + cfg_freq.step = freq_sweep.frequency_data.step + cfg_setup.freq_sweep.frequencies.append(cfg_freq) + self.configuration.setups.append(cfg_setup) + return self.configuration.setups diff --git a/tests/grpc/system/test_edb_configuration_2p0.py b/tests/grpc/system/test_edb_configuration_2p0.py index 8dbe6aed7a..803efc07d7 100644 --- a/tests/grpc/system/test_edb_configuration_2p0.py +++ b/tests/grpc/system/test_edb_configuration_2p0.py @@ -20,6 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import json +import os from pathlib import Path import pytest @@ -1371,3 +1372,52 @@ def test_probes(self, edb_examples): assert edbapp.configuration.load(data, apply_file=True) assert "probe1" in edbapp.probes edbapp.close() + + def test_configfile_refactoring(self, edb_examples): + edb = edb_examples.get_si_verse() + spice_file = os.path.join(edb.edbpath, "GRM32ER72A225KA35_25C_0V.sp") + touchstone_file = os.path.join(self.example_models_path, file_folder_path)("TEDB/ANSYS-HSD_V1.aedb") + # Add setup + setup = edb.hfss.add_setup(name="test_setup") + setup.add_sweep("test_sweep") + + # Add port on pin group + power_nets = edb.components.instances["U1"].nets + power_net = next(net for net in power_nets if net in edb.nets.power and not net == "GND") + ref_pin_group = edb.components.create_pin_group_on_net(reference_designator="U1", net_name="GND") + positive_pingroup = edb.components.create_pin_group_on_net(reference_designator="U1", net_name=power_net) + edb.source_excitation.create_circuit_port_on_pin_group( + positive_pingroup[0], neg_pin_group_name=ref_pin_group[0] + ) + + # Add coax ports + pci_nets = [net for net in edb.components.IOs["X1"].nets if "PCIe" in net] + edb.source_excitation.create_coax_port_on_component(ref_des_list="X1", net_list=pci_nets) + + # Add sources + pos_pin1 = edb.components.ICs["U10"].pins["1"] + neg_pin1 = edb.components.ICs["U10"].pins["2"] + pos_pin2 = edb.components.ICs["U10"].pins["3"] + neg_pin2 = edb.components.ICs["U10"].pins["4"] + edb.source_excitation.create_current_source_on_pin(pos_pin=pos_pin1, neg_pin=neg_pin1, current_value=0.85) + edb.source_excitation.create_voltage_source_on_pin(pos_pin=pos_pin2, neg_pin=neg_pin2, voltage_value=2.5) + + # Add sparameter + for refdes, cmp in edb.components.capacitors.items(): + edb.components.set_component_model( + componentname=refdes, model_type="Touchstone", modelpath=touchstone_file, modelname="test" + ) + + # Add spice + for refdes, cmp in edb.components.inductors.items(): + edb.components.set_component_model( + componentname=refdes, model_type="Spice", modelpath=spice_file, modelname="test2" + ) + + # deactivate resistors + for refdes, comp in edb.components.resistors.items(): + comp.enabled = False + + edb.configuration.load_from_layout() + edb.configuration.export_configuration_file(r"D:\Temp\test_export.json") + edb.configuration.load_file(config_file)