diff --git a/_unittest/example_models/T20/test_cad.nas b/_unittest/example_models/T20/test_cad.nas index 948c6b48f5a..640b41ee14f 100644 --- a/_unittest/example_models/T20/test_cad.nas +++ b/_unittest/example_models/T20/test_cad.nas @@ -23,7 +23,7 @@ GRID 17 -142.085-81.8469969.8416 GRID 18 338.1222.4335859436.2341 GRID 19 328.0788-91.8056 418.809 GRID 20 260.6554-89.1117 459.994 -GRID 21 1574.546 -4.1-111484.805 +GRID 21 1574.546 -34.1-11484.805 GRID 22 4411.665-98.6627 505.296 GRID 23 4410.392-95.7236504.7545 GRID 24 4412.102-93.0528505.4793 @@ -38,18 +38,18 @@ GRID 32 4410.434-95.7239504.6637 GRID 33 4414.683-98.9433506.4686 GRID 34 4416.393-96.2763507.1935 GRID 35 4413.413-96.0004505.9284 -GRID 40 0.0 0.0 0.0 -GRID 41 1.0 0.0 0.0 -GRID 42 1.0 1.0 0.0 -GRID 43 0.0 1.0 0.0 -GRID 44 0.0 0.0 1.0 -GRID 45 1.0 0.0 1.0 -GRID 46 1.0 1.0 1.0 -GRID 47 0.0 1.0 1.0 -GRID 50 0.0 0.0 0.0 -GRID 51 2.0 0.0 0.0 -GRID 52 0.0 2.0 0.0 -GRID 53 0.0 0.0 2.0 +GRID 40 0.0 0.0 0.0 +GRID 41 1.0 0.0 0.0 +GRID 42 1.0 1.0 0.0 +GRID 43 0.0 1.0 0.0 +GRID 44 0.0 0.0 1.0 +GRID 45 1.0 0.0 1.0 +GRID 46 1.0 1.0 1.0 +GRID 47 0.0 1.0 1.0 +GRID 50 0.0 0.0 0.0 +GRID 51 2.0 0.0 0.0 +GRID 52 0.0 2.0 0.0 +GRID 53 0.0 0.0 2.0 CTRIA3 92455 31 4 5 6 CTRIA3 92456 31 5 10 11 CTRIA3 92457 31 21 20 16 @@ -64,7 +64,8 @@ CPENTA 25229 9 27 22 28 33 29 35 CPENTA 25279 9 25 28 24 30 35 31 CPENTA 25284 9 26 27 28 34 33 35 CPENTA 25328 9 26 28 25 34 35 30 -CHEXA 1 105 40 41 42 43 44 45 46 47 +CHEXA 1 105 40 41 42 43 44 45 +* 46 47 CTETRA 1 115 50 51 52 53 ENDDATA diff --git a/_unittest/example_models/T20/test_cad_2.nas b/_unittest/example_models/T20/test_cad_2.nas new file mode 100644 index 00000000000..f41ccef4bea --- /dev/null +++ b/_unittest/example_models/T20/test_cad_2.nas @@ -0,0 +1,24 @@ +$ +$ Settings : +$ +$ Output format : MSC Nastran +$ +$ Output : Visible +$ +$ +$ +$ +BEGIN BULK +GRID* 4 627.87512 568.96751* +* 1942.1985 +GRID* 5 812.95973 486.74968* +* 1495.9564 +GRID* 6 812.99916 484.24676* +* 1470.1764 +CTRIA3* 75986 19 4 5 +* 6 + + +ENDDATA +$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ +$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ diff --git a/_unittest/test_00_EDB.py b/_unittest/test_00_EDB.py index 21ca2b6b926..32b3a9f6949 100644 --- a/_unittest/test_00_EDB.py +++ b/_unittest/test_00_EDB.py @@ -264,12 +264,10 @@ def test_009_vias_creation(self): assert self.edbapp.padstacks.definitions["myVia"].hole_range == "through" self.edbapp.padstacks.create(padstackname="myVia_bullet", antipad_shape="Bullet") assert "myVia_bullet" in list(self.edbapp.padstacks.definitions.keys()) - self.edbapp.add_design_variable("via_x", 5e-3) self.edbapp["via_y"] = "1mm" assert self.edbapp["via_y"].value == 1e-3 assert self.edbapp["via_y"].value_string == "1mm" - assert self.edbapp.padstacks.place(["via_x", "via_x+via_y"], "myVia", via_name="via_test1") assert self.edbapp.padstacks.place(["via_x", "via_x+via_y*2"], "myVia_bullet") self.edbapp.padstacks["via_test1"].net_name = "GND" @@ -2921,9 +2919,8 @@ def test_147_find_dc_shorts(self): edbapp.layout_validation.illegal_rlc_values(True) # assert len(dc_shorts) == 20 - assert ["LVDS_CH09_N", "GND"] in dc_shorts - assert ["LVDS_CH09_N", "DDR4_DM3"] in dc_shorts - assert ["DDR4_DM3", "LVDS_CH07_N"] in dc_shorts + assert ["SFPA_Tx_Fault", "PCIe_Gen4_CLKREQ_L"] in dc_shorts + assert ["VDD_DDR", "GND"] in dc_shorts assert len(edbapp.nets["DDR4_DM3"].find_dc_short()) > 0 edbapp.nets["DDR4_DM3"].find_dc_short(True) assert len(edbapp.nets["DDR4_DM3"].find_dc_short()) == 0 diff --git a/_unittest/test_12_PostProcessing.py b/_unittest/test_12_PostProcessing.py index 5b5db41e60a..1457c97ba9b 100644 --- a/_unittest/test_12_PostProcessing.py +++ b/_unittest/test_12_PostProcessing.py @@ -209,14 +209,9 @@ def test_09_manipulate_report_D(self, field_test): def test_09_manipulate_report_E(self, field_test): field_test.modeler.create_polyline([[0, 0, 0], [0, 5, 30]], name="Poly1", non_model=True) variations2 = field_test.available_variations.nominal_w_values_dict - variations2["Theta"] = ["All"] - variations2["Phi"] = ["All"] - variations2["Freq"] = ["30GHz"] - variations2["Distance"] = ["All"] - assert field_test.post.create_report( + + assert field_test.setups[0].create_report( "Mag_E", - field_test.nominal_adaptive, - variations=variations2, primary_sweep_variable="Distance", context="Poly1", report_category="Fields", @@ -226,11 +221,10 @@ def test_09_manipulate_report_E(self, field_test): new_report.polyline = "Poly1" assert new_report.create() new_report = field_test.post.reports_by_category.modal_solution("S(1,1)") - new_report.plot_type = "Smith Chart" + new_report.report_type = "Smith Chart" assert new_report.create() - data = field_test.post.get_solution_data( + data = field_test.setups[0].get_solution_data( "Mag_E", - field_test.nominal_adaptive, variations=variations2, primary_sweep_variable="Theta", context="Poly1", @@ -269,7 +263,7 @@ def test_17_circuit(self, circuit_test): circuit_test.analyze_setup("LNA") circuit_test.analyze_setup("Transient") assert circuit_test.setups[0].is_solved - assert circuit_test.post.create_report(["dB(S(Port1, Port1))", "dB(S(Port1, Port2))"], "LNA") + assert circuit_test.setups[0].create_report(["dB(S(Port1, Port1))", "dB(S(Port1, Port2))"]) new_report = circuit_test.post.reports_by_category.standard( ["dB(S(Port1, Port1))", "dB(S(Port1, Port2))"], "LNA" ) @@ -382,7 +376,7 @@ def test_55_time_plot(self, sbr_test): assert sbr_test.setups[0].is_solved solution_data = sbr_test.post.get_solution_data( expressions=["NearEX", "NearEY", "NearEZ"], - variations={"_u": ["All"], "_v": ["All"], "Freq": ["All"]}, + # variations={"_u": ["All"], "_v": ["All"], "Freq": ["All"]}, context="Near_Field", report_category="Near Fields", ) diff --git a/_unittest/test_20_HFSS.py b/_unittest/test_20_HFSS.py index 72f828818a7..4ded45f5f93 100644 --- a/_unittest/test_20_HFSS.py +++ b/_unittest/test_20_HFSS.py @@ -1421,9 +1421,11 @@ def test_58_create_near_field_line(self): def test_59_test_nastran(self): self.aedtapp.insert_design("Nas_teest") example_project = os.path.join(local_path, "../_unittest/example_models", test_subfolder, "test_cad.nas") + example_project2 = os.path.join(local_path, "../_unittest/example_models", test_subfolder, "test_cad_2.nas") cads = self.aedtapp.modeler.import_nastran(example_project) assert len(cads) > 0 + assert self.aedtapp.modeler.import_nastran(example_project2) def test_60_set_variable(self): self.aedtapp.variable_manager.set_variable("var_test", expression="123") @@ -1481,8 +1483,13 @@ def test_63_set_phase_center_per_port(self): name="Wave2", renormalize=False, ) - assert self.aedtapp.set_phase_center_per_port() - assert self.aedtapp.set_phase_center_per_port(["Global", "Global"]) + if self.aedtapp.desktop_class.is_grpc_api: + assert self.aedtapp.set_phase_center_per_port() + assert self.aedtapp.set_phase_center_per_port(["Global", "Global"]) + else: + assert not self.aedtapp.set_phase_center_per_port() + assert not self.aedtapp.set_phase_center_per_port(["Global", "Global"]) + assert not self.aedtapp.set_phase_center_per_port(["Global"]) assert not self.aedtapp.set_phase_center_per_port("Global") diff --git a/_unittest/test_98_Icepak.py b/_unittest/test_98_Icepak.py index b9e5ab38d95..07dc3477583 100644 --- a/_unittest/test_98_Icepak.py +++ b/_unittest/test_98_Icepak.py @@ -200,7 +200,7 @@ def test_12a_AssignMeshOperation(self): @pytest.mark.skipif(config["use_grpc"], reason="GRPC usage leads to SystemExit.") def test_12b_failing_AssignMeshOperation(self): - assert not self.aedtapp.mesh.assign_mesh_region("N0C0MP", 1, is_submodel=True) + assert self.aedtapp.mesh.assign_mesh_region("N0C0MP", 1, is_submodel=True) test = self.aedtapp.mesh.assign_mesh_region(["USB_ID"], 1) b = self.aedtapp.modeler.create_box([0, 0, 0], [1, 1, 1]) b.model = False diff --git a/pyaedt/application/Analysis.py b/pyaedt/application/Analysis.py index 7c490d965fa..07e8cdebe20 100644 --- a/pyaedt/application/Analysis.py +++ b/pyaedt/application/Analysis.py @@ -1195,7 +1195,7 @@ def _create_setup(self, setupname="MySetupAuto", setuptype=None, props=None): setup = SetupHFSSAuto(self, setuptype, name) elif setuptype == 4: setup = SetupSBR(self, setuptype, name) - elif setuptype in [5, 6, 7, 8, 9, 10]: + elif setuptype in [5, 6, 7, 8, 9, 10, 56]: setup = SetupMaxwell(self, setuptype, name) elif setuptype in [14]: setup = SetupQ3D(self, setuptype, name) diff --git a/pyaedt/application/design_solutions.py b/pyaedt/application/design_solutions.py index 7d6207c5d73..e497c10fb1a 100644 --- a/pyaedt/application/design_solutions.py +++ b/pyaedt/application/design_solutions.py @@ -110,7 +110,7 @@ "name": None, "options": None, "report_type": None, - "default_setup": None, + "default_setup": 8, "default_adaptive": "LastAdaptive", "intrinsics": ["Freq", "Phase"], }, @@ -118,7 +118,7 @@ "name": None, "options": None, "report_type": None, - "default_setup": None, + "default_setup": 8, "default_adaptive": "LastAdaptive", }, "ElectroDCConduction": { @@ -138,13 +138,21 @@ }, }, "Twin Builder": { - "TR": {"name": None, "options": None, "report_type": "Standard", "default_setup": 35, "default_adaptive": None}, + "TR": { + "name": None, + "options": None, + "report_type": "Standard", + "default_setup": 35, + "default_adaptive": None, + "intrinsics": ["Time"], + }, "AC": { "name": None, "options": None, "report_type": "Standard", "default_setup": None, "default_adaptive": None, + "intrinsics": ["Freq"], }, "DC": { "name": None, @@ -161,6 +169,7 @@ "report_type": "Standard", "default_setup": 15, "default_adaptive": None, + "intrinsics": ["Freq"], }, "NexximDC": { "name": None, @@ -175,6 +184,7 @@ "report_type": "Standard", "default_setup": 17, "default_adaptive": None, + "intrinsics": ["Time"], }, "NexximVerifEye": { "name": None, @@ -182,6 +192,7 @@ "report_type": "Standard", "default_setup": 19, "default_adaptive": None, + "intrinsics": ["Time"], }, "NexximQuickEye": { "name": None, @@ -189,6 +200,7 @@ "report_type": "Standard", "default_setup": 18, "default_adaptive": None, + "intrinsics": ["Time"], }, "NexximAMI": { "name": None, @@ -196,6 +208,7 @@ "report_type": "Standard", "default_setup": 20, "default_adaptive": None, + "intrinsics": ["Time"], }, "NexximOscillatorRSF": { "name": None, @@ -203,6 +216,7 @@ "report_type": "Standard", "default_setup": 21, "default_adaptive": None, + "intrinsics": ["Freq"], }, "NexximOscillator1T": { "name": None, @@ -210,6 +224,7 @@ "report_type": "Standard", "default_setup": 22, "default_adaptive": None, + "intrinsics": ["Freq"], }, "NexximOscillatorNT": { "name": None, @@ -217,6 +232,7 @@ "report_type": "Standard", "default_setup": 23, "default_adaptive": None, + "intrinsics": ["Freq"], }, "NexximHarmonicBalance1T": { "name": None, @@ -224,6 +240,7 @@ "report_type": "Standard", "default_setup": 24, "default_adaptive": None, + "intrinsics": ["Freq"], }, "NexximHarmonicBalanceNT": { "name": None, @@ -231,6 +248,7 @@ "report_type": "Standard", "default_setup": 25, "default_adaptive": None, + "intrinsics": ["Freq"], }, "NexximSystem": { "name": None, @@ -238,6 +256,7 @@ "report_type": "Standard", "default_setup": 26, "default_adaptive": None, + "intrinsics": ["Time"], }, "NexximTVNoise": { "name": None, @@ -245,6 +264,7 @@ "report_type": "Standard", "default_setup": 27, "default_adaptive": None, + "intrinsics": ["Freq"], }, "HSPICE": { "name": None, @@ -252,8 +272,16 @@ "report_type": "Standard", "default_setup": 28, "default_adaptive": None, + "intrinsics": ["Time"], + }, + "TR": { + "name": None, + "options": None, + "report_type": "Standard", + "default_setup": 17, + "default_adaptive": None, + "intrinsics": ["Time"], }, - "TR": {"name": None, "options": None, "report_type": "Standard", "default_setup": 17, "default_adaptive": None}, }, "2D Extractor": { "Open": { @@ -353,6 +381,7 @@ "report_type": "Modal Solution Data", "default_setup": 4, "default_adaptive": "Sweep", + "intrinsics": ["Freq", "Phase"], }, }, "Icepak": { @@ -439,6 +468,7 @@ "report_type": "Standard", "default_setup": 41, "default_adaptive": None, + "intrinsics": ["Freq"], }, "LNA3DLayout": { "name": None, @@ -446,6 +476,7 @@ "report_type": "Standard", "default_setup": 42, "default_adaptive": None, + "intrinsics": ["Freq"], }, }, "Mechanical": { diff --git a/pyaedt/desktop.py b/pyaedt/desktop.py index 65a79301e7d..b33e7417acd 100644 --- a/pyaedt/desktop.py +++ b/pyaedt/desktop.py @@ -833,6 +833,8 @@ def _init_dotnet( ): # pragma: no cover import pythoncom + pythoncom.CoInitialize() + if is_linux: raise Exception( "PyAEDT supports COM initialization in Windows only. To use in Linux, upgrade to AEDT 2022 R2 or later." diff --git a/pyaedt/edb.py b/pyaedt/edb.py index 3b530bbf89b..b278abe3658 100644 --- a/pyaedt/edb.py +++ b/pyaedt/edb.py @@ -1412,6 +1412,7 @@ def _create_extent( smart_cut=False, reference_list=[], include_pingroups=True, + pins_to_preserve=None, ): if extent_type in ["Conforming", self.edb_api.geometry.extent_type.Conforming, 1]: if use_pyaedt_extent: @@ -1423,7 +1424,7 @@ def _create_extent( expansion_size, smart_cut, reference_list, - include_pingroups, + pins_to_preserve, ) else: _poly = self.layout.expanded_extent( @@ -1448,7 +1449,7 @@ def _create_extent( expansion_size, smart_cut, reference_list, - include_pingroups, + pins_to_preserve, ) else: _poly = self.layout.expanded_extent( @@ -1473,65 +1474,73 @@ def _create_conformal( round_extension, smart_cutout=False, reference_list=[], - include_pingroups=True, + pins_to_preserve=None, ): names = [] _polys = [] for net in net_signals: names.append(net.GetName()) + if pins_to_preserve: + insts = self.padstacks.instances + for i in pins_to_preserve: + p = insts[i].position + pos_1 = [i - expansion_size for i in p] + pos_2 = [i + expansion_size for i in p] + plane = self.modeler.Shape("rectangle", pointA=pos_1, pointB=pos_2) + rectangle_data = self.modeler.shape_to_polygon_data(plane) + _polys.append(rectangle_data) + for prim in self.modeler.primitives: if prim is not None and prim.net_name in names: - obj_data = prim.primitive_object.GetPolygonData().Expand( - expansion_size, tolerance, round_corner, round_extension - ) - if obj_data: - _polys.extend(list(obj_data)) + _polys.append(prim.primitive_object.GetPolygonData()) if smart_cutout: - _polys.extend(self._smart_cut(net_signals, reference_list, include_pingroups)) - _poly_unite = self.edb_api.geometry.polygon_data.unite(_polys) + objs_data = self._smart_cut(reference_list, expansion_size) + _polys.extend(objs_data) + k = 0 + delta = expansion_size / 5 + while k < 10: + unite_polys = [] + for i in _polys: + obj_data = i.Expand(expansion_size, tolerance, round_corner, round_extension) + if obj_data: + unite_polys.extend(list(obj_data)) + _poly_unite = self.edb_api.geometry.polygon_data.unite(unite_polys) + if len(_poly_unite) == 1: + self.logger.info("Correctly computed Extension at first iteration.") + return _poly_unite[0] + k += 1 + expansion_size += delta if len(_poly_unite) == 1: + self.logger.info("Correctly computed Extension in {} iterations.".format(k)) return _poly_unite[0] else: + self.logger.info("Failed to Correctly computed Extension.") areas = [i.Area() for i in _poly_unite] return _poly_unite[areas.index(max(areas))] @pyaedt_function_handler() - def _smart_cut(self, net_signals, reference_list=[], include_pingroups=True): + def _smart_cut(self, reference_list=[], expansion_size=1e-12): + from pyaedt.generic.clr_module import Tuple + _polys = [] terms = [term for term in self.layout.terminals if int(term.GetBoundaryType()) in [0, 3, 4, 7, 8]] locations = [] for term in terms: - if term.GetTerminalType().ToString() == "PadstackInstanceTerminal": - if term.GetParameters()[1].GetNet().GetName() in reference_list: - locations.append(self.padstacks.instances[term.GetParameters()[1].GetId()].position) - elif term.GetTerminalType().ToString() == "PointTerminal" and term.GetNet().GetName() in reference_list: + if term.GetTerminalType().ToString() == "PointTerminal" and term.GetNet().GetName() in reference_list: pd = term.GetParameters()[1] locations.append([pd.X.ToDouble(), pd.Y.ToDouble()]) - if include_pingroups: - for reference in reference_list: - for pin in self.nets.nets[reference].padstack_instances: - if pin.pingroups: - locations.append(pin.position) for point in locations: pointA = self.edb_api.geometry.point_data( - self.edb_value(point[0] - 1e-12), self.edb_value(point[1] - 1e-12) + self.edb_value(point[0] - expansion_size), self.edb_value(point[1] - expansion_size) ) pointB = self.edb_api.geometry.point_data( - self.edb_value(point[0] + 1e-12), self.edb_value(point[1] + 1e-12) + self.edb_value(point[0] + expansion_size), self.edb_value(point[1] + expansion_size) ) points = Tuple[self.edb_api.geometry.geometry.PointData, self.edb_api.geometry.geometry.PointData]( pointA, pointB ) _polys.append(self.edb_api.geometry.polygon_data.create_from_bbox(points)) - for cname, c in self.components.instances.items(): - if ( - set(net_signals).intersection(c.nets) - and c.is_enabled - and c.model_type in ["SParameterModel", "SpiceModel", "NetlistModel"] - ): - for pin in c.pins: - locations.append(pin.position) return _polys @pyaedt_function_handler() @@ -1544,17 +1553,28 @@ def _create_convex_hull( round_extension, smart_cut=False, reference_list=[], - include_pingroups=True, + pins_to_preserve=None, ): names = [] _polys = [] for net in net_signals: names.append(net.GetName()) + if pins_to_preserve: + insts = self.padstacks.instances + for i in pins_to_preserve: + p = insts[i].position + pos_1 = [i - 1e-12 for i in p] + pos_2 = [i + 1e-12 for i in p] + plane = self.modeler.Shape("rectangle", pointA=pos_1, pointB=pos_2) + rectangle_data = self.modeler.shape_to_polygon_data(plane) + _polys.append(rectangle_data) for prim in self.modeler.primitives: if prim is not None and prim.net_name in names: _polys.append(prim.primitive_object.GetPolygonData()) if smart_cut: - _polys.extend(self._smart_cut(net_signals, reference_list, include_pingroups)) + objs_data = self._smart_cut(reference_list, expansion_size) + _polys.extend(objs_data) + _poly = self.edb_api.geometry.polygon_data.get_convex_hull_of_polygons(convert_py_list_to_net_list(_polys)) _poly = _poly.Expand(expansion_size, tolerance, round_corner, round_extension)[0] return _poly @@ -2027,6 +2047,17 @@ def _create_cutout_multithread( ): pins_to_preserve.extend([i.id for i in el.pins.values()]) nets_to_preserve.extend(el.nets) + if include_pingroups: + for reference in reference_list: + for pin in self.nets.nets[reference].padstack_instances: + if pin.pingroups: + pins_to_preserve.append(pin.id) + if check_terminals: + terms = [term for term in self.layout.terminals if int(term.GetBoundaryType()) in [0, 3, 4, 7, 8]] + for term in terms: + if term.GetTerminalType().ToString() == "PadstackInstanceTerminal": + if term.GetParameters()[1].GetNet().GetName() in reference_list: + pins_to_preserve.append(term.GetParameters()[1].GetId()) for i in self.nets.nets.values(): name = i.name @@ -2077,6 +2108,7 @@ def _create_cutout_multithread( smart_cut=check_terminals, reference_list=reference_list, include_pingroups=include_pingroups, + pins_to_preserve=pins_to_preserve, ) if extent_type in ["Conforming", self.edb_api.geometry.extent_type.Conforming, 1] and extent_defeature > 0: _poly = _poly.Defeature(extent_defeature) diff --git a/pyaedt/edb_core/components.py b/pyaedt/edb_core/components.py index 77d82118365..2361dbb670d 100644 --- a/pyaedt/edb_core/components.py +++ b/pyaedt/edb_core/components.py @@ -1700,6 +1700,7 @@ def create_pingroup_from_pins(self, pins, group_name=None): def delete_single_pin_rlc(self, deactivate_only=False): # type: (bool) -> list """Delete all RLC components with a single pin. + Single pin component model type will be reverted to ``"RLC"``. Parameters ---------- @@ -1727,6 +1728,7 @@ def delete_single_pin_rlc(self, deactivate_only=False): if val.numpins < 2 and val.type in ["Resistor", "Capacitor", "Inductor"]: if deactivate_only: val.is_enabled = False + val.model_type = "RLC" else: val.edbcomponent.Delete() deleted_comps.append(comp) diff --git a/pyaedt/edb_core/edb_data/obj_base.py b/pyaedt/edb_core/edb_data/obj_base.py index 45945326703..9f3ac6cb6d6 100644 --- a/pyaedt/edb_core/edb_data/obj_base.py +++ b/pyaedt/edb_core/edb_data/obj_base.py @@ -13,4 +13,7 @@ def is_null(self): @property def type(self): """Get type.""" - return self._edb_object.GetType() + try: + return self._edb_object.GetType() + except AttributeError: # pragma: no cover + return None diff --git a/pyaedt/edb_core/edb_data/padstacks_data.py b/pyaedt/edb_core/edb_data/padstacks_data.py index 4abd7768f42..9bc4897a57c 100644 --- a/pyaedt/edb_core/edb_data/padstacks_data.py +++ b/pyaedt/edb_core/edb_data/padstacks_data.py @@ -1669,7 +1669,10 @@ def lower_elevation(self): float Lower elavation of the placement layer. """ - return self._edb_padstackinstance.GetGroup().GetPlacementLayer().Clone().GetLowerElevation() + try: + return self._edb_padstackinstance.GetGroup().GetPlacementLayer().Clone().GetLowerElevation() + except AttributeError: # pragma: no cover + return None @property def upper_elevation(self): @@ -1680,7 +1683,10 @@ def upper_elevation(self): float Upper elevation of the placement layer. """ - return self._edb_padstackinstance.GetGroup().GetPlacementLayer().Clone().GetUpperElevation() + try: + return self._edb_padstackinstance.GetGroup().GetPlacementLayer().Clone().GetUpperElevation() + except AttributeError: # pragma: no cover + return None @property def top_bottom_association(self): diff --git a/pyaedt/edb_core/edb_data/primitives_data.py b/pyaedt/edb_core/edb_data/primitives_data.py index 8896d96cb99..259f8f31b33 100644 --- a/pyaedt/edb_core/edb_data/primitives_data.py +++ b/pyaedt/edb_core/edb_data/primitives_data.py @@ -1,5 +1,6 @@ import math +from pyaedt.edb_core.dotnet.database import NetDotNet from pyaedt.edb_core.dotnet.primitive import BondwireDotNet from pyaedt.edb_core.dotnet.primitive import CircleDotNet from pyaedt.edb_core.dotnet.primitive import PathDotNet @@ -82,7 +83,10 @@ def type(self): ------- str """ - return self._edb_object.GetPrimitiveType().ToString() + try: + return self._edb_object.GetPrimitiveType().ToString() + except AttributeError: # pragma: no cover + return "" @property def net_name(self): @@ -101,14 +105,20 @@ def net_name(self, name): self.primitive_object.SetNet(net) else: try: - self.net = name - except: + if isinstance(name, str): + self.net = name + elif isinstance(name, NetDotNet): + self.net = name.name + except: # pragma: no cover self._app.logger.error("Failed to set net name.") @property def layer(self): """Get the primitive edb layer object.""" - return self.primitive_object.GetLayer() + try: + return self.primitive_object.GetLayer() + except AttributeError: # pragma: no cover + return None @property def layer_name(self): @@ -118,7 +128,10 @@ def layer_name(self): ------- str """ - return self.layer.GetName() + try: + return self.layer.GetName() + except AttributeError: # pragma: no cover + return None @layer_name.setter def layer_name(self, val): @@ -144,7 +157,10 @@ def is_void(self): ------- bool """ - return self._edb_object.IsVoid() + try: + return self._edb_object.IsVoid() + except AttributeError: # pragma: no cover + return None def get_connected_objects(self): """Get connected objects. diff --git a/pyaedt/edb_core/layout_validation.py b/pyaedt/edb_core/layout_validation.py index 1f5446ab8d8..ccbaa8f0033 100644 --- a/pyaedt/edb_core/layout_validation.py +++ b/pyaedt/edb_core/layout_validation.py @@ -55,7 +55,10 @@ def dc_shorts(self, net_list=None, fix=False): else: _padstacks_list[n_name] = [pad] dc_shorts = [] + all_shorted_nets = [] for net in net_list: + if net in all_shorted_nets: + continue objs = [] for i in _objects_list.get(net, []): objs.append(i) @@ -68,11 +71,13 @@ def dc_shorts(self, net_list=None, fix=False): connected_objs = objs[0].get_connected_objects() connected_objs.append(objs[0]) net_dc_shorts = [obj for obj in connected_objs] + all_shorted_nets.append(net) if net_dc_shorts: dc_nets = list(set([obj.net.name for obj in net_dc_shorts if not obj.net.name == net])) for dc in dc_nets: if dc: dc_shorts.append([net, dc]) + all_shorted_nets.append(dc) if fix: temp = [] for i in net_dc_shorts: @@ -95,7 +100,12 @@ def dc_shorts(self, net_list=None, fix=False): @pyaedt_function_handler() def disjoint_nets( - self, net_list=None, keep_only_main_net=False, clean_disjoints_less_than=0.0, order_by_area=False + self, + net_list=None, + keep_only_main_net=False, + clean_disjoints_less_than=0.0, + order_by_area=False, + keep_disjoint_pins=False, ): """Find and fix disjoint nets from a given netlist. @@ -110,6 +120,8 @@ def disjoint_nets( order_by_area : bool, optional Whether if the naming order has to be by number of objects (fastest) or area (slowest but more accurate). Default is ``False``. + keep_disjoint_pins : bool, optional + Whether if delete disjoints pins not connected to any other primitive or not. Default is ``False``. Returns ------- List @@ -190,14 +202,24 @@ def area_calc(elem): except KeyError: pass elif len(disjoints) == 1 and ( - isinstance(obj_dict[disjoints[0]], EDBPadstackInstance) - or clean_disjoints_less_than + clean_disjoints_less_than + and "area" in dir(obj_dict[disjoints[0]]) and obj_dict[disjoints[0]].area() < clean_disjoints_less_than ): try: obj_dict[disjoints[0]].delete() except KeyError: pass + elif ( + len(disjoints) == 1 + and not keep_disjoint_pins + and isinstance(obj_dict[disjoints[0]], EDBPadstackInstance) + ): + try: + obj_dict[disjoints[0]].delete() + except KeyError: + pass + else: new_net_name = generate_unique_name(net, n=6) net_obj = self._pedb.nets.find_or_create_net(new_net_name) diff --git a/pyaedt/generic/configurations.py b/pyaedt/generic/configurations.py index 91403b5f4c3..1b0163bade4 100644 --- a/pyaedt/generic/configurations.py +++ b/pyaedt/generic/configurations.py @@ -1143,7 +1143,7 @@ def _export_general(self, dict_out): dict_out["general"]["date"] = datetime.now().strftime("%d/%m/%Y %H:%M:%S") dict_out["general"]["object_mapping"] = {} dict_out["general"]["output_variables"] = {} - if self._app.output_variables: + if list(self._app.output_variables): oo_out = os.path.join(tempfile.gettempdir(), generate_unique_name("oo") + ".txt") self._app.ooutput_variable.ExportOutputVariables(oo_out) with open(oo_out, "r") as f: diff --git a/pyaedt/hfss.py b/pyaedt/hfss.py index 53d96d99303..9029d85043d 100644 --- a/pyaedt/hfss.py +++ b/pyaedt/hfss.py @@ -5947,7 +5947,7 @@ def set_phase_center_per_port(self, coordinate_system=None): """ if not self.desktop_class.is_grpc_api: # pragma: no cover - self.hfss.logger.warning("Set phase center is not supported by AEDT COM API. Set phase center manually") + self.logger.warning("Set phase center is not supported by AEDT COM API. Set phase center manually") return False port_names = [] diff --git a/pyaedt/icepak.py b/pyaedt/icepak.py index c20bee56ea5..ac637ba8c63 100644 --- a/pyaedt/icepak.py +++ b/pyaedt/icepak.py @@ -34,6 +34,7 @@ from pyaedt.modules.Boundary import BoundaryObject from pyaedt.modules.Boundary import NativeComponentObject from pyaedt.modules.Boundary import NetworkObject +from pyaedt.modules.Boundary import _create_boundary from pyaedt.modules.monitor_icepak import Monitor @@ -630,14 +631,7 @@ def create_conduting_plate( ) props["Shell Conduction"] = shell_conduction bound = BoundaryObject(self, bc_name, props, "Conducting Plate") - try: - if bound.create(): - self._boundaries[bound.name] = bound - return bound - else: # pragma : no cover - raise SystemExit - except (GrpcApiError, SystemExit): - return None + return _create_boundary(bound) @pyaedt_function_handler() def create_source_power( @@ -727,14 +721,7 @@ def create_source_power( props["Temperature"] = temperature props["Radiation"] = OrderedDict({"Radiate": radiate}) bound = BoundaryObject(self, source_name, props, "SourceIcepak") - try: - if bound.create(): - self._boundaries[bound.name] = bound - return bound - else: # pragma : no cover - raise SystemExit - except (GrpcApiError, SystemExit): - return None + return _create_boundary(bound) @pyaedt_function_handler() def create_network_block( @@ -3486,14 +3473,7 @@ def assign_stationary_wall( props["External Radiation Reference Temperature"] = ext_surf_rad_ref_temp props["External Radiation View Factor"] = ext_surf_rad_view_factor bound = BoundaryObject(self, name, props, "Stationary Wall") - try: - if bound.create(): - self._boundaries[bound.name] = bound - return bound - else: # pragma : no cover - raise SystemExit - except (GrpcApiError, SystemExit): - return None + return _create_boundary(bound) @pyaedt_function_handler() def assign_stationary_wall_with_heat_flux( @@ -4200,14 +4180,7 @@ def assign_solid_block( boundary_name = generate_unique_name("Block") bound = BoundaryObject(self, boundary_name, props, "Block") - try: - if bound.create(): - self._boundaries[bound.name] = bound - return bound - else: # pragma : no cover - raise SystemExit - except (GrpcApiError, SystemExit): - return None + return _create_boundary(bound) @pyaedt_function_handler def assign_hollow_block( @@ -4335,14 +4308,7 @@ def assign_hollow_block( boundary_name = generate_unique_name("Block") bound = BoundaryObject(self, boundary_name, props, "Block") - try: - if bound.create(): - self._boundaries[bound.name] = bound - return bound - else: # pragma : no cover - raise SystemExit - except (GrpcApiError, SystemExit): - return None + return _create_boundary(bound) @pyaedt_function_handler() def get_fans_operating_point(self, export_file=None, setup_name=None, timestep=None, design_variation=None): @@ -4827,11 +4793,7 @@ def assign_mass_flow_free_opening( ) @pyaedt_function_handler() - def assign_symmetry_wall( - self, - geometry, - boundary_name=None, - ): + def assign_symmetry_wall(self, geometry, boundary_name=None): """Assign symmetry wall boundary condition. Parameters @@ -4864,14 +4826,7 @@ def assign_symmetry_wall( props["Objects"] = geometry bound = BoundaryObject(self, boundary_name, props, "Symmetry Wall") - try: - if bound.create(): - self._boundaries[bound.name] = bound - return bound - else: # pragma : no cover - raise SystemExit - except (GrpcApiError, SystemExit): - return None + return _create_boundary(bound) @pyaedt_function_handler() def assign_adiabatic_plate(self, assignment, high_radiation_dict=None, low_radiation_dict=None, boundary_name=None): @@ -5044,11 +4999,11 @@ def assign_recirculation_opening(self, face_list, extract_face, thermal_specific if not len(face_list) == 2: self.logger.error("Recirculation boundary condition must be assigned to two faces.") return False - if conductance_external_temperature is not None and thermal_specification is not "Conductance": + if conductance_external_temperature is not None and thermal_specification != "Conductance": self.logger.warning( '``conductance_external_temperature`` does not have any effect unless the ``thermal_specification`` ' 'is ``"Conductance"``.') - if conductance_external_temperature is not None and thermal_specification is not "Conductance": + if conductance_external_temperature is not None and thermal_specification != "Conductance": self.logger.warning( '``conductance_external_temperature`` must be specified when ``thermal_specification`` ' 'is ``"Conductance"``. Setting ``conductance_external_temperature`` to ``"AmbientTemp"``.') @@ -5122,14 +5077,7 @@ def assign_recirculation_opening(self, face_list, extract_face, thermal_specific boundary_name = generate_unique_name("Recirculating") bound = BoundaryObject(self, boundary_name, props, "Recirculating") - try: - if bound.create(): - self._boundaries[bound.name] = bound - return bound - else: # pragma: no cover - raise SystemExit - except (GrpcApiError, SystemExit): # pragma : no cover - return None + return _create_boundary(bound) @pyaedt_function_handler() def assign_blower_type1(self, faces, inlet_face, fan_curve_pressure, fan_curve_flow, blower_power="0W", blade_rpm=0, @@ -5281,11 +5229,4 @@ def _assign_blower(self, props, faces, inlet_face, fan_curve_flow_unit, fan_curv if not boundary_name: boundary_name = generate_unique_name("Blower") bound = BoundaryObject(self, boundary_name, props, "Blower") - try: - if bound.create(): - self._boundaries[bound.name] = bound - return bound - else: # pragma : no cover - raise SystemExit - except (GrpcApiError, SystemExit): # pragma: no cover - return None + return _create_boundary(bound) diff --git a/pyaedt/modeler/modeler3d.py b/pyaedt/modeler/modeler3d.py index 7ebb7fccb7a..b2f2fdb9b32 100644 --- a/pyaedt/modeler/modeler3d.py +++ b/pyaedt/modeler/modeler3d.py @@ -889,130 +889,112 @@ def import_nastran(self, file_path, import_lines=True, lines_thickness=0, import lines = f.read().splitlines() id = 0 for line in lines: - line_split = [line[i : i + 8] for i in range(0, len(line), 8)] - if len(line_split) < 5: + line_type = line[:8].strip() + if line.startswith("$") or line.startswith("*"): continue - if line_split[0].startswith("GRID"): - try: - import re - - out = re.findall("^.{24}(.{8})(.{8})(.{8})", line)[0] - n1 = out[0].replace(".-", ".e-").strip() - n2 = out[1].replace(".-", ".e-").strip() - n3 = out[2].replace(".-", ".e-").strip() - - if "-" in n1[1:]: - n1 = n1[0] + n1[1:].replace("-", "e-") - n1 = float(n1) - if "-" in n2[1:]: - n2 = n2[0] + n2[1:].replace("-", "e-") - n2 = float(n2) - if "-" in n3[1:]: - n3 = n3[0] + n3[1:].replace("-", "e-") - n3 = float(n3) - - nas_to_dict["Points"][int(line_split[1])] = [n1, n2, n3] - nas_to_dict["PointsId"][int(line_split[1])] = id + elif line_type in ["GRID", "CTRIA3"]: + grid_id = int(line[8:16]) + if line_type == "CTRIA3": + tria_id = int(line[16:24]) + n1 = line[24:32].strip() + if "-" in n1[1:]: + n1 = n1[0] + n1[1:].replace("-", "e-") + n2 = line[32:40].strip() + if "-" in n2[1:]: + n2 = n2[0] + n2[1:].replace("-", "e-") + n3 = line[40:48].strip() + if "-" in n3[1:]: + n3 = n3[0] + n3[1:].replace("-", "e-") + if line_type == "GRID": + nas_to_dict["Points"][grid_id] = [float(n1), float(n2), float(n3)] + nas_to_dict["PointsId"][grid_id] = grid_id id += 1 - except: - pass - elif line_split[0].startswith("CTRIA3"): - if int(line_split[2]) in nas_to_dict["Triangles"]: - nas_to_dict["Triangles"][int(line_split[2])].append( - [ - int(line_split[3]), - int(line_split[4]), - int(line_split[5]), - ] - ) - else: - nas_to_dict["Triangles"][int(line_split[2])] = [ - [ - int(line_split[3]), - int(line_split[4]), - int(line_split[5]), - ] - ] - elif line_split[0].startswith("CPENTA"): - if int(line_split[2]) in nas_to_dict["Solids"]: - nas_to_dict["Solids"][int(line_split[2])].append( - [ - line_split[0].strip(), - int(line_split[3]), - int(line_split[4]), - int(line_split[5]), - int(line_split[6]), - int(line_split[7]), - int(line_split[8]), - ] - ) else: - nas_to_dict["Solids"][int(line_split[2])] = [ - [ - line_split[0].strip(), - int(line_split[3]), - int(line_split[4]), - int(line_split[5]), - int(line_split[6]), - int(line_split[7]), - int(line_split[8]), - ] - ] - elif line_split[0].startswith("CHEXA"): - if int(line_split[2]) in nas_to_dict["Solids"]: - nas_to_dict["Solids"][int(line_split[2])].append( - [ - line_split[0].strip(), - int(line_split[3]), - int(line_split[4]), - int(line_split[5]), - int(line_split[6]), - int(line_split[7]), - int(line_split[8]), - int(line_split[9]), - int(line_split[10]), + if tria_id in nas_to_dict["Triangles"]: + nas_to_dict["Triangles"][tria_id].append( + [ + int(n1), + int(n2), + int(n3), + ] + ) + else: + nas_to_dict["Triangles"][tria_id] = [ + [ + int(n1), + int(n2), + int(n3), + ] ] - ) + elif line_type in ["GRID*", "CTRIA3*"]: + grid_id = int(line[8:24]) + if line_type == "CTRIA3*": + tria_id = int(line[24:40]) + n1 = line[40:56].strip() + if "-" in n1[1:]: + n1 = n1[0] + n1[1:].replace("-", "e-") + n2 = line[56:72].strip() + if "-" in n2[1:]: + n2 = n2[0] + n2[1:].replace("-", "e-") + + n3 = line[72:88].strip() + if not n3 or n3 == "*": + n3 = lines[lines.index(line) + 1][8:24].strip() + if "-" in n3[1:]: + n3 = n3[0] + n3[1:].replace("-", "e-") + if line_type == "GRID*": + nas_to_dict["Points"][grid_id] = [float(n1), float(n2), float(n3)] + nas_to_dict["PointsId"][grid_id] = id + id += 1 else: - nas_to_dict["Solids"][int(line_split[2])] = [ - [ - line_split[0].strip(), - int(line_split[3]), - int(line_split[4]), - int(line_split[5]), - int(line_split[6]), - int(line_split[7]), - int(line_split[8]), - int(line_split[9]), - int(line_split[10]), - ] - ] - elif line_split[0].startswith("CTETRA"): - if int(line_split[2]) in nas_to_dict["Solids"]: - nas_to_dict["Solids"][int(line_split[2])].append( - [ - line_split[0].strip(), - int(line_split[3]), - int(line_split[4]), - int(line_split[5]), - int(line_split[6]), + if tria_id in nas_to_dict["Triangles"]: + nas_to_dict["Triangles"][tria_id].append( + [ + int(n1), + int(n2), + int(n3), + ] + ) + else: + nas_to_dict["Triangles"][tria_id] = [ + [ + int(n1), + int(n2), + int(n3), + ] ] - ) + elif line_type in ["CPENTA", "CHEXA", "CTETRA"]: + obj_id = int(line[16:24]) + n1 = int(line[24:32]) + n2 = int(line[32:40]) + n3 = int(line[40:48]) + n4 = int(line[48:56]) + obj_list = [line_type, n1, n2, n3, n4] + if line_type == "CPENTA": + n5 = int(line[56:64]) + n6 = int(line[64:72]) + obj_list.extend([n5, n6]) + + if line_type == "CHEXA": + n5 = int(line[56:64]) + n6 = int(line[64:72]) + n7 = int(lines[lines.index(line) + 1][8:16].strip()) + n8 = int(lines[lines.index(line) + 1][16:24].strip()) + + obj_list.extend([n5, n6, n7, n8]) + if obj_id in nas_to_dict["Solids"]: + nas_to_dict["Solids"][obj_id].append(obj_list) else: - nas_to_dict["Solids"][int(line_split[2])] = [ - [ - line_split[0].strip(), - int(line_split[3]), - int(line_split[4]), - int(line_split[5]), - int(line_split[6]), - ] - ] - elif line_split[0].startswith("CROD") or line_split[0].startswith("CBEAM"): - if int(line_split[2]) in nas_to_dict["Lines"]: - nas_to_dict["Lines"][int(line_split[2])].append([int(line_split[3]), int(line_split[4])]) + nas_to_dict["Solids"][obj_id] = [[i for i in obj_list]] + elif line_type in ["CROD", "CBEAM"]: + obj_id = int(line[16:24]) + n1 = int(line[24:32]) + n2 = int(line[32:40]) + if obj_id in nas_to_dict["Lines"]: + nas_to_dict["Lines"][obj_id].append([n1, n2]) else: - nas_to_dict["Lines"][int(line_split[2])] = [[int(line_split[3]), int(line_split[4])]] + nas_to_dict["Lines"][obj_id] = [[n1, n2]] + self.logger.info_timer("File loaded") objs_before = [i for i in self.object_names] if nas_to_dict["Triangles"]: diff --git a/pyaedt/modules/Boundary.py b/pyaedt/modules/Boundary.py index 92ea792ce45..8061aaa131f 100644 --- a/pyaedt/modules/Boundary.py +++ b/pyaedt/modules/Boundary.py @@ -4506,3 +4506,14 @@ def _create_node_dict(self, default_dict): node_args[k] = val return node_args + + +def _create_boundary(bound): + try: + if bound.create(): + bound._app._boundaries[bound.name] = bound + return bound + else: # pragma : no cover + raise Exception + except Exception: # pragma: no cover + return None diff --git a/pyaedt/modules/PostProcessor.py b/pyaedt/modules/PostProcessor.py index 7a79917c1ce..e8f51209f24 100644 --- a/pyaedt/modules/PostProcessor.py +++ b/pyaedt/modules/PostProcessor.py @@ -1585,6 +1585,15 @@ def create_report( ... "InputCurrent(PHA)", domain="Time", primary_sweep_variable="Time", plotname="Winding Plot 1" ... ) """ + if not setup_sweep_name: + setup_sweep_name = self._app.nominal_sweep + if not domain: + domain = "Sweep" + setup_name = setup_sweep_name.split(":")[0] + if setup_name: + for setup in self._app.setups: + if setup.name == setup_name and "Time" in setup.default_intrinsics: + domain = "Time" if domain in ["Spectral", "Spectrum"]: report_category = "Spectrum" elif not report_category and not self._app.design_solutions.report_type: @@ -1598,8 +1607,7 @@ def create_report( report_class = TEMPLATES_BY_NAME["Fields"] else: report_class = TEMPLATES_BY_NAME["Standard"] - if not setup_sweep_name: - setup_sweep_name = self._app.nominal_sweep + report = report_class(self, report_category, setup_sweep_name) if not expressions: expressions = [ @@ -1607,6 +1615,8 @@ def create_report( ] report.expressions = expressions report.domain = domain + if not variations: + variations = self._app.available_variations.nominal_w_values_dict if primary_sweep_variable: report.primary_sweep = primary_sweep_variable elif domain == "DCIR": # pragma: no cover @@ -1617,8 +1627,8 @@ def create_report( variations = {"Index": "All"} if secondary_sweep_variable: report.secondary_sweep = secondary_sweep_variable - if variations: - report.variations = variations + + report.variations = variations report.report_type = plot_type report.sub_design_id = subdesign_id report.point_number = polyline_points @@ -1669,7 +1679,7 @@ def get_solution_data( self, expressions=None, setup_sweep_name=None, - domain="Sweep", + domain=None, variations=None, primary_sweep_variable=None, report_category=None, @@ -1696,7 +1706,7 @@ def get_solution_data( Plot Domain. Options are "Sweep" for frequency domain related results and "Time" for transient related data. variations : dict, optional Dictionary of all families including the primary sweep. - The default is ``None`` which will use the nominal variations of the design. + The default is ``None`` which will use the nominal variations of the setup. primary_sweep_variable : str, optional Name of the primary sweep. The default is ``"None"`` which, depending on the context, will internally assign the primary sweep to: @@ -1784,6 +1794,15 @@ def get_solution_data( ...) """ expressions = [expressions] if isinstance(expressions, str) else expressions + if not setup_sweep_name: + setup_sweep_name = self._app.nominal_sweep + if not domain: + domain = "Sweep" + setup_name = setup_sweep_name.split(":")[0] + if setup_name: + for setup in self._app.setups: + if setup.name == setup_name and "Time" in setup.default_intrinsics: + domain = "Time" if domain in ["Spectral", "Spectrum"]: report_category = "Spectrum" if not report_category and not self._app.design_solutions.report_type: @@ -1797,8 +1816,7 @@ def get_solution_data( report_class = TEMPLATES_BY_NAME["Fields"] else: report_class = TEMPLATES_BY_NAME["Standard"] - if not setup_sweep_name: - setup_sweep_name = self._app.nominal_sweep + report = report_class(self, report_category, setup_sweep_name) if not expressions: expressions = [ @@ -1812,6 +1830,8 @@ def get_solution_data( report.primary_sweep = primary_sweep_variable if variations: report.variations = variations + else: + report.variations = self._app.available_variations.nominal_w_values_dict report.sub_design_id = subdesign_id report.point_number = polyline_points if context == "Differential Pairs": diff --git a/pyaedt/modules/SolveSetup.py b/pyaedt/modules/SolveSetup.py index 91ff27b461a..f7eae27897c 100644 --- a/pyaedt/modules/SolveSetup.py +++ b/pyaedt/modules/SolveSetup.py @@ -220,6 +220,241 @@ def name(self, name): self._setupname = name self.props["Name"] = name + @pyaedt_function_handler() + def get_solution_data( + self, + expressions=None, + domain=None, + variations=None, + primary_sweep_variable=None, + report_category=None, + context=None, + polyline_points=1001, + math_formula=None, + sweep_name=None, + ): + """Get a simulation result from a solved setup and cast it in a ``SolutionData`` object. + Data to be retrieved from Electronics Desktop are any simulation results available in that + specific simulation context. + Most of the argument have some defaults which works for most of the ``Standard`` report quantities. + + Parameters + ---------- + expressions : str or list, optional + One or more formulas to add to the report. Example is value ``"dB(S(1,1))"`` or a list of values. + Default is `None` which will return all traces. + domain : str, optional + Plot Domain. Options are "Sweep" for frequency domain related results and "Time" for transient related data. + variations : dict, optional + Dictionary of all families including the primary sweep. + The default is ``None`` which will use the nominal variations of the setup. + primary_sweep_variable : str, optional + Name of the primary sweep. The default is ``"None"`` which, depending on the context, + will internally assign the primary sweep to: + 1. ``Freq`` for frequency domain results, + 2. ``Time`` for transient results, + 3. ``Theta`` for radiation patterns, + 4. ``distance`` for field plot over a polyline. + report_category : str, optional + Category of the Report to be created. If `None` default data Report will be used. + The Report Category can be one of the types available for creating a report depend on the simulation setup. + For example for a Far Field Plot in HFSS the UI shows the report category as "Create Far Fields Report". + The report category will be in this case "Far Fields". + Depending on the setup different categories are available. + If `None` default category will be used (the first item in the Results drop down menu in AEDT). + To get the list of available categories user can use method ``available_report_types``. + context : str, dict, optional + This is the context of the report. + The default is ``None``. It can be: + 1. `None` + 2. Infinite Sphere name for Far Fields Plot. + 3. Dictionary. If dictionary is passed, key is the report property name and value is property value. + polyline_points : int, optional + Number of points on which to create the report for plots on polylines. + This parameter is valid for ``Fields`` plot only. + math_formula : str, optional + One of the available AEDT mathematical formulas to apply. For example, ``abs, dB``. + sweep_name : str, optional + Name of the sweep adaptive setup from which get solutions. Default is ``LastAdaptive``. + + Returns + ------- + :class:`pyaedt.modules.solutions.SolutionData` + Solution Data object. + + References + ---------- + + >>> oModule.GetSolutionDataPerVariation + + Examples + -------- + >>> from pyaedt import Hfss + >>> aedtapp = Hfss() + >>> aedtapp.post.create_report("dB(S(1,1))") + + >>> variations = aedtapp.available_variations.nominal_w_values_dict + >>> variations["Theta"] = ["All"] + >>> variations["Phi"] = ["All"] + >>> variations["Freq"] = ["30GHz"] + >>> data1 = aedtapp.post.get_solution_data( + ... "GainTotal", + ... aedtapp.nominal_adaptive, + ... variations=variations, + ... primary_sweep_variable="Phi", + ... secondary_sweep_variable="Theta", + ... context="3D", + ... report_category="Far Fields", + ...) + + >>> data2 =aedtapp.post.get_solution_data( + ... "S(1,1)", + ... aedtapp.nominal_sweep, + ... variations=variations, + ...) + >>> data2.plot() + + >>> from pyaedt import Maxwell2d + >>> maxwell_2d = Maxwell2d() + >>> data3 = maxwell_2d.post.get_solution_data( + ... "InputCurrent(PHA)", domain="Time", primary_sweep_variable="Time", + ... ) + >>> data3.plot("InputCurrent(PHA)") + + >>> from pyaedt import Circuit + >>> circuit = Circuit() + >>> context = {"algorithm": "FFT", "max_frequency": "100MHz", "time_stop": "2.5us", "time_start": "0ps"} + >>> spectralPlotData = circuit.post.get_solution_data( + ... expressions="V(Vprobe1)", primary_sweep_variable="Spectrum", domain="Spectral", + ... context=context + ...) + """ + if sweep_name: + setup_sweep_name = [ + i for i in self._app.existing_analysis_sweeps if self.name == i.split(" : ")[0] and sweep_name in i + ] + else: + setup_sweep_name = [i for i in self._app.existing_analysis_sweeps if self.name == i.split(" : ")[0]] + if setup_sweep_name: + return self._app.post.get_solution_data( + expressions=expressions, + domain=domain, + variations=variations, + primary_sweep_variable=primary_sweep_variable, + report_category=report_category, + context=context, + polyline_points=polyline_points, + math_formula=math_formula, + setup_sweep_name=setup_sweep_name[0], + ) + return None + + @pyaedt_function_handler() + def create_report( + self, + expressions=None, + domain="Sweep", + variations=None, + primary_sweep_variable=None, + secondary_sweep_variable=None, + report_category=None, + plot_type="Rectangular Plot", + context=None, + subdesign_id=None, + polyline_points=1001, + plotname=None, + sweep_name=None, + ): + """Create a report in AEDT. It can be a 2D plot, 3D plot, polar plots or data tables. + + Parameters + ---------- + expressions : str or list, optional + One or more formulas to add to the report. Example is value = ``"dB(S(1,1))"``. + domain : str, optional + Plot Domain. Options are "Sweep", "Time", "DCIR". + variations : dict, optional + Dictionary of all families including the primary sweep. The default is ``{"Freq": ["All"]}``. + primary_sweep_variable : str, optional + Name of the primary sweep. The default is ``"Freq"``. + secondary_sweep_variable : str, optional + Name of the secondary sweep variable in 3D Plots. + report_category : str, optional + Category of the Report to be created. If `None` default data Report will be used. + The Report Category can be one of the types available for creating a report depend on the simulation setup. + For example for a Far Field Plot in HFSS the UI shows the report category as "Create Far Fields Report". + The report category will be in this case "Far Fields". + Depending on the setup different categories are available. + If `None` default category will be used (the first item in the Results drop down menu in AEDT). + plot_type : str, optional + The format of Data Visualization. Default is ``Rectangular Plot``. + context : str, optional + The default is ``None``. It can be `None`, `"Differential Pairs"`,`"RL"`, + `"Sources"`, `"Vias"`,`"Bondwires"`, `"Probes"` for Hfss3dLayout or + Reduce Matrix Name for Q2d/Q3d solution or Infinite Sphere name for Far Fields Plot. + plotname : str, optional + Name of the plot. The default is ``None``. + polyline_points : int, optional, + Number of points on which create the report for plots on polylines. + subdesign_id : int, optional + Specify a subdesign ID to export a Touchstone file of this subdesign. Valid for Circuit Only. + The default value is ``None``. + context : str, optional + sweep_name : str, optional + Name of the sweep adaptive setup from which get solutions. Default is ``LastAdaptive``. + + Returns + ------- + :class:`pyaedt.modules.report_templates.Standard` + ``True`` when successful, ``False`` when failed. + + + References + ---------- + + >>> oModule.CreateReport + + Examples + -------- + >>> from pyaedt import Circuit + >>> aedtapp = Circuit() + >>> aedtapp.post.create_report("dB(S(1,1))") + + >>> variations = aedtapp.available_variations.nominal_w_values_dict + >>> aedtapp.post.setups[0].create_report( + ... "dB(S(1,1))", + ... variations=variations, + ... primary_sweep_variable="Freq", + ...) + + >>> aedtapp.post.create_report( + ... "S(1,1)", + ... variations=variations, + ... plot_type="Smith Chart", + ...) + """ + if sweep_name: + setup_sweep_name = [ + i for i in self._app.existing_analysis_sweeps if self.name == i.split(" : ")[0] and sweep_name in i + ] + else: + setup_sweep_name = [i for i in self._app.existing_analysis_sweeps if self.name == i.split(" : ")[0]] + if setup_sweep_name: + return self._app.post.create_report( + expressions=expressions, + domain=domain, + variations=variations, + primary_sweep_variable=primary_sweep_variable, + secondary_sweep_variable=secondary_sweep_variable, + report_category=report_category, + plot_type=plot_type, + context=context, + polyline_points=polyline_points, + plotname=plotname, + setup_sweep_name=setup_sweep_name[0], + ) + return None + class Setup(CommonSetup): """Initializes, creates, and updates a 3D setup. @@ -1181,6 +1416,168 @@ def disable(self, setup_name=None): self._odesign.EnableSolutionSetup(setup_name, False) return True + @pyaedt_function_handler() + def get_solution_data( + self, + expressions=None, + domain=None, + variations=None, + primary_sweep_variable=None, + report_category=None, + context=None, + subdesign_id=None, + polyline_points=1001, + math_formula=None, + ): + """Get a simulation result from a solved setup and cast it in a ``SolutionData`` object. + Data to be retrieved from Electronics Desktop are any simulation results available in that + specific simulation context. + Most of the argument have some defaults which works for most of the ``Standard`` report quantities. + + Parameters + ---------- + expressions : str or list, optional + One or more formulas to add to the report. Example is value ``"dB(S(1,1))"`` or a list of values. + Default is `None` which will return all traces. + domain : str, optional + Plot Domain. Options are "Sweep" for frequency domain related results and "Time" for transient related data. + variations : dict, optional + Dictionary of all families including the primary sweep. + The default is ``None`` which will use the nominal variations of the setup. + primary_sweep_variable : str, optional + Name of the primary sweep. The default is ``"None"`` which, depending on the context, + will internally assign the primary sweep to: + 1. ``Freq`` for frequency domain results, + 2. ``Time`` for transient results, + 3. ``Theta`` for radiation patterns, + 4. ``distance`` for field plot over a polyline. + report_category : str, optional + Category of the Report to be created. If `None` default data Report will be used. + The Report Category can be one of the types available for creating a report depend on the simulation setup. + For example for a Far Field Plot in HFSS the UI shows the report category as "Create Far Fields Report". + The report category will be in this case "Far Fields". + Depending on the setup different categories are available. + If `None` default category will be used (the first item in the Results drop down menu in AEDT). + To get the list of available categories user can use method ``available_report_types``. + context : str, dict, optional + This is the context of the report. + The default is ``None``. It can be: + 1. `None` + 2. ``"Differential Pairs"`` + 3. Reduce Matrix Name for Q2d/Q3d solution + 4. Infinite Sphere name for Far Fields Plot. + 5. Dictionary. If dictionary is passed, key is the report property name and value is property value. + subdesign_id : int, optional + Subdesign ID for exporting a Touchstone file of this subdesign. + This parameter is valid for ``Circuit`` only. + The default value is ``None``. + polyline_points : int, optional + Number of points on which to create the report for plots on polylines. + This parameter is valid for ``Fields`` plot only. + math_formula : str, optional + One of the available AEDT mathematical formulas to apply. For example, ``abs, dB``. + + + Returns + ------- + :class:`pyaedt.modules.solutions.SolutionData` + Solution Data object. + + References + ---------- + + >>> oModule.GetSolutionDataPerVariation + """ + return self._app.post.get_solution_data( + expressions=expressions, + domain=domain, + variations=variations, + primary_sweep_variable=primary_sweep_variable, + report_category=report_category, + context=context, + subdesign_id=subdesign_id, + polyline_points=polyline_points, + math_formula=math_formula, + setup_sweep_name=self.name, + ) + + @pyaedt_function_handler() + def create_report( + self, + expressions=None, + domain="Sweep", + variations=None, + primary_sweep_variable=None, + secondary_sweep_variable=None, + report_category=None, + plot_type="Rectangular Plot", + context=None, + subdesign_id=None, + polyline_points=1001, + plotname=None, + ): + """Create a report in AEDT. It can be a 2D plot, 3D plot, polar plots or data tables. + + Parameters + ---------- + expressions : str or list, optional + One or more formulas to add to the report. Example is value = ``"dB(S(1,1))"``. + domain : str, optional + Plot Domain. Options are "Sweep", "Time", "DCIR". + variations : dict, optional + Dictionary of all families including the primary sweep. The default is ``{"Freq": ["All"]}``. + primary_sweep_variable : str, optional + Name of the primary sweep. The default is ``"Freq"``. + secondary_sweep_variable : str, optional + Name of the secondary sweep variable in 3D Plots. + report_category : str, optional + Category of the Report to be created. If `None` default data Report will be used. + The Report Category can be one of the types available for creating a report depend on the simulation setup. + For example for a Far Field Plot in HFSS the UI shows the report category as "Create Far Fields Report". + The report category will be in this case "Far Fields". + Depending on the setup different categories are available. + If `None` default category will be used (the first item in the Results drop down menu in AEDT). + plot_type : str, optional + The format of Data Visualization. Default is ``Rectangular Plot``. + context : str, optional + The default is ``None``. It can be `None`, `"Differential Pairs"`,`"RL"`, + `"Sources"`, `"Vias"`,`"Bondwires"`, `"Probes"` for Hfss3dLayout or + Reduce Matrix Name for Q2d/Q3d solution or Infinite Sphere name for Far Fields Plot. + plotname : str, optional + Name of the plot. The default is ``None``. + polyline_points : int, optional, + Number of points on which create the report for plots on polylines. + subdesign_id : int, optional + Specify a subdesign ID to export a Touchstone file of this subdesign. Valid for Circuit Only. + The default value is ``None``. + context : str, optional + + Returns + ------- + :class:`pyaedt.modules.report_templates.Standard` + ``True`` when successful, ``False`` when failed. + + + References + ---------- + + >>> oModule.CreateReport + """ + return self._app.post.create_report( + expressions=expressions, + domain=domain, + variations=variations, + primary_sweep_variable=primary_sweep_variable, + secondary_sweep_variable=secondary_sweep_variable, + report_category=report_category, + plot_type=plot_type, + context=context, + polyline_points=polyline_points, + plotname=plotname, + subdesign_id=subdesign_id, + setup_sweep_name=self.name, + ) + class Setup3DLayout(CommonSetup): """Initializes, creates, and updates a 3D Layout setup. @@ -1232,13 +1629,20 @@ def is_solved(self): `True` if solutions are available. """ if self.props.get("SolveSetupType", "HFSS") == "HFSS": - sol = self._app.post.reports_by_category.standard(setup_name="{} : Last Adaptive".format(self.name)) + combined_name = "{} : Last Adaptive".format(self.name) + expressions = [i for i in self.p_app.post.available_report_quantities(solution=combined_name)] + sol = self._app.post.reports_by_category.standard(setup_name=combined_name, expressions=expressions[0]) elif self.props.get("SolveSetupType", "HFSS") == "SIwave": + combined_name = "{} : {}".format(self.name, self.sweeps[0].name) + expressions = [i for i in self.p_app.post.available_report_quantities(solution=combined_name)] sol = self._app.post.reports_by_category.standard( - setup_name="{} : {}".format(self.name, self.sweeps[0].name) + setup_name=combined_name, + expressions=expressions[0], ) else: - sol = self._app.post.reports_by_category.standard(setup_name=self.name) + expressions = [i for i in self.p_app.post.available_report_quantities(solution=self.name)] + + sol = self._app.post.reports_by_category.standard(setup_name=self.name, expressions=expressions[0]) if identify_setup(self.props): sol.domain = "Time" return True if sol.get_solution_data() else False