diff --git a/_unittest/test_02_3D_modeler.py b/_unittest/test_02_3D_modeler.py index 3553cd5e597..454c62da9c5 100644 --- a/_unittest/test_02_3D_modeler.py +++ b/_unittest/test_02_3D_modeler.py @@ -272,7 +272,7 @@ def test_27_create_region(self): assert self.aedtapp.modeler.create_air_region(20, 20, 30, 50, 50, 100, False) self.aedtapp.modeler["Region"].delete() self.aedtapp["region_param"] = "20mm" - assert self.aedtapp.modeler.create_air_region("region_param", 20, "30mm", "50", 50, 100, False) + assert self.aedtapp.modeler.create_air_region("region_param", 20, "30", "50", 50, 100, False) assert self.aedtapp.modeler.edit_region_dimensions(["40mm", "30mm", 30, 50, 50, 100]) self.aedtapp.modeler["Region"].delete() assert self.aedtapp.modeler.create_air_region("20", 20, 30, 50, 50, 100) diff --git a/_unittest/test_98_Icepak.py b/_unittest/test_98_Icepak.py index b84085e149e..625c1a5e91d 100644 --- a/_unittest/test_98_Icepak.py +++ b/_unittest/test_98_Icepak.py @@ -8,6 +8,7 @@ from pyaedt.generic.settings import settings from pyaedt.modules.Boundary import NativeComponentObject from pyaedt.modules.Boundary import NetworkObject +from pyaedt.modules.MeshIcepak import MeshRegion from pyaedt.modules.SetupTemplates import SetupKeys test_subfolder = "T98" @@ -74,9 +75,17 @@ def test_02A_find_top(self): def test_03_AssignPCBRegion(self): self.aedtapp.globalMeshSettings(2) - self.aedtapp.create_meshregion_component() - pcb_mesh_region = self.aedtapp.mesh.MeshRegion(self.aedtapp.mesh.omeshmodule, [1, 1, 1], "mm", self.aedtapp) + assert self.aedtapp.create_meshregion_component() + self.aedtapp.modeler.create_box([0, 0, 0], [50, 50, 2], "PCB") + old_pcb_mesh_region = MeshRegion( + meshmodule=self.aedtapp.mesh.omeshmodule, dimension=[1, 1, 1], unit="mm", app=self.aedtapp + ) + assert old_pcb_mesh_region.MaxElementSizeX == 1 / 20 + assert old_pcb_mesh_region.MaxElementSizeY == 1 / 20 + assert old_pcb_mesh_region.MaxElementSizeZ == 1 / 20 + pcb_mesh_region = MeshRegion(self.aedtapp, "PCB") pcb_mesh_region.name = "PCB_Region" + # backward compatibility check pcb_mesh_region.UserSpecifiedSettings = True pcb_mesh_region.MaxElementSizeX = 2 pcb_mesh_region.MaxElementSizeY = 2 @@ -90,9 +99,43 @@ def test_03_AssignPCBRegion(self): pcb_mesh_region.MinGapX = 1 pcb_mesh_region.MinGapY = 1 pcb_mesh_region.MinGapZ = 1 - pcb_mesh_region.Objects = ["Component_Region"] - assert pcb_mesh_region.create() assert pcb_mesh_region.update() + if settings.aedt_version > "2023.2": + assert pcb_mesh_region.assignment.padding_values == ["0"] * 6 + assert pcb_mesh_region.assignment.padding_types == ["Percentage Offset"] * 6 + pcb_mesh_region.assignment.negative_x_padding = 1 + pcb_mesh_region.assignment.positive_x_padding = 1 + pcb_mesh_region.assignment.negative_y_padding = 1 + pcb_mesh_region.assignment.positive_y_padding = 1 + pcb_mesh_region.assignment.negative_z_padding = 1 + pcb_mesh_region.assignment.positive_z_padding = 1 + pcb_mesh_region.assignment.negative_x_padding_type = "Absolute Offset" + pcb_mesh_region.assignment.positive_x_padding_type = "Absolute Position" + pcb_mesh_region.assignment.negative_y_padding_type = "Transverse Percentage Offset" + pcb_mesh_region.assignment.positive_y_padding_type = "Absolute Position" + pcb_mesh_region.assignment.negative_z_padding_type = "Absolute Offset" + pcb_mesh_region.assignment.positive_z_padding_type = "Transverse Percentage Offset" + assert pcb_mesh_region.assignment.negative_x_padding == "1mm" + assert pcb_mesh_region.assignment.positive_x_padding == "1mm" + assert pcb_mesh_region.assignment.negative_y_padding == "1" + assert pcb_mesh_region.assignment.positive_y_padding == "1mm" + assert pcb_mesh_region.assignment.negative_z_padding == "1mm" + assert pcb_mesh_region.assignment.positive_z_padding == "1" + assert pcb_mesh_region.assignment.negative_x_padding_type == "Absolute Offset" + assert pcb_mesh_region.assignment.positive_x_padding_type == "Absolute Position" + assert pcb_mesh_region.assignment.negative_y_padding_type == "Transverse Percentage Offset" + assert pcb_mesh_region.assignment.positive_y_padding_type == "Absolute Position" + assert pcb_mesh_region.assignment.negative_z_padding_type == "Absolute Offset" + assert pcb_mesh_region.assignment.positive_z_padding_type == "Transverse Percentage Offset" + pcb_mesh_region.assignment.padding_values = 2 + pcb_mesh_region.assignment.padding_types = "Absolute Offset" + assert pcb_mesh_region.assignment.padding_values == ["2mm"] * 6 + assert pcb_mesh_region.assignment.padding_types == ["Absolute Offset"] * 6 + subregion = self.aedtapp.modeler.create_subregion([50, 50, 50, 50, 100, 100], "Percentage Offset", "PCB") + else: + box = self.aedtapp.modeler.create_box([0, 0, 0], [1, 2, 3]) + pcb_mesh_region.Objects = box.name + assert pcb_mesh_region.update() assert self.aedtapp.mesh.meshregions_dict assert pcb_mesh_region.delete() @@ -219,24 +262,6 @@ def test_12b_failing_AssignMeshOperation(self): assert not test.update() assert test.delete() - def test_12c_AssignVirtualMeshOperation(self): - self.aedtapp.oproject = test_project_name - self.aedtapp.odesign = "IcepakDesign1" - group_name = "Group1" - mesh_level_Filter = "2" - component_name = ["RadioBoard1_1"] - mesh_level_RadioPCB = "1" - test = self.aedtapp.mesh.assign_mesh_level_to_group(mesh_level_Filter, group_name) - assert test - # assert self.aedtapp.mesh.assignMeshLevel2Component(mesh_level_RadioPCB, component_name) - test = self.aedtapp.mesh.assign_mesh_region( - component_name, mesh_level_RadioPCB, is_submodel=True, virtual_region=True - ) - assert test - assert test.delete() - test = self.aedtapp.mesh.assign_mesh_region(["USB_ID"], mesh_level_RadioPCB, virtual_region=True) - assert test - def test_13a_assign_openings(self): airfaces = [self.aedtapp.modeler["Region"].top_face_x.id] openings = self.aedtapp.assign_openings(airfaces) @@ -1305,6 +1330,7 @@ def test_66_update_3d_component(self): component_filepath = self.aedtapp.modeler.user_defined_components["test"].get_component_filepath() assert component_filepath comp = self.aedtapp.modeler.user_defined_components["test"].edit_definition() + comp.modeler.refresh_all_ids() comp.modeler.objects_by_name["surf1"].move([1, 1, 1]) comp.modeler.create_3dcomponent(component_filepath) comp.close_project() diff --git a/pyaedt/application/Analysis3D.py b/pyaedt/application/Analysis3D.py index 48559dffb26..0416ae6804b 100644 --- a/pyaedt/application/Analysis3D.py +++ b/pyaedt/application/Analysis3D.py @@ -577,6 +577,12 @@ def export_3d_model( if removed_objects is None: removed_objects = [] + sub_regions = [] + if self.settings.aedt_version > "2023.2": + sub_regions = [ + o for o in self.modeler.non_model_objects if self.modeler[o].history().command == "CreateSubRegion" + ] + if not object_list: allObjects = self.modeler.object_names if removed_objects: @@ -585,6 +591,8 @@ def export_3d_model( else: if "Region" in allObjects: allObjects.remove("Region") + for o in sub_regions: + allObjects.remove(o) else: allObjects = object_list[:] diff --git a/pyaedt/application/Analysis3DLayout.py b/pyaedt/application/Analysis3DLayout.py index 932296c4ae9..e62a71e73d8 100644 --- a/pyaedt/application/Analysis3DLayout.py +++ b/pyaedt/application/Analysis3DLayout.py @@ -340,7 +340,7 @@ def get_next_xtalk_list(self, trlist=[], tx_prefix=""): return next @pyaedt_function_handler() - def get_fext_xtalk_list(self, trlist=[], reclist=[], tx_prefix="", rx_prefix="", skip_same_index_couples=True): + def get_fext_xtalk_list(self, trlist=None, reclist=None, tx_prefix="", rx_prefix="", skip_same_index_couples=True): """Retrieve a list of all the far end XTalks from two lists of exctitations (driver and receiver). Parameters @@ -371,9 +371,9 @@ def get_fext_xtalk_list(self, trlist=[], reclist=[], tx_prefix="", rx_prefix="", >>> oModule.GetAllPorts """ fext = [] - if not trlist: + if trlist is None: trlist = [i for i in self.excitations if tx_prefix in i] - if not reclist: + if reclist is None: reclist = [i for i in self.excitations if rx_prefix in i] for i in trlist: for k in reclist: diff --git a/pyaedt/application/AnalysisNexxim.py b/pyaedt/application/AnalysisNexxim.py index 716fb0fc605..ef087ec72bd 100644 --- a/pyaedt/application/AnalysisNexxim.py +++ b/pyaedt/application/AnalysisNexxim.py @@ -526,15 +526,10 @@ def get_fext_xtalk_list(self, trlist=None, reclist=None, tx_prefix="", rx_prefix >>> oEditor.GetAllPorts """ - if trlist == None: - trlist = [] - if reclist == None: - reclist = [] - fext = [] - if not trlist: + if trlist is None: trlist = [i for i in list(self.excitations.keys()) if tx_prefix in i] - if not reclist: + if reclist is None: reclist = [i for i in list(self.excitations.keys()) if rx_prefix in i] for i in trlist: for k in reclist: diff --git a/pyaedt/circuit.py b/pyaedt/circuit.py index 1f3da74e6ee..a9b1ac80009 100644 --- a/pyaedt/circuit.py +++ b/pyaedt/circuit.py @@ -1398,7 +1398,7 @@ def set_differential_pair( self.odesign.SaveDiffPairsToFile(tmpfile1) with open_file(tmpfile1, "r") as fh: lines = fh.read().splitlines() - num_diffs_before = len(lines) + old_arg = [] for line in lines: data = line.split(",") diff --git a/pyaedt/desktop.py b/pyaedt/desktop.py index b4949e87125..1e0f4bc856d 100644 --- a/pyaedt/desktop.py +++ b/pyaedt/desktop.py @@ -925,9 +925,9 @@ def _initialize( StandalonePyScriptWrapper = AnsoftCOMUtil.Ansoft.CoreCOMScripting.COM.StandalonePyScriptWrapper if non_graphical or new_session: self.launched_by_pyaedt = True - oapp = StandalonePyScriptWrapper.CreateObjectNew(non_graphical) + return StandalonePyScriptWrapper.CreateObjectNew(non_graphical) else: - oapp = StandalonePyScriptWrapper.CreateObject(version) + return StandalonePyScriptWrapper.CreateObject(version) else: base_path = settings.aedt_install_dir sys.path.insert(0, base_path) @@ -2089,7 +2089,7 @@ def get_ansyscloud_job_info(self, job_id=None, job_name=None): # pragma: no cov >>> d.release_desktop(False,False) """ command = os.path.join(self.install_path, "common", "AnsysCloudCLI", "AnsysCloudCli.exe") - ver = self.aedt_version_id.replace(".", "R") + if job_name: command = [command, "jobinfo", "-j", job_name] elif job_id: diff --git a/pyaedt/generic/configurations.py b/pyaedt/generic/configurations.py index fb0a1e87913..14ba767c84e 100644 --- a/pyaedt/generic/configurations.py +++ b/pyaedt/generic/configurations.py @@ -6,6 +6,7 @@ import pkgutil import tempfile +import pyaedt from pyaedt import Icepak from pyaedt import __version__ from pyaedt import generate_unique_folder_name @@ -14,6 +15,7 @@ from pyaedt.application.Variables import decompose_variable_value from pyaedt.generic.DataHandlers import _arg2dict from pyaedt.generic.LoadAEDTFile import load_keyword_in_aedt_file +from pyaedt.generic.general_methods import GrpcApiError from pyaedt.generic.general_methods import generate_unique_name from pyaedt.generic.general_methods import pyaedt_function_handler from pyaedt.generic.general_methods import read_configuration_file @@ -28,6 +30,8 @@ from pyaedt.modules.DesignXPloration import SetupParam from pyaedt.modules.MaterialLib import Material from pyaedt.modules.Mesh import MeshOperation +from pyaedt.modules.MeshIcepak import MeshRegion +from pyaedt.modules.MeshIcepak import SubRegion if not is_ironpython: from jsonschema import exceptions @@ -689,7 +693,9 @@ def __init__(self, app): self.results = ImportResults() # Read the default configuration schema from pyaedt - schema_bytes = pkgutil.get_data(__name__, "../misc/config.schema.json") + schema_bytes = pkgutil.get_data( + __name__, os.path.join(os.path.dirname(pyaedt.__file__), "misc", "config.schema.json") + ) schema_string = schema_bytes.decode("utf-8") self._schema = json.loads(schema_string) @@ -917,7 +923,7 @@ def _update_mesh_operations(self, name, props): bound = MeshOperation(self._app.mesh, name, props, props["Type"]) if bound.create(): self._app.mesh.meshoperations.append(bound) - self._app.logger.info("mesh Operation {} added.".format(name)) + self._app.logger.info("Mesh Operation {} added.".format(name)) return True else: self._app.logger.warning("Failed to add Mesh {} ".format(name)) @@ -1142,6 +1148,14 @@ def import_config(self, config_file, *args): "Circuit Design", ]: self._app.modeler.set_working_coordinate_system("Global") + + if self.options.import_mesh_operations and dict_in.get("mesh", None): + self.results.import_mesh_operations = True + for name, props in dict_in["mesh"].items(): + self._convert_objects(props, dict_in["general"]["object_mapping"]) + if not self._update_mesh_operations(name, props): + self.results.import_mesh_operations = False + if self.options.import_object_properties and dict_in.get("objects", None): self.results.import_object_properties = True for obj, val in dict_in["objects"].items(): @@ -1168,13 +1182,6 @@ def import_config(self, config_file, *args): if not self._update_boundaries(name, dict_in["boundaries"][name]): self.results.import_boundaries = False - if self.options.import_mesh_operations and dict_in.get("mesh", None): - self.results.import_mesh_operations = True - for name, props in dict_in["mesh"].items(): - self._convert_objects(props, dict_in["general"]["object_mapping"]) - if not self._update_mesh_operations(name, props): - self.results.import_mesh_operations = False - if self.options.import_setups and dict_in.get("setups", None): self.results.import_setup = True for setup, props in dict_in["setups"].items(): @@ -1612,19 +1619,23 @@ def _update_mesh_operations(self, name, props): if el in mesh_el.__dict__: mesh_el.__dict__[el] = props[el] return mesh_el.update() - - bound = self._app.mesh.MeshRegion( - self._app.mesh.omeshmodule, self._app.mesh.boundingdimension, self._app.mesh._model_units, self._app - ) - bound.name = name - for el in props: - if el in bound.__dict__: - bound.__dict__[el] = props[el] - if bound.create(): + try: + if self._app.settings.aedt_version < "2024.1": + objs = props.get("Objects", []) + props.get("Submodels", []) + else: + subregion = SubRegion(self._app, props["_subregion_information"]["parts"]) + subregion.padding_values = props["_subregion_information"]["pad_vals"] + subregion.padding_types = props["_subregion_information"]["pad_types"] + objs = subregion.name + bound = MeshRegion(app=self._app, name=name, objects=objs) + bound.manual_settings = props["UserSpecifiedSettings"] + for el in props: + if el in bound.settings: + bound.settings[el] = props[el] self._app.mesh.meshregions.append(bound) - self._app.logger.info("mesh Operation {} added.".format(name)) - else: - self._app.logger.warning("Failed to add Mesh {} ".format(name)) + self._app.logger.info("Mesh Operation {} added.".format(name)) + except GrpcApiError: + self._app.logger.warning("Failed to add mesh {} ".format(name)) return True @pyaedt_function_handler() @@ -1635,7 +1646,7 @@ def _export_objects_properties(self, dict_out): self._app.modeler.refresh_all_ids() udc_parts_id = [part for _, uc in self._app.modeler.user_defined_components.items() for part in uc.parts] for val in self._app.modeler.objects.values(): - if val.id in udc_parts_id: + if val.id in udc_parts_id or val.history().command == "CreateSubRegion": continue dict_out["objects"][val.name] = {} dict_out["objects"][val.name]["SurfaceMaterial"] = val.surface_material_name @@ -1651,10 +1662,7 @@ def _export_objects_properties(self, dict_out): def _export_mesh_operations(self, dict_out): dict_out["mesh"] = {} args = ["NAME:Settings"] - if self._app.mesh.global_mesh_region.UserSpecifiedSettings: - args += self._app.mesh.global_mesh_region.manualsettings - else: - args += self._app.mesh.global_mesh_region.autosettings + args += self._app.mesh.global_mesh_region.settings.parse_settings() mop = OrderedDict({}) _arg2dict(args, mop) dict_out["mesh"]["Settings"] = mop["Settings"] @@ -1664,12 +1672,17 @@ def _export_mesh_operations(self, dict_out): args = ["NAME:Settings"] else: args = ["NAME:" + mesh.name, "Enable:=", mesh.Enable] - if mesh.UserSpecifiedSettings: - args += mesh.manualsettings - else: - args += mesh.autosettings + args += mesh.settings.parse_settings() + args += getattr(mesh, "_parse_assignment_value")() + args += ["UserSpecifiedSettings:=", not mesh.manual_settings] mop = OrderedDict({}) _arg2dict(args, mop) + if self._app.modeler[args[-3][0]].history().command == "CreateSubRegion": + mop[mesh.name]["_subregion_information"] = { + "pad_vals": mesh.assignment.padding_values, + "pad_types": mesh.assignment.padding_types, + "parts": list(mesh.assignment.parts.keys()), + } dict_out["mesh"][mesh.name] = mop[mesh.name] self._map_object(mop, dict_out) pass diff --git a/pyaedt/icepak.py b/pyaedt/icepak.py index 75362d3937b..626d01ec959 100644 --- a/pyaedt/icepak.py +++ b/pyaedt/icepak.py @@ -2768,6 +2768,9 @@ def create_meshregion_component( ): """Create a bounding box to use as a mesh region in Icepak. + .. deprecated:: 0.8.3 + Use ``create_subregion`` or ``create_region`` functions inside the modeler class. + Parameters ---------- scale_factor : float, optional @@ -2787,6 +2790,12 @@ def create_meshregion_component( >>> oeditor.ChangeProperty """ + warnings.warn( + "``create_meshregion_component`` was deprecated in 0.8.3." + "Use ``create_subregion`` or ``create_region`` instead.", + DeprecationWarning, + ) + self.modeler.edit_region_dimensions([0, 0, 0, 0, 0, 0]) vertex_ids = self.modeler.oeditor.GetVertexIDsFromObject("Region") diff --git a/pyaedt/modeler/cad/Primitives.py b/pyaedt/modeler/cad/Primitives.py index 023c94a5ef5..343e5f83db5 100644 --- a/pyaedt/modeler/cad/Primitives.py +++ b/pyaedt/modeler/cad/Primitives.py @@ -4002,7 +4002,7 @@ def create_air_region(self, x_pos=0, y_pos=0, z_pos=0, x_neg=0, y_neg=0, z_neg=0 >>> oEditor.CreateRegion """ - return self.create_region([x_pos, y_pos, z_pos, x_neg, y_neg, z_neg], is_percentage) + return self.create_region(pad_percent=[x_pos, y_pos, z_pos, x_neg, y_neg, z_neg], is_percentage=is_percentage) @pyaedt_function_handler() def edit_region_dimensions(self, listvalues): @@ -6039,92 +6039,121 @@ def does_object_exists(self, obj_to_check): else: return False - @pyaedt_function_handler() - def create_region(self, pad_percent=300, is_percentage=True): - """Create an air region. + @pyaedt_function_handler + def create_subregion(self, padding_values, padding_types, parts, region_name=None): + """Create a subregion. Parameters ---------- - pad_percent : float, str, list of floats or list of str, optional - Same padding is applied if not a list. The default is ``300``. - If a list of floats or str, interpret as adding for ``["+X", "+Y", "+Z", "-X", "-Y", "-Z"]``. - is_percentage : bool, optional - Region definition in percentage or absolute value. The default is `True``. + padding_values : float, str, list of floats or list of str + Padding values to apply. If a list is not provided, the same + value is applied to all padding directions. If a list of floats + or strings is provided, the values are + interpreted as padding for ``["+X", "-X", "+Y", "-Y", "+Z", "-Z"]``. + padding_types : str or list of str, optional + Padding definition. The default is ``"Percentage Offset"``. + Options are ``"Absolute Offset"``, + ``"Absolute Position"``, ``"Percentage Offset"``, and + ``"Transverse Percentage Offset"``. When using a list, + different padding types can be provided for different + directions. + parts : list of str + One or more names of the parts to include in the subregion. + region_name : str, optional + Region name. The default is ``None``, in which case the name + is generated automatically. Returns ------- :class:`pyaedt.modeler.cad.object3d.Object3d` - Region object. + Subregion object. References ---------- >>> oEditor.CreateRegion """ - return self._create_region(pad_percent=pad_percent, is_percentage=is_percentage) + if region_name is None: + region_name = generate_unique_name("SubRegion") + is_percentage = padding_types in ["Percentage Offset", "Transverse Percentage Offset"] + arg, arg2 = self._parse_region_args( + padding_values, padding_types, region_name, parts, "SubRegion", is_percentage + ) + self.oeditor.CreateSubregion(arg, arg2) + return self._create_object(region_name) - @pyaedt_function_handler() - def _create_region(self, pad_percent=300, is_percentage=True): - """Create an air region. + def reassign_subregion(self, region, parts): + """Modify parts in the subregion. Parameters ---------- - pad_percent : float, str, list of floats or list of str, optional - Same padding is applied if not a list. The default is ``300``. - If a list of floats or str, interpret as adding for ``["+X", "+Y", "+Z", "-X", "-Y", "-Z"]``. - is_percentage : bool, optional - Region definition in percentage or absolute value. The default is `True``. + region : :class:`pyaedt.modules.MeshIcepak.SubRegion` + Subregion to modify. + parts : list of str + One or more names of the parts to include in the subregion. Returns ------- - :class:`pyaedt.modeler.cad.object3d.Object3d` - Region object. + bool + ``True`` when successful, ``False`` when failed. References ---------- >>> oEditor.CreateRegion """ - if "Region" in self.object_names: - return None - if not isinstance(pad_percent, list): - pad_percent = [pad_percent] * 6 - - arg = ["NAME:RegionParameters"] + is_percentage = region.padding_types in ["Percentage Offset", "Transverse Percentage Offset"] + arg, arg2 = self._parse_region_args( + region.padding_values, region.padding_types, region.name, parts, "SubRegion", is_percentage + ) + self.oeditor.ReassignSubregion(arg, arg2) + if self._create_object(region.name): + return True + return False - # TODO: Can the order be updated to match the UI? - p = ["+X", "+Y", "+Z", "-X", "-Y", "-Z"] - i = 0 - for pval in p: - region_type = "Percentage Offset" - if not is_percentage: - region_type = "Absolute Offset" + @pyaedt_function_handler() + def _parse_region_args(self, pad_value, pad_type, region_name, parts, region_type, is_percentage): + arg = ["NAME:{}Parameters".format(region_type)] + p = ["+X", "-X", "+Y", "-Y", "+Z", "-Z"] + if not isinstance(pad_value, list): + pad_value = [pad_value] * 6 + if not isinstance(pad_type, list): + pad_type = [pad_type] * 6 + for i, pval in enumerate(p): pvalstr = str(pval) + "PaddingType:=" qvalstr = str(pval) + "Padding:=" arg.append(pvalstr) - arg.append(region_type) + arg.append(pad_type[i]) arg.append(qvalstr) - if isinstance(pad_percent[i], str): - units = decompose_variable_value(pad_percent[i])[1] - if not units and pad_percent[i].isnumeric(): - if not is_percentage: - units = self.model_units - pad_percent[i] += units - elif is_percentage: + if isinstance(pad_value[i], str): + units = decompose_variable_value(pad_value[i])[1] + if not units and pad_value[i].isnumeric() and not is_percentage: + units = self.model_units + pad_value[i] += units + elif units and is_percentage: self.logger.error("Percentage input must not have units") - return False + return False, False elif not is_percentage: units = self.model_units - pad_percent[i] = str(pad_percent[i]) - pad_percent[i] += units - arg.append(str(pad_percent[i])) - i += 1 + pad_value[i] = str(pad_value[i]) + pad_value[i] += units + arg.append(str(pad_value[i])) + flags = "Wireframe#" + if region_type == "SubRegion": + if not isinstance(parts, list): + parts = [parts] + normal_parts = [p for p in parts if p in self._app.modeler.objects_by_name] + submodel_parts = [p for p in parts if p in self._app.modeler.user_defined_components] + normal_parts = ",".join(normal_parts) + submodel_parts = ",".join(submodel_parts) + arg += [["NAME:SubRegionPartNames", normal_parts], ["NAME:SubRegionSubmodelNames", submodel_parts]] + flags = "NonModel#Wireframe" arg2 = [ "NAME:Attributes", "Name:=", - "Region", + region_name, "Flags:=", - "Wireframe#", + flags, "Color:=", "(143 175 143)", "Transparency:=", @@ -6146,8 +6175,78 @@ def _create_region(self, pad_percent=300, is_percentage=True): "IsLightweight:=", False, ] - self.oeditor.CreateRegion(arg, arg2) - return self._create_object("Region") + return arg, arg2 + + @pyaedt_function_handler() + def _create_region( + self, pad_value=300, pad_type="Percentage Offset", region_name="Region", parts=None, region_type="Region" + ): + if region_name in self._app.modeler.objects_by_name: + self._app.logger.error("{} object already exists".format(region_name)) + return False + if not isinstance(pad_value, list): + pad_value = [pad_value] * 6 + is_percentage = pad_type in ["Percentage Offset", "Transverse Percentage Offset"] + arg, arg2 = self._parse_region_args(pad_value, pad_type, region_name, parts, region_type, is_percentage) + if arg and arg2: + self.oeditor.CreateRegion(arg, arg2) + return self._create_object(region_name) + else: + return False + + @pyaedt_function_handler() + def create_region(self, pad_value=300, pad_type="Percentage Offset", region_name="Region", **kwarg): + """Create an air region. + + Parameters + ---------- + pad_value : float, str, list of floats or list of str, optional + Padding values to apply. If a list is not provided, the same + value is applied to all padding directions. If a list of floats + or strings is provided, the values are + interpreted as padding for ``["+X", "-X", "+Y", "-Y", "+Z", "-Z"]``. + pad_type : str, optional + Padding definition. The default is ``"Percentage Offset"``. + Options are ``"Absolute Offset"``, + ``"Absolute Position"``, ``"Percentage Offset"``, and + ``"Transverse Percentage Offset"``. When using a list, + different padding types can be provided for different + directions. + region_name : str, optional + Region name. The default is ``None``, in which case the name + is generated automatically. + + Returns + ------- + :class:`pyaedt.modeler.cad.object3d.Object3d` + Region object. + + References + ---------- + + >>> oEditor.CreateRegion + """ + # backward compatibility + if kwarg: + if "is_percentage" in kwarg.keys(): + is_percentage = kwarg["is_percentage"] + else: + is_percentage = True + if kwarg.get("pad_percent", False): + pad_percent = kwarg["pad_percent"] + else: + pad_percent = 300 + pad_value = pad_percent + if isinstance(pad_value, list): + pad_value = [pad_value[i // 2 + 3 * (i % 2)] for i in range(6)] + pad_type = ["Absolute Offset", "Percentage Offset"][int(is_percentage)] + + if isinstance(pad_type, bool): + pad_type = ["Absolute Offset", "Percentage Offset"][int(pad_type)] + if isinstance(pad_value, list): + pad_value = [pad_value[i // 2 + 3 * (i % 2)] for i in range(6)] + + return self._create_region(pad_value, pad_type, region_name, region_type="Region") @pyaedt_function_handler() def create_object_from_edge(self, edge, non_model=False): diff --git a/pyaedt/modeler/cad/Primitives2D.py b/pyaedt/modeler/cad/Primitives2D.py index 6ff9c63bd84..b70edcfe437 100644 --- a/pyaedt/modeler/cad/Primitives2D.py +++ b/pyaedt/modeler/cad/Primitives2D.py @@ -305,15 +305,15 @@ def create_region(self, pad_percent=300, is_percentage=True): """ if not isinstance(pad_percent, list): if self._app.design_type == "2D Extractor" or self._app.design_type == "Maxwell 2D": - if hasattr(self._app.SOLUTIONS, self._app.solution_type): - pad_percent = [pad_percent, pad_percent, 0, pad_percent, pad_percent, 0] + if self._app.odesign.GetGeometryMode() == "XY": + pad_percent = [pad_percent, pad_percent, pad_percent, pad_percent, 0, 0] else: - pad_percent = [pad_percent, 0, pad_percent, 0, 0, pad_percent] + pad_percent = [pad_percent, 0, 0, 0, pad_percent, pad_percent] else: if self._app.design_type == "2D Extractor" or self._app.design_type == "Maxwell 2D": - if hasattr(self._app.SOLUTIONS, self._app.solution_type): - pad_percent = [pad_percent[0], pad_percent[1], 0, pad_percent[2], pad_percent[3], 0] + if self._app.odesign.GetGeometryMode() == "XY": + pad_percent = [pad_percent[0], pad_percent[2], pad_percent[1], pad_percent[3], 0, 0] else: - pad_percent = [pad_percent[0], 0, pad_percent[1], 0, 0, pad_percent[2]] + pad_percent = [pad_percent[0], 0, 0, 0, pad_percent[1], pad_percent[2]] - return self._create_region(pad_percent, is_percentage) + return self._create_region(pad_percent, ["Absolute Offset", "Percentage Offset"][int(is_percentage)]) diff --git a/pyaedt/modeler/circuits/object3dcircuit.py b/pyaedt/modeler/circuits/object3dcircuit.py index 89a376aeee3..29bc419c2e4 100644 --- a/pyaedt/modeler/circuits/object3dcircuit.py +++ b/pyaedt/modeler/circuits/object3dcircuit.py @@ -470,8 +470,6 @@ def model_data(self): ------- :class:`pyaedt.modeler.Object3d.ModelParameters` """ - """Return the model data if the component has one. - """ if self._model_data: return self._model_data if self.model_name: diff --git a/pyaedt/modeler/modelerpcb.py b/pyaedt/modeler/modelerpcb.py index 0ce1de8e043..0523da5227a 100644 --- a/pyaedt/modeler/modelerpcb.py +++ b/pyaedt/modeler/modelerpcb.py @@ -157,7 +157,7 @@ def fit_all(self): @property def model_units(self): - """Model units. + """Model units as a string (for example, "mm"). References ---------- @@ -170,7 +170,6 @@ def model_units(self): @model_units.setter def model_units(self, units): assert units in AEDT_UNITS["Length"], "Invalid units string {0}.".format(units) - """Set the model units as a string (for example, "mm").""" self.oeditor.SetActiveUnits(units) @property diff --git a/pyaedt/modeler/schematic.py b/pyaedt/modeler/schematic.py index b9a80729d48..e8c0a597848 100644 --- a/pyaedt/modeler/schematic.py +++ b/pyaedt/modeler/schematic.py @@ -563,8 +563,8 @@ def primitives(self): @model_units.setter def model_units(self, units): + """Set the model units as a string e.g. "mm".""" assert units in AEDT_UNITS["Length"], "Invalid units string {0}".format(units) - """ Set the model units as a string e.g. "mm" """ self.oeditor.SetActivelUnits(["NAME:Units Parameter", "Units:=", units, "Rescale:=", False]) @pyaedt_function_handler() diff --git a/pyaedt/modules/AdvancedPostProcessing.py b/pyaedt/modules/AdvancedPostProcessing.py index 86e709c5272..e88ed21ce0a 100644 --- a/pyaedt/modules/AdvancedPostProcessing.py +++ b/pyaedt/modules/AdvancedPostProcessing.py @@ -1118,7 +1118,7 @@ def get_fans_operating_point(self, export_file=None, setup_name=None, timestep=N @pyaedt_function_handler def _parse_field_summary_content(self, fs, setup_name, design_variation, quantity_name): - content = fs.get_field_summary_data(sweep_name=setup_name, design_variation=design_variation) + content = fs.get_field_summary_data(setup_name=setup_name, design_variation=design_variation) pattern = r"\[([^]]*)\]" match = re.search(pattern, content["Quantity"][0]) if match: @@ -1175,11 +1175,15 @@ def evaluate_faces_quantity( """ if design_variation is None: design_variation = {} - name = generate_unique_name(quantity_name) - self._app.modeler.create_face_list(faces_list, name) + facelist_name = generate_unique_name(quantity_name) + self._app.modeler.create_face_list(faces_list, facelist_name) fs = self.create_field_summary() - fs.add_calculation("Object", "Surface", name, quantity_name, side=side, ref_temperature=ref_temperature) - return self._parse_field_summary_content(fs, setup_name, design_variation, quantity_name) + fs.add_calculation( + "Object", "Surface", facelist_name, quantity_name, side=side, ref_temperature=ref_temperature + ) + out = self._parse_field_summary_content(fs, setup_name, design_variation, quantity_name) + self._app.oeditor.Delete(["NAME:Selections", "Selections:=", facelist_name]) + return out @pyaedt_function_handler() def evaluate_boundary_quantity( diff --git a/pyaedt/modules/Boundary.py b/pyaedt/modules/Boundary.py index 0d9dd231e7f..2d3f0b7bb13 100644 --- a/pyaedt/modules/Boundary.py +++ b/pyaedt/modules/Boundary.py @@ -3631,6 +3631,16 @@ def create(self): self.props["Faces"] = [node.props["FaceID"] for _, node in self.face_nodes.items()] if not self.props.get("SchematicData", None): self.props["SchematicData"] = OrderedDict({}) + + if self.props.get("Links", None): + self.props["Links"] = {link_name: link_values.props for link_name, link_values in self.links.items()} + else: # pragma : no cover + raise KeyError("Links information is missing.") + if self.props.get("Nodes", None): + self.props["Nodes"] = {node_name: node_values.props for node_name, node_values in self.nodes.items()} + else: # pragma : no cover + raise KeyError("Nodes information is missing.") + args = self._get_args() clean_args = self._clean_list(args) diff --git a/pyaedt/modules/MeshIcepak.py b/pyaedt/modules/MeshIcepak.py index 27e0cfa6d94..f4c1e03ec17 100644 --- a/pyaedt/modules/MeshIcepak.py +++ b/pyaedt/modules/MeshIcepak.py @@ -1,5 +1,9 @@ +from abc import abstractmethod from collections import OrderedDict +import warnings +from pyaedt.generic.general_methods import GrpcApiError +from pyaedt.generic.general_methods import _dim_arg from pyaedt.generic.general_methods import generate_unique_name from pyaedt.generic.general_methods import pyaedt_function_handler from pyaedt.generic.settings import settings @@ -7,6 +11,911 @@ from pyaedt.modules.Mesh import meshers +class CommonRegion(object): + def __init__(self, app, name): + self._app = app + self._name = name + self._padding_type = None # ["Percentage Offset"] * 6 + self._padding_value = None # [50] * 6 + self._coordinate_system = None # "Global" + self._dir_order = ["+X", "-X", "+Y", "-Y", "+Z", "-Z"] + + @property + def padding_types(self): + """ + Get a list of strings containing thepadding types used, + one for each direction, in the following order: + +X, -X, +Y, -Y, +Z, -Z. + + Returns + ------- + List[str] + """ + self._update_region_data() + return self._padding_type + + @property + def padding_values(self): + """ + Get a list of padding values (string or float) used, + one for each direction, in the following order: + +X, -X, +Y, -Y, +Z, -Z. + + Returns + ------- + List[Union[str, float]] + """ + self._update_region_data() + return self._padding_value + + @property + def positive_x_padding_type(self): + """ + Get a string with the padding type used in the +X direction. + + Returns + ------- + str + """ + return self._get_region_data("+X") + + @property + def negative_x_padding_type(self): + """ + Get a string with the padding type used in the -X direction. + + Returns + ------- + str + """ + return self._get_region_data("-X") + + @property + def positive_y_padding_type(self): + """ + Get a string with the padding type used in the +Y direction. + + Returns + ------- + str + """ + return self._get_region_data("+Y") + + @property + def negative_y_padding_type(self): + """ + Get a string with the padding type used in the -Y direction. + + Returns + ------- + str + """ + return self._get_region_data("-Y") + + @property + def positive_z_padding_type(self): + """ + Get a string with the padding type used in the +Z direction. + + Returns + ------- + str + """ + return self._get_region_data("+Z") + + @property + def negative_z_padding_type(self): + """ + Get a string with the padding type used in the -Z direction. + + Returns + ------- + str + """ + return self._get_region_data("-Z") + + @property + def positive_x_padding(self): + """ + Get a string with the padding value used in the +X direction. + + Returns + ------- + float + """ + return self._get_region_data("+X", False) + + @property + def negative_x_padding(self): + """ + Get a string with the padding value used in the -X direction. + + Returns + ------- + float + """ + return self._get_region_data("-X", False) + + @property + def positive_y_padding(self): + """ + Get a string with the padding value used in the +Y direction. + + Returns + ------- + float + """ + return self._get_region_data("+Y", False) + + @property + def negative_y_padding(self): + """ + Get a string with the padding value used in the -Y direction. + + Returns + ------- + float + """ + return self._get_region_data("-Y", False) + + @property + def positive_z_padding(self): + """ + Get a string with the padding value used in the +Z direction. + + Returns + ------- + float + """ + return self._get_region_data("+Z", False) + + @property + def negative_z_padding(self): + """ + Get a string with the padding value used in the -Z direction. + + Returns + ------- + float + """ + return self._get_region_data("-Z", False) + + @padding_types.setter + def padding_types(self, values): + if not isinstance(values, list): + values = [values] * 6 + for i, direction in enumerate(self._dir_order): + self._set_region_data(values[i], direction, True) + + @padding_values.setter + def padding_values(self, values): + if not isinstance(values, list): + values = [values] * 6 + for i, direction in enumerate(self._dir_order): + self._set_region_data(values[i], direction, False) + + @positive_x_padding_type.setter + def positive_x_padding_type(self, value): + self._set_region_data(value, "+X", True) + + @negative_x_padding_type.setter + def negative_x_padding_type(self, value): + self._set_region_data(value, "-X", True) + + @positive_y_padding_type.setter + def positive_y_padding_type(self, value): + self._set_region_data(value, "+Y", True) + + @negative_y_padding_type.setter + def negative_y_padding_type(self, value): + self._set_region_data(value, "-Y", True) + + @positive_z_padding_type.setter + def positive_z_padding_type(self, value): + self._set_region_data(value, "+Z", True) + + @negative_z_padding_type.setter + def negative_z_padding_type(self, value): + self._set_region_data(value, "-Z", True) + + @positive_x_padding.setter + def positive_x_padding(self, value): + self._set_region_data(value, "+X", False) + + @negative_x_padding.setter + def negative_x_padding(self, value): + self._set_region_data(value, "-X", False) + + @positive_y_padding.setter + def positive_y_padding(self, value): + self._set_region_data(value, "+Y", False) + + @negative_y_padding.setter + def negative_y_padding(self, value): + self._set_region_data(value, "-Y", False) + + @positive_z_padding.setter + def positive_z_padding(self, value): + self._set_region_data(value, "+Z", False) + + @negative_z_padding.setter + def negative_z_padding(self, value): + self._set_region_data(value, "-Z", False) + + @property + def object(self): + """ + Get the subregion modeler object. + + Returns + ------- + ::class::modeler.cad.object3d.Object3d + """ + if isinstance(self, Region): + return { + "CreateRegion": oo + for o, oo in self._app.modeler.objects_by_name.items() + if oo.history().command == "CreateRegion" + }.get("CreateRegion", None) + else: + return self._app.modeler.objects_by_name.get(self._name, None) + + @property + def name(self): + """ + Get the subregion name. + + Returns + ------- + str + """ + return self.object.name + + @name.setter + def name(self, value): + try: + self._app.modeler.objects_by_name[self._name].name = value + except KeyError: + if self._app.modeler.objects_by_name[value].history().command == "CreateSubRegion": + self._name = value + + def _set_region_data(self, value, direction=None, padding_type=True): + self._update_region_data() + region = self.object + create_region = region.history() + set_type = ["Data", "Type"][int(padding_type)] + create_region.props["{} Padding {}".format(direction, set_type)] = value + + def _update_region_data(self): + region = self.object + create_region = region.history() + self._padding_type = [] + self._padding_value = [] + for padding_direction in ["+X", "-X", "+Y", "-Y", "+Z", "-Z"]: + self._padding_type.append(create_region.props["{} Padding Type".format(padding_direction)]) + self._padding_value.append(create_region.props["{} Padding Data".format(padding_direction)]) + self._coordinate_system = create_region.props["Coordinate System"] + + def _get_region_data(self, direction=None, padding_type=True): + self._update_region_data() + idx = self._dir_order.index(direction) + if padding_type: + return self._padding_type[idx] + else: + return self._padding_value[idx] + + +class Region(CommonRegion): + def __init__(self, app): + super(Region, self).__init__(app, None) + try: + self._update_region_data() + except AttributeError: + pass + + +class SubRegion(CommonRegion): + def __init__(self, app, parts, name=None): + if name is None: + name = generate_unique_name("SubRegion") + super(SubRegion, self).__init__(app, name) + self.create(0, "Percentage Offset", name, parts) + + def create(self, padding_values, padding_types, region_name, parts): + """ + Create subregion object. + + Parameters + ---------- + padding_values : list of str or float + List of padding values to apply in each direction, in the following order: + +X, -X, +Y, -Y, +Z, -Z. + padding_types : list of str + List of padding types to apply in each direction, in the following order: + +X, -X, +Y, -Y, +Z, -Z. + region_name : str + Name to assign to the subregion. + parts : list of str + Parts to be included in the subregion. + + Returns + ------- + bool + True if successful, else False + """ + try: + if ( + self.object is not None and self._app.modeler.objects_by_name.get(self.object.name, False) + ) or self._app.modeler.objects_by_name.get(region_name, False): + self._app.logger.error("{} already exists in the design.".format(self.object.name)) + return False + if not isinstance(parts, list): + objects = [parts] + if not isinstance(objects[0], str): + objects = [o.name for o in objects] + self._app.modeler.create_subregion(padding_values, padding_types, parts, region_name) + return True + except: + return False + + def delete(self): + """ + Delete the subregion object. + + Returns + ------- + bool + True if successful, else False + """ + try: + self.object.delete() + self._app.mesh.meshregions.remove( + [mo for mo in self._app.mesh.meshregions.values() if mo.subregion == self][0] + ) + return True + except Exception: + return False + + @property + def parts(self): + """ + Parts included in the subregion. + + Returns + ------- + dict + Dictionary with the part names as keys and ::class::modeler.cad.object3d.Object3d as values. + """ + if self.object: + return { + obj_name: self._app.modeler[obj_name] + for obj_name in self.object.history().props["Part Names"].split(",") + } + else: + return {} + + @parts.setter + def parts(self, parts): + """ + Parts included in the subregion. + + Parameters + ------- + parts : List[str] + List of strings containing all the parts that must be included in the subregion. + """ + self._app.modeler.reassign_subregion(self, parts) + + +class MeshSettings(object): + automatic_mesh_settings = {"MeshRegionResolution": 3} # min: 1, max: 5 + common_mesh_settings = { + "ProximitySizeFunction": True, + "CurvatureSizeFunction": True, + "EnableTransition": False, + "OptimizePCBMesh": True, + "Enable2DCutCell": False, + "EnforceCutCellMeshing": False, + "Enforce2dot5DCutCell": False, + "StairStepMeshing": False, + } + manual_mesh_settings = { + "MaxElementSizeX": "0.02mm", + "MaxElementSizeY": "0.02mm", + "MaxElementSizeZ": "0.03mm", + "MinElementsInGap": "3", + "MinElementsOnEdge": "2", + "MaxSizeRatio": "2", + "NoOGrids": False, + "EnableMLM": True, + "EnforeMLMType": "3D", + "MaxLevels": "0", + "BufferLayers": "0", + "UniformMeshParametersType": "Average", + "2DMLMType": "2DMLM_None", + "MinGapX": "1mm", + "MinGapY": "1mm", + "MinGapZ": "1mm", + } + aedt_20212_args = [ + "ProximitySizeFunction", + "CurvatureSizeFunction", + "EnableTransition", + "OptimizePCBMesh", + "Enable2DCutCell", + "EnforceCutCellMeshing", + "Enforce2dot5DCutCell", + ] + + def __init__(self, mesh_class, app): + self._app = app + self._mesh_class = mesh_class + self.instance_settings = self.common_mesh_settings.copy() + self.instance_settings.update(self.manual_mesh_settings.copy()) + self.instance_settings.update(self.automatic_mesh_settings.copy()) + if settings.aedt_version < "2021.2": + for arg in self.aedt_20212_args: + del self.instance_settings[arg] + + @pyaedt_function_handler() + def _dim_arg(self, value): + if isinstance(value, str): + return value + else: + return _dim_arg(value, getattr(self._mesh_class, "_model_units")) + + def parse_settings(self): + """ + Parse mesh region settings. + + Returns + ------- + dict + List of strings containing all the parts that must be included in the subregion. + """ + out = [] + for k, v in self.instance_settings.items(): + out.append(k + ":=") + if k in ["MaxElementSizeX", "MaxElementSizeY", "MaxElementSizeZ", "MinGapX", "MinGapY", "MinGapZ"]: + v = self._dim_arg(v) + out.append(v) + return out + + def _key_in_dict(self, key): + if self._mesh_class.manual_settings: + ref_dict = self.manual_mesh_settings + else: + ref_dict = self.automatic_mesh_settings + return key in ref_dict or key in self.common_mesh_settings + + def __getitem__(self, key): + if key == "Level": + key = "MeshRegionResolution" + if self._key_in_dict(key): + return self.instance_settings[key] + else: + raise KeyError("Setting not available.") + + def __setitem__(self, key, value): + if key == "Level": + key = "MeshRegionResolution" + if self._key_in_dict(key): + if key == "MeshRegionResolution": + try: + value = int(value) + if value < 1: + self._app.logger.warning( + 'Minimum resolution value is 1. `"MeshRegionResolution"` has been ' "set to 1." + ) + value = 1 + if value > 5: + self._app.logger.warning( + 'Maximum resolution value is 5. `"MeshRegionResolution"` has been ' "set to 5." + ) + value = 5 + except TypeError: + pass + self.instance_settings[key] = value + else: + self._app.logger.error("Setting not available.") + + def __delitem__(self, key): + self._app.logger.error("Setting cannot be removed.") + + def __iter__(self): + return self.instance_settings.__iter__() + + def __len__(self): + return self.instance_settings.__len__() + + def __contains__(self, x): + return self.instance_settings.__contains__(x) + + +class MeshRegionCommon(object): + """ + Manages Icepak mesh region settings. + + Attributes: + name : str + Name of the mesh region. + manual_settings : bool + Whether to use manual settings or automatic ones. + settings : ::class::modules.MeshIcepak.MeshSettings + Dictionary-like object to handle settings + """ + + def __init__(self, units, app, name): + self.manual_settings = False + self.settings = MeshSettings(self, app) + self._name = name + self._model_units = units + self._app = app + + @abstractmethod + def update(self): + """ + Update the mesh region object. + """ + + @abstractmethod + def delete(self): + """ + Delete the mesh region object. + """ + + @abstractmethod + def create(self): + """ + Create the mesh region object. + """ + + # backward compatibility + def __getattr__(self, name): + if "settings" in self.__dict__ and name in self.__dict__["settings"]: + return self.__dict__["settings"][name] + elif name == "UserSpecifiedSettings": + return self.__dict__["manual_settings"] + else: + return self.__dict__[name] + + def __setattr__(self, name, value): + if "settings" in self.__dict__ and name in self.settings: + self.settings[name] = value + elif name == "UserSpecifiedSettings": + self.__dict__["manual_settings"] = value + else: + super(MeshRegionCommon, self).__setattr__(name, value) + + +class GlobalMeshRegion(MeshRegionCommon): + def __init__(self, app): + self.global_region = Region(app) + super(GlobalMeshRegion, self).__init__( + app.modeler.model_units, + app, + name="Settings", + ) + + @property + def name(self): + """ + Mesh region name. + """ + return "Global" + + @pyaedt_function_handler + def update(self): + """Update mesh region settings with the settings in the object variable. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + References + ---------- + + >>> oModule.EditGlobalMeshRegion + """ + args = ["NAME:Settings"] + args += self.settings.parse_settings() + try: + self._app.omeshmodule.EditGlobalMeshRegion(args) + return True + except GrpcApiError: # pragma : no cover + return False + + @property + def Objects(self): + """ + Get the region object from the modeler. + """ + return self.global_region.name + + def delete(self): + """ + Delete the region object in the modeler. + """ + self.global_region.object.delete() + self.global_region = None + + def create(self): + """ + Create the region object in the modeler. + """ + self.delete() + self.global_region = Region(self._app) + self.global_region.create(self.padding_types, self.padding_values) + + +class MeshRegion(MeshRegionCommon): + def __init__(self, app, objects=None, name=None, **kwargs): + if name is None: + name = generate_unique_name("MeshRegion") + super(MeshRegion, self).__init__( + app.modeler.model_units, + app, + name, + ) + self.enable = True + if settings.aedt_version > "2023.2" and objects is not None: + if not isinstance(objects, list): + objects = [objects] + if ( + objects[0] not in self._app.modeler.user_defined_components + and self._app.modeler[objects[0]].history().command == "CreateSubRegion" + ): + self._assignment = objects[0] + else: + self._assignment = SubRegion(app, objects) + else: + self._assignment = objects + if self._assignment is not None: + self.create() + # backward compatibility + if any(i in kwargs for i in ["dimension", "meshmodule", "unit"]): + warnings.warn( + "``MeshRegion`` initialization changed. ``meshmodule``, ``dimension``, ``unit`` " + "arguments are not supported anymore.", + DeprecationWarning, + ) + if "dimension" in kwargs: + self.manual_settings = True + self.settings["MaxElementSizeX"] = float(kwargs["dimension"][0]) / 20 + self.settings["MaxElementSizeY"] = float(kwargs["dimension"][1]) / 20 + self.settings["MaxElementSizeZ"] = float(kwargs["dimension"][2]) / 20 + + def _parse_assignment_value(self, assignment=None): + if assignment is None: + assignment = self.assignment + a = [] + if isinstance(assignment, SubRegion): + a += ["Objects:=", [assignment.name]] + else: + if any(o in self._app.modeler.object_names for o in assignment): + obj_assignment = [o for o in assignment if o in self._app.modeler.object_names] + a += ["Objects:=", obj_assignment] + if any(o in self._app.modeler.user_defined_components for o in assignment): + obj_assignment = [o for o in assignment if o in self._app.modeler.user_defined_components] + a += ["Submodels:=", obj_assignment] + return a + + @property + def name(self): + """ + Name of the mesh region. + + Returns + ------- + str + """ + return self._name + + @name.setter + def name(self, value): + self._app.odesign.ChangeProperty( + [ + "NAME:AllTabs", + [ + "NAME:Icepak", + ["NAME:PropServers", "MeshRegion:{}".format(self.name)], + ["NAME:ChangedProps", ["NAME:Name", "Value:=", value]], + ], + ] + ) + self._app.modeler.refresh() + self._name = value + if isinstance(self.assignment, SubRegion): + self._assignment = self.assignment + + @pyaedt_function_handler + def update(self): + """Update mesh region settings with the settings in the object variable. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + References + ---------- + + >>> oModule.EditMeshRegion + """ + args = ["NAME:" + self.name, "Enable:=", self.enable] + args += self.settings.parse_settings() + args += self._parse_assignment_value() + args += ["UserSpecifiedSettings:=", not self.manual_settings] + try: + self._app.omeshmodule.EditMeshRegion(self.name, args) + return True + except GrpcApiError: # pragma : no cover + return False + + @pyaedt_function_handler() + def delete(self): + """Delete the mesh region. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + References + ---------- + + >>> oModule.DeleteMeshRegions() + """ + self._app.omeshmodule.DeleteMeshRegions([self.name]) + self._app.mesh.meshregions.remove(self) + return True + + @property + def assignment(self): + """ + List of objects included in mesh region. + + Returns + ------- + list + """ + if isinstance(self._assignment, SubRegion): + # try to update name + try: + parts = self._app.odesign.GetChildObject("Mesh").GetChildObject(self.name).GetPropValue("Parts") + if not isinstance(parts, list): + parts = [parts] + sub_regions = self._app.modeler.non_model_objects + for sr in sub_regions: + p1 = [] + p2 = [] + if "Part Names" in self._app.modeler[sr].history().props: + p1 = self._app.modeler[sr].history().props.get("Part Names", None) + if not isinstance(p1, list): + p1 = [p1] + elif "Submodel Names" in self._app.modeler[sr].history().props: + p2 = self._app.modeler[sr].history().props.get("Submodel Names", None) + if not isinstance(p2, list): + p2 = [p2] + p1 += p2 + if "CreateSubRegion" == self._app.modeler[sr].history().command and all(p in p1 for p in parts): + self._assignment.name = sr + except GrpcApiError: + pass + return self._assignment + elif isinstance(self._assignment, list): + return self._assignment + else: + return [self._assignment] + + @assignment.setter + def assignment(self, value): + arg = ["NAME:Assignment"] + self._parse_assignment_value(value) + try: + self._app.omeshmodule.ReassignMeshRegion(self.name, arg) + self._assignment = value + except GrpcApiError: # pragma : no cover + self._app.logger.error("Mesh region reassignment failed.") + + @pyaedt_function_handler() + def create(self): + """Create a mesh region. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + References + ---------- + + >>> oModule.AssignMeshRegion + """ + if self.name == "Settings": + self._app.logger.error("Cannot create a new mesh region with this Name") + return False + args = ["NAME:" + self.name, "Enable:=", self.enable] + args += self.settings.parse_settings() + args += ["UserSpecifiedSettings:=", not self.manual_settings] + args += self._parse_assignment_value() + self._app.omeshmodule.AssignMeshRegion(args) + self._app.mesh.meshregions.append(self) + self._app.modeler.refresh_all_ids() + self._assignment = self.assignment + return True + + # backward compatibility + @property + def Enable(self): + """ + Get whether the mesh region is enabled. + + Returns + ------- + book + """ + warnings.warn( + "`Enable` is deprecated. Use `enable` instead.", + DeprecationWarning, + ) + return self.enable + + @Enable.setter + def Enable(self, val): + warnings.warn( + "`Enable` is deprecated. Use `enable` instead.", + DeprecationWarning, + ) + self.enable = val + + @property + def Objects(self): + """ + List of objects included in mesh region. + + Returns + ------- + list + """ + warnings.warn( + "`Objects` is deprecated. Use `assignment` instead.", + DeprecationWarning, + ) + return self.assignment + + @Objects.setter + def Objects(self, objects): + warnings.warn( + "`Objects` is deprecated. Use `assignment` instead.", + DeprecationWarning, + ) + self.assignment = objects + + @property + def Submodels(self): + """ + List of objects included in mesh region. + + Returns + ------- + list + """ + warnings.warn( + "`Submodels` is deprecated. Use `assignment` instead.", + DeprecationWarning, + ) + return self.assignment + + @Submodels.setter + def Submodels(self, objects): + warnings.warn( + "`Submodels` is deprecated. Use `assignment` instead.", + DeprecationWarning, + ) + self.assignment = objects + + class IcepakMesh(object): """Manages Icepak meshes. @@ -25,11 +934,12 @@ def __init__(self, app): self.id = 0 self._oeditor = self.modeler.oeditor self._model_units = self.modeler.model_units - self.global_mesh_region = self.MeshRegion( - self.omeshmodule, self.boundingdimension, self._model_units, self._app - ) self.meshoperations = self._get_design_mesh_operations() self.meshregions = self._get_design_mesh_regions() + try: + self.global_mesh_region = [mo for mo in self.meshregions if isinstance(mo, GlobalMeshRegion)][0] + except IndexError: + self.global_mesh_region = GlobalMeshRegion(app) self._priorities_args = [] @property @@ -62,372 +972,6 @@ def omeshmodule(self): """ return self._app.omeshmodule - class MeshRegion(object): - """ - Manages Icepak mesh region settings. - - Attributes: - name : str - Name of the mesh region. - UserSpecifiedSettings : bool - Whether to use manual settings. Default is ``False``. - ComputeGap : bool - Whether to enable minimum gap override. Default is ``True``. - Level : int - Automatic mesh detail level. Default is 3. - MaxElementSizeX : str - Maximum element size along the X-axis. Default is 1/20 of the region - X-dimension. - MaxElementSizeY : str - Maximum element size along the Y-axis. Default is 1/20 of the region - Y-dimension. - MaxElementSizeZ : str - Maximum element size along the Z-axis. Default is 1/20 of the region - Z-dimension. - MinElementsInGap : str - Minimum number of elements in gaps between adjacent objects. Default is "3". - MinElementsOnEdge : str - Minimum number of elements on each edge of each object. Default is "2". - MaxSizeRatio : str - Maximum ratio of the sizes of adjacent elements. Default is "2". - NoOGrids : bool - Whether objects will have O-grids around them. Default is ``False``. - EnableMLM : bool - Enable Multi-Level Mesh (MLM). Default is ``True``. - EnforceMLMType : str - Type of MLM to use, ``"2D"`` or ``"3D"``. Default is ``"3D"``. - MaxLevels : str - Maximum number of refinement level for Multi-Level Mesh. Default is ``"0"``. - BufferLayers : str - Number of buffer layers between refinement level. Default is ``"0"``. - UniformMeshParametersType : str - Whether to create a creates a uniform mesh with the same mesh size in all - coordinate directions (``"Average"``) or different spacing in each - direction (``"XYZ Max Sizes"``). Default is ``"Average"``. - StairStepMeshing : bool - Whether to disable vertices projection step used to obtain conformal mesh. - Default is ``False``. - DMLMType : str - If ``EnforceMLMType`` is ``"2D"``, in which 2D plane mesh refinement is - constrained. Available options are ``"2DMLM_None"``, ``"2DMLM_YZ"``, - ``"2DMLM_XZ"`` or ``"2DMLM_XY"``. Default is ``"2DMLM_None"`` - which means ``Auto``. - MinGapX : str - Minimum gap size along the X-axis. Default is ``"1"``. - MinGapY : str - Minimum gap size along the Y-axis. Default is ``"1"``. - MinGapZ : str - Minimum gap size along the Z-axis. Default is ``"1"``. - Objects : list - Objects to which meshing settings are applied. - SubModels : bool - SubModels to which meshing settings are applied. - Default is ``False``, so ``Objects`` attribute will be used. - Enable : bool - Enable mesh region. Default is ``True``. - ProximitySizeFunction : bool - Whether to use proximity-based size function. Default is ``True``. - CurvatureSizeFunction : bool - Whether to use curvature-based size function. Default is ``True``. - EnableTransition : bool - Whether to enable mesh transition. Default is ``False``. - OptimizePCBMesh : bool - Whether to optimize PCB mesh. Default is ``True``. - Enable2DCutCell : bool - Whether to enable 2D cut cell meshing. Default is ``False``. - EnforceCutCellMeshing : bool - Whether to enforce cut cell meshing. Default is ``False``. - Enforce2dot5DCutCell : bool - Whether to enforce 2.5D cut cell meshing. Default is ``False``. - SlackMinX : str - Slack along the negative X-axis. Default is ``"0mm"``. - SlackMinY : str - Slack along the negative Y-axis. Default is ``"0mm"``. - SlackMinZ : str - Slack along the negative Z-axis. Default is ``"0mm"``. - SlackMaxX : str - Slack along the positive X-axis. Default is ``"0mm"``. - SlackMaxY : str - Slack along the positive Y-axis. Default is ``"0mm"``. - SlackMaxZ : str - Slack along the positive Z-axis. Default is ``"0mm"``. - CoordCS : str - Coordinate system of the mesh region. Default is ``"Global"``. - virtual_region: bool - Whether to use virtual region. In order to use it, Icepak version must be 22R2 or - newer and the corresponding beta feature must be enabled. - """ - - def __init__(self, meshmodule, dimension, units, app, name=None): - if name is None: - name = "Settings" - self.name = name - self.meshmodule = meshmodule - self.model_units = units - self.UserSpecifiedSettings = False - self.ComputeGap = True - self.Level = 3 - self.MaxElementSizeX = str(float(dimension[0]) / 20) - self.MaxElementSizeY = str(float(dimension[1]) / 20) - self.MaxElementSizeZ = str(float(dimension[2]) / 20) - self.MinElementsInGap = "3" - self.MinElementsOnEdge = "2" - self.MaxSizeRatio = "2" - self.NoOGrids = False - self.EnableMLM = True - self.EnforeMLMType = "3D" - self.MaxLevels = "0" - self.BufferLayers = "0" - self.UniformMeshParametersType = "Average" - self.StairStepMeshing = False - self.DMLMType = "2DMLM_None" - self.MinGapX = "1" - self.MinGapY = "1" - self.MinGapZ = "1" - self.Objects = ["Region"] - self.SubModels = False - self.Enable = True - self.ProximitySizeFunction = True - self.CurvatureSizeFunction = True - self.EnableTransition = False - self.OptimizePCBMesh = True - self.Enable2DCutCell = False - self.EnforceCutCellMeshing = False - self.Enforce2dot5DCutCell = False - self.SlackMinX = "0mm" - self.SlackMinY = "0mm" - self.SlackMinZ = "0mm" - self.SlackMaxX = "0mm" - self.SlackMaxY = "0mm" - self.SlackMaxZ = "0mm" - self.CoordCS = "Global" - self.virtual_region = False - self._app = app - - @pyaedt_function_handler() - def _dim_arg(self, value): - from pyaedt.generic.general_methods import _dim_arg - - return _dim_arg(value, self.model_units) - - @property - def _new_versions_fields(self): - arg = [] - if settings.aedt_version > "2021.2": - arg = [ - "ProximitySizeFunction:=", - self.ProximitySizeFunction, - "CurvatureSizeFunction:=", - self.CurvatureSizeFunction, - "EnableTransition:=", - self.EnableTransition, - "OptimizePCBMesh:=", - self.OptimizePCBMesh, - "Enable2DCutCell:=", - self.Enable2DCutCell, - "EnforceCutCellMeshing:=", - self.EnforceCutCellMeshing, - "Enforce2dot5DCutCell:=", - self.Enforce2dot5DCutCell, - ] - if self.virtual_region: - if self._app.check_beta_option_enabled("S544753_ICEPAK_VIRTUALMESHREGION_PARADIGM"): - arg.extend( - [ - "SlackMinX:=", - self.SlackMinX, - "SlackMinY:=", - self.SlackMinY, - "SlackMinZ:=", - self.SlackMinZ, - "SlackMaxX:=", - self.SlackMaxX, - "SlackMaxY:=", - self.SlackMaxY, - "SlackMaxZ:=", - self.SlackMaxZ, - "CoordCS:=", - self.CoordCS, - ] - ) - else: - if settings.aedt_version < "2022.2": - self._app.logger.warning("Virtual Mesh Region feature is not available in this version.") - else: - self._app.logger.warning("Virtual Mesh Region beta feature is not enabled.") - - return arg - - @property - def autosettings(self): - """Automatic mesh settings.""" - arg = [ - "MeshMethod:=", - "MesherHD", - "UserSpecifiedSettings:=", - self.UserSpecifiedSettings, - "ComputeGap:=", - self.ComputeGap, - "MeshRegionResolution:=", - self.Level, - "MinGapX:=", - self._dim_arg(self.MinGapX), - "MinGapY:=", - self._dim_arg(self.MinGapY), - "MinGapZ:=", - self._dim_arg(self.MinGapZ), - ] - if self.SubModels: - arg.append("SubModels:=") - arg.append(self.SubModels) - if self.Objects: - arg.append("Objects:=") - arg.append(self.Objects) - arg.extend(self._new_versions_fields) - return arg - - @property - def manualsettings(self): - """Manual mesh settings.""" - - arg = [ - "MeshMethod:=", - "MesherHD", - "UserSpecifiedSettings:=", - self.UserSpecifiedSettings, - "ComputeGap:=", - self.ComputeGap, - "MaxElementSizeX:=", - self._dim_arg(self.MaxElementSizeX), - "MaxElementSizeY:=", - self._dim_arg(self.MaxElementSizeY), - "MaxElementSizeZ:=", - self._dim_arg(self.MaxElementSizeZ), - "MinElementsInGap:=", - self.MinElementsInGap, - "MinElementsOnEdge:=", - self.MinElementsOnEdge, - "MaxSizeRatio:=", - self.MaxSizeRatio, - "NoOGrids:=", - self.NoOGrids, - "EnableMLM:=", - self.EnableMLM, - "EnforeMLMType:=", - self.EnforeMLMType, - "MaxLevels:=", - self.MaxLevels, - "BufferLayers:=", - self.BufferLayers, - "UniformMeshParametersType:=", - self.UniformMeshParametersType, - "StairStepMeshing:=", - self.StairStepMeshing, - "2DMLMType:=", - self.DMLMType, - "MinGapX:=", - self._dim_arg(self.MinGapX), - "MinGapY:=", - self._dim_arg(self.MinGapY), - "MinGapZ:=", - self._dim_arg(self.MinGapZ), - ] - if self.SubModels: - arg.append("SubModels:=") - arg.append(self.SubModels) - if self.Objects: - arg.append("Objects:=") - arg.append(self.Objects) - arg.extend(self._new_versions_fields) - return arg - - @property - def _odesign(self): - """Instance of a design in a project.""" - return self._app._odesign - - @pyaedt_function_handler() - def update(self): - """Update mesh region settings with the settings in the object variable. - - Returns - ------- - bool - ``True`` when successful, ``False`` when failed. - - References - ---------- - - >>> oModule.EditGlobalMeshRegion - >>> oModule.EditMeshRegion - """ - if self.name == "Settings": - args = ["NAME:Settings"] - else: - args = ["NAME:" + self.name, "Enable:=", self.Enable] - if self.UserSpecifiedSettings: - args += self.manualsettings - else: - args += self.autosettings - if self.name == "Settings": - try: - self.meshmodule.EditGlobalMeshRegion(args) - return True - except Exception: # pragma : no cover - return False - else: - try: - self.meshmodule.EditMeshRegion(self.name, args) - return True - except Exception: # pragma : no cover - return False - - @pyaedt_function_handler() - def create(self): - """Create a new mesh region. - - Returns - ------- - bool - ``True`` when successful, ``False`` when failed. - - References - ---------- - - >>> oModule.AssignMeshRegion - >>> oModule.AssignVirtualMeshRegion - """ - assert self.name != "Settings", "Cannot create a new mesh region with this Name" - args = ["NAME:" + self.name, "Enable:=", self.Enable] - if self.UserSpecifiedSettings: - args += self.manualsettings - else: - args += self.autosettings - if self.virtual_region and self._app.check_beta_option_enabled("S544753_ICEPAK_VIRTUALMESHREGION_PARADIGM"): - self.meshmodule.AssignVirtualMeshRegion(args) - else: - self.meshmodule.AssignMeshRegion(args) - self._app.mesh.meshregions.append(self) - self._app.modeler.refresh_all_ids() - return True - - @pyaedt_function_handler() - def delete(self): - """Delete mesh region. - - Returns - ------- - bool - ``True`` when successful, ``False`` when failed. - - References - ---------- - - >>> oModule.DeleteMeshRegions() - """ - self.meshmodule.DeleteMeshRegions([self.name]) - return True - @property def boundingdimension(self): """Bounding dimension.""" @@ -464,8 +1008,8 @@ def _get_design_mesh_operations(self): "Icepak", ) ) - except: - pass + except Exception as e: + self._app.logger.error(e) return meshops @pyaedt_function_handler() @@ -479,10 +1023,9 @@ def _get_design_mesh_regions(self): if self._app.design_properties["MeshRegion"]["MeshSetup"][ds]["DType"] == "RegionT": dict_prop = self._app.design_properties["MeshRegion"]["MeshSetup"][ds] if ds == "Global": - ds = "Settings" - meshop = self.MeshRegion( - self.omeshmodule, self.boundingdimension, self.modeler.model_units, self._app, ds - ) + meshop = GlobalMeshRegion(self._app) + else: + meshop = MeshRegion(self._app, None, ds) for el in dict_prop: if el in meshop.__dict__: meshop.__dict__[el] = dict_prop[el] @@ -494,16 +1037,15 @@ def _get_design_mesh_regions(self): ): dict_prop = self._app.design_properties["MeshRegion"]["MeshSetup"]["MeshRegions"][ds] if ds == "Global": - ds = "Settings" - meshop = self.MeshRegion( - self.omeshmodule, self.boundingdimension, self.modeler.model_units, self._app, ds - ) + meshop = GlobalMeshRegion(self._app) + else: + meshop = MeshRegion(self._app, None, ds) for el in dict_prop: if el in meshop.__dict__: meshop.__dict__[el] = dict_prop[el] meshops.append(meshop) - except: - pass + except Exception as e: + self._app.logger.error(e) return meshops @pyaedt_function_handler() @@ -594,6 +1136,8 @@ def assign_mesh_from_file(self, objects, filename, meshop_name=None): def automatic_mesh_pcb(self, accuracy=2): """Create a custom mesh tailored on a PCB design. + .. deprecated:: 0.8.14 + Parameters ---------- accuracy : int, optional @@ -610,6 +1154,7 @@ def automatic_mesh_pcb(self, accuracy=2): >>> oModule.EditMeshOperation """ + warnings.warn("This method was deprecated in version 8.14.", DeprecationWarning) xsize = self.boundingdimension[0] / (15 * accuracy * accuracy) ysize = self.boundingdimension[1] / (15 * accuracy * accuracy) zsize = self.boundingdimension[2] / (10 * accuracy) @@ -708,7 +1253,6 @@ def add_priority(self, entity_type, obj_list=None, comp_name=None, priority=3): for comp in obj_list: if comp != "Region" and comp in non_user_defined_component_parts: new_obj_list.append(comp) - objects = ", ".join(new_obj_list) if not new_obj_list: return False @@ -787,23 +1331,19 @@ def add_priority(self, entity_type, obj_list=None, comp_name=None, priority=3): return True @pyaedt_function_handler() - def assign_mesh_region(self, objectlist=[], level=5, is_submodel=False, name=None, virtual_region=False): + def assign_mesh_region(self, objectlist=None, level=5, name=None, **kwargs): """Assign a predefined surface mesh level to an object. Parameters ---------- objectlist : list, optional List of objects to apply the mesh region to. The default - is ``[]``. + is ``None``, in which case all objects are selected. level : int, optional Level of the surface mesh. Options are ``1`` through ``5``. The default is ``5``. - is_submodel : bool - Define if the object list is made by component models name : str, optional Name of the mesh region. The default is ``"MeshRegion1"``. - virtual_region : bool, optional - Whether to use the virtual mesh region beta feature (available from version 22.2). The default is ``False``. Returns ------- @@ -816,42 +1356,22 @@ def assign_mesh_region(self, objectlist=[], level=5, is_submodel=False, name=Non """ if not name: name = generate_unique_name("MeshRegion") - meshregion = self.MeshRegion(self.omeshmodule, self.boundingdimension, self.modeler.model_units, self._app) - meshregion.UserSpecifiedSettings = False - meshregion.Level = level - meshregion.name = name - meshregion.virtual_region = virtual_region - if not objectlist: + if objectlist is None: objectlist = [i for i in self.modeler.object_names] - if is_submodel: - meshregion.SubModels = objectlist - else: - meshregion.Objects = objectlist + meshregion = MeshRegion(self._app, objectlist, name) + meshregion.manual_settings = False + meshregion.Level = level all_objs = [i for i in self.modeler.object_names] - try: - meshregion.create() - created = True - except Exception: # pragma : no cover - created = False + created = bool(meshregion) if created: - if virtual_region and self._app.check_beta_option_enabled( - "S544753_ICEPAK_VIRTUALMESHREGION_PARADIGM" - ): # pragma : no cover - if is_submodel: - meshregion.Objects = [i for i in objectlist if i in all_objs] - meshregion.SubModels = [i for i in objectlist if i not in all_objs] - else: - meshregion.Objects = objectlist - meshregion.SubModels = None - else: + if settings.aedt_version < "2024.1": objectlist2 = self.modeler.object_names added_obj = [i for i in objectlist2 if i not in all_objs] if not added_obj: added_obj = [i for i in objectlist2 if i not in all_objs or i in objectlist] meshregion.Objects = added_obj meshregion.SubModels = None - - meshregion.update() + meshregion.update() return meshregion else: return False diff --git a/pyaedt/modules/PostProcessor.py b/pyaedt/modules/PostProcessor.py index 892304de2c7..949bc22a783 100644 --- a/pyaedt/modules/PostProcessor.py +++ b/pyaedt/modules/PostProcessor.py @@ -5055,23 +5055,99 @@ def __init__(self, app): @pyaedt_function_handler() def add_calculation( - self, entity, geometry, geometry_name, quantity, normal="", side="Default", mesh="All", ref_temperature="" + self, + entity, + geometry, + geometry_name, + quantity, + normal="", + side="Default", + mesh="All", + ref_temperature="AmbientTemp", ): + """ + Add an entry in the field summary calculation requests. + + Parameters + ---------- + entity : str + Type of entity to perform the calculation on. Options are + ``"Boundary"``, ``"Monitor``", and ``"Object"``. + (``"Monitor"`` is available in AEDT 2024 R1 and later.) + geometry : str + Location to perform the calculation on. Options are + ``"Surface"`` and ``"Volume"``. + geometry_name : str or list of str + Objects to perform the calculation on. If a list is provided, + the calculation is performed on the combination of those + objects. + quantity : str + Quantity to compute. + normal : list of floats + Coordinate values for direction relative to normal. The default is ``""``, + in which case the normal to the face is used. + side : str, optional + String containing which side of the face to use. The default is + ``"Default"``. Options are ``"Adjacent"``, ``"Combined"``, and + `"Default"``. + mesh : str, optional + Surface meshes to use. The default is ``"All"``. Options are ``"All"`` and + ``"Reduced"``. + ref_temperature : str, optional + Reference temperature to use in the calculation of the heat transfer + coefficient. The default is ``"AmbientTemp"``. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + """ if quantity not in AVAILABLE_QUANTITIES: raise AttributeError( "Quantity {} is not supported. Available quantities are:\n{}".format( quantity, ", ".join(AVAILABLE_QUANTITIES) ) ) + if isinstance(normal, list): + if not isinstance(normal[0], str): + normal = [str(i) for i in normal] + normal = ",".join(normal) + if isinstance(geometry_name, str): + geometry_name = [geometry_name] self.calculations.append( - [entity, geometry, geometry_name, quantity, normal, side, mesh, ref_temperature, False] + [entity, geometry, ",".join(geometry_name), quantity, normal, side, mesh, ref_temperature, False] ) # TODO : last argument not documented + return True @pyaedt_function_handler() - def get_field_summary_data(self, sweep_name=None, design_variation={}, intrinsic_value="", pandas_output=False): + def get_field_summary_data(self, setup_name=None, design_variation={}, intrinsic_value="", pandas_output=False): + """ + Get field summary output computation. + + Parameters + ---------- + setup_name : str, optional + Setup name to use for the computation. The + default is ``None``, in which case the nominal variation is used. + design_variation : dict, optional + Dictionary containing the design variation to use for the computation. + The default is ``{}``, in which case nominal variation is used. + intrinsic_value : str, optional + Intrinsic values to use for the computation. The default is ``""``, + suitable when no frequency needs to be selected. + pandas_output : bool, optional + Whether to use pandas output. The default is ``False``, in + which case the dictionary output is used. + + Returns + ------- + dict or pandas.DataFrame + Output type depending on the Boolean ``pandas_output`` parameter. + The output consists of information exported from the field summary. + """ with tempfile.NamedTemporaryFile(mode="w+", delete=False) as temp_file: temp_file.close() - self.export_csv(temp_file.name, sweep_name, design_variation, intrinsic_value) + self.export_csv(temp_file.name, setup_name, design_variation, intrinsic_value) with open(temp_file.name, "r") as f: for _ in range(4): _ = next(f) @@ -5088,17 +5164,39 @@ def get_field_summary_data(self, sweep_name=None, design_variation={}, intrinsic return out_dict @pyaedt_function_handler() - def export_csv(self, filename, sweep_name=None, design_variation={}, intrinsic_value=""): - if not sweep_name: - sweep_name = self._app.nominal_sweep + def export_csv(self, filename, setup_name=None, design_variation={}, intrinsic_value=""): + """ + Get the field summary output computation. + + Parameters + ---------- + filename : str + Path and filename to write the output file to. + setup_name : str, optional + Setup name to use for the computation. The + default is ``None``, in which case the nominal variation is used. + design_variation : dict, optional + Dictionary containing the design variation to use for the computation. + The default is ``{}``, in which case the nominal variation is used. + intrinsic_value : str, optional + Intrinsic values to use for the computation. The default is ``""``, + suitable when no frequency needs to be selected. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + """ + if not setup_name: + setup_name = self._app.nominal_sweep dv_string = "" for el in design_variation: dv_string += el + "='" + design_variation[el] + "' " - self._create_field_summary(sweep_name, dv_string) + self._create_field_summary(setup_name, dv_string) self._app.osolution.ExportFieldsSummary( [ "SolutionName:=", - sweep_name, + setup_name, "DesignVariationKey:=", dv_string, "ExportFileName:=",