diff --git a/codegen/allapigen.py b/codegen/allapigen.py index 2a92ccb4e865..5d0018de82b9 100644 --- a/codegen/allapigen.py +++ b/codegen/allapigen.py @@ -17,6 +17,7 @@ gt_222 = FluentVersion(version) > FluentVersion.v222 ge_231 = FluentVersion(version) >= FluentVersion.v231 ge_242 = FluentVersion(version) >= FluentVersion.v242 + ge_261 = FluentVersion(version) >= FluentVersion.v261 static_infos = { StaticInfoType.DATAMODEL_WORKFLOW: meshing._datamodel_service_se.get_static_info( @@ -40,6 +41,10 @@ static_infos[StaticInfoType.DATAMODEL_MESHING_UTILITIES] = ( meshing._datamodel_service_se.get_static_info("MeshingUtilities") ) + if ge_261: + static_infos[StaticInfoType.DATAMODEL_MESHING_WORKFLOW] = ( + meshing._datamodel_service_se.get_static_info("meshing_workflow") + ) meshing.exit() solver = launch_fluent( diff --git a/doc/changelog.d/3748.added.md b/doc/changelog.d/3748.added.md new file mode 100644 index 000000000000..cdee1b3b429c --- /dev/null +++ b/doc/changelog.d/3748.added.md @@ -0,0 +1 @@ +Updates for the client side dm-api. diff --git a/doc/changelog.d/4476.added.md b/doc/changelog.d/4476.added.md new file mode 100644 index 000000000000..1c372141119c --- /dev/null +++ b/doc/changelog.d/4476.added.md @@ -0,0 +1 @@ +Create UI for meshing workflow. diff --git a/doc/datamodel_rstgen.py b/doc/datamodel_rstgen.py index d06759587c8a..dd38d431f1b8 100644 --- a/doc/datamodel_rstgen.py +++ b/doc/datamodel_rstgen.py @@ -16,6 +16,7 @@ def generate_meshing_datamodels(): "pm_file_management", "preferences", "workflow", + "meshing_workflow", ] for meshing_datamodel in meshing_datamodels: try: diff --git a/src/ansys/fluent/core/codegen/__init__.py b/src/ansys/fluent/core/codegen/__init__.py index 6e188f5ca259..55e1949c9f6e 100644 --- a/src/ansys/fluent/core/codegen/__init__.py +++ b/src/ansys/fluent/core/codegen/__init__.py @@ -32,6 +32,7 @@ class StaticInfoType(Enum): TUI_SOLVER = auto() TUI_MESHING = auto() DATAMODEL_WORKFLOW = auto() + DATAMODEL_MESHING_WORKFLOW = auto() DATAMODEL_MESHING = auto() DATAMODEL_PART_MANAGEMENT = auto() DATAMODEL_PM_FILE_MANAGEMENT = auto() diff --git a/src/ansys/fluent/core/codegen/datamodelgen.py b/src/ansys/fluent/core/codegen/datamodelgen.py index d7327ffed2e9..2b90a9c12aa2 100644 --- a/src/ansys/fluent/core/codegen/datamodelgen.py +++ b/src/ansys/fluent/core/codegen/datamodelgen.py @@ -188,6 +188,7 @@ def _build_command_query_docstring( "MeshingUtilities": "meshing_utilities", "flicing": "flicing", "solverworkflow": "solver_workflow", + "meshing_workflow": "meshing_workflow", } @@ -242,6 +243,13 @@ def __init__(self, version, static_infos: dict, verbose: bool = False): ), self.version, ) + if StaticInfoType.DATAMODEL_MESHING_WORKFLOW in static_infos: + self._static_info["meshing_workflow"] = DataModelStaticInfo( + StaticInfoType.DATAMODEL_MESHING_WORKFLOW, + "meshing_workflow", + ("meshing",), + self.version, + ) if StaticInfoType.DATAMODEL_MESHING in static_infos: self._static_info["meshing"] = DataModelStaticInfo( StaticInfoType.DATAMODEL_MESHING, "meshing", ("meshing",), self.version @@ -623,6 +631,10 @@ def generate(version, static_infos: dict, verbose: bool = False): static_infos[StaticInfoType.DATAMODEL_MESHING_UTILITIES] = ( meshing._datamodel_service_se.get_static_info("MeshingUtilities") ) + if FluentVersion(version) >= FluentVersion.v261: + static_infos[StaticInfoType.DATAMODEL_MESHING_WORKFLOW] = ( + meshing._datamodel_service_se.get_static_info("meshing_workflow") + ) parser = argparse.ArgumentParser( description="A script to write Fluent API files with an optional verbose output." ) diff --git a/src/ansys/fluent/core/session_base_meshing.py b/src/ansys/fluent/core/session_base_meshing.py index fe9c87ed6643..cbf6776ef8b2 100644 --- a/src/ansys/fluent/core/session_base_meshing.py +++ b/src/ansys/fluent/core/session_base_meshing.py @@ -72,6 +72,7 @@ def __init__( self._fluent_version = fluent_version self._meshing_utilities = None self._old_workflow = None + self._meshing_workflow = None self._part_management = None self._pm_file_management = None self._preferences = None @@ -127,6 +128,13 @@ def workflow(self): self._old_workflow = _make_datamodel_module(self, "workflow") return self._old_workflow + @property + def meshing_workflow(self): + """Full API to meshing and meshing_workflow.""" + if self._meshing_workflow is None: + self._meshing_workflow = _make_datamodel_module(self, "meshing_workflow") + return self._meshing_workflow + def watertight_workflow(self, initialize: bool = True): """Datamodel root of workflow.""" self._current_workflow = WorkflowMode.WATERTIGHT_MESHING_MODE.value( @@ -248,3 +256,81 @@ def preferences(self): if self._preferences is None: self._preferences = _make_datamodel_module(self, "preferences") return self._preferences + + +# class WorkflowWrapper: +# def __init__(self, workflow): +# self.workflow = workflow +# +# def __getattr__(self, name): +# try: +# return getattr(self.workflow, name) +# except AttributeError as ex: +# _task_object = self.workflow.task_object.get(name) +# if _task_object: +# return TaskWrapper(_task_object) +# else: +# raise ex +# +# +# class TaskWrapper: +# def __init__(self, task): +# self.task = task +# +# def __getattr__(self, name): +# try: +# return getattr(self.task, name) +# except AttributeError as ex: +# if name in self.task.arguments(): +# return ArgumentWrapper(self.task, name, self.task.arguments().get(name)) +# else: +# raise ex +# +# def __call__(self, *args, **kwargs): +# self.task.execute() +# +# +# class ArgumentWrapper: +# def __init__(self, task, arg_name, arg_state): +# self.__dict__.update( +# dict( +# tash=task, +# arg_name=arg_name, +# arg_state=arg_state, +# ) +# ) +# +# def __getattr__(self, name): +# # try: +# # return getattr(self.task.arguments()[self.arg_name], name) +# # except AttributeError as ex: +# try: +# if type(self.arg_state) is dict: +# return ArgumentWrapper(self.task, name, self.arg_state[name]) +# else: +# return self.arg_state[name] +# except KeyError: +# raise AttributeError(f"{self.arg_name} has no object named {name}.") +# +# def __setattr__(self, name, value): +# try: +# # if type(self.arg_state) is dict: +# # ArgumentWrapper(self.task, self.arg_state[name]) +# # else: +# self.task.arguments.update_dict(dict(name=value)) +# except KeyError as ex: +# raise AttributeError(f"No object named {name}.") from ex +# +# def __call__(self, *args, **kwargs): +# return self.arg_state +# +# +# class SubArgumentWrapper: +# def __init__(self, arg_state): +# self.arg_state = arg_state +# +# def __getattr__(self, name): +# if type(self.arg_state[name]) is dict: +# return SubArgumentWrapper(self.arg_state[name]) +# else: +# return self.arg_state[name] diff --git a/src/ansys/fluent/core/session_meshing.py b/src/ansys/fluent/core/session_meshing.py index 901d5a149a15..12c6fd4a05d7 100644 --- a/src/ansys/fluent/core/session_meshing.py +++ b/src/ansys/fluent/core/session_meshing.py @@ -127,6 +127,11 @@ def workflow(self): """Workflow datamodel root.""" return super(Meshing, self).workflow + @property + def meshing_workflow(self): + """Full API to meshing and meshing_workflow.""" + return super(Meshing, self).meshing_workflow + @property def PartManagement(self): """Part management datamodel root.""" diff --git a/src/ansys/fluent/core/session_pure_meshing.py b/src/ansys/fluent/core/session_pure_meshing.py index fd4c9b9e249e..bfe5fcf94b3a 100644 --- a/src/ansys/fluent/core/session_pure_meshing.py +++ b/src/ansys/fluent/core/session_pure_meshing.py @@ -50,6 +50,7 @@ class PureMeshing(BaseSession): _rules = [ "workflow", + "meshing_workflow", "meshing", "MeshingUtilities", "PartManagement", @@ -146,6 +147,11 @@ def workflow(self): """Datamodel root of workflow.""" return self._base_meshing.workflow + @property + def meshing_workflow(self): + """Full API to meshing and meshing_workflow.""" + return self._base_meshing.meshing_workflow + def watertight(self): """Get a new watertight workflow.""" return self._base_meshing.watertight_workflow() diff --git a/src/ansys/fluent/core/session_pure_meshing.pyi b/src/ansys/fluent/core/session_pure_meshing.pyi index d4bfea5052c7..33e94fa92a4e 100644 --- a/src/ansys/fluent/core/session_pure_meshing.pyi +++ b/src/ansys/fluent/core/session_pure_meshing.pyi @@ -34,6 +34,9 @@ from ansys.fluent.core.generated.datamodel_252.preferences import ( Root as preferences_root, ) from ansys.fluent.core.generated.datamodel_252.workflow import Root as workflow_root +from ansys.fluent.core.generated.datamodel_261.meshing_workflow import ( + Root as meshing_workflow_root, +) from ansys.fluent.core.generated.meshing.tui_252 import main_menu class PureMeshing: @@ -45,6 +48,8 @@ class PureMeshing: def meshing_utilities(self) -> meshing_utilities_root: ... @property def workflow(self) -> workflow_root: ... + @property + def meshing_workflow(self) -> meshing_workflow_root: ... def watertight(self): ... def fault_tolerant(self): ... def two_dimensional_meshing(self): ... diff --git a/src/ansys/fluent/core/ui/standalone_web_ui.py b/src/ansys/fluent/core/ui/standalone_web_ui.py index ef2fc06cbe05..1c6f37aded4a 100644 --- a/src/ansys/fluent/core/ui/standalone_web_ui.py +++ b/src/ansys/fluent/core/ui/standalone_web_ui.py @@ -34,6 +34,11 @@ "Missing dependencies, use 'pip install ansys-fluent-core[ui]' to install them." ) from exc +from ansys.fluent.core.services.datamodel_se import ( + PyCommand, + PyMenu, + PyNamedObjectContainer, +) from ansys.fluent.core.solver.flobject import ( BaseCommand, Group, @@ -255,8 +260,14 @@ def _settings_view(obj, indent: int = 0) -> pn.viewable.Viewable: else: command_names = obj.command_names child_names = list(obj) + command_names + elif isinstance(obj, (PyMenu, PyNamedObjectContainer)): + child_names = list(obj()) + command_names = [] + # for item in child_names: + # if isinstance(getattr(obj, item), PyCommand): + # command_names.append(item) else: - if isinstance(obj, BaseCommand): + if isinstance(obj, (BaseCommand, PyCommand)): return _command_view(obj, props) if props["is_active"] else pn.pane.HTML("") else: return _param_view(obj, props) if props["is_active"] else pn.pane.HTML("") @@ -269,7 +280,10 @@ def _loader(name=child_name, parent=obj, lvl=indent + 1): try: child_obj = getattr(parent, name) except AttributeError: - child_obj = parent[name] + try: + child_obj = parent[name] + except TypeError: + child_obj = getattr(parent, name.split(":")[0])[name.split(":")[-1]] return _settings_view(child_obj, lvl) # Each child gets its own one-item accordion (mirrors your ipywidgets UX) diff --git a/src/ansys/fluent/core/ui/utils.py b/src/ansys/fluent/core/ui/utils.py index f92b1b3f306b..f06a35fc189f 100644 --- a/src/ansys/fluent/core/ui/utils.py +++ b/src/ansys/fluent/core/ui/utils.py @@ -22,6 +22,7 @@ """Utilities methods for ui rendering.""" +from ansys.fluent.core.services.datamodel_se import PyNumerical, PyTextual from ansys.fluent.core.solver.flobject import ( BaseCommand, Boolean, @@ -133,9 +134,9 @@ def _render_widget_from_props_generic( return widget_map["bool"](bool(settings_val)) elif isinstance(settings_obj, Integer): return widget_map["int"](int(settings_val)) - elif isinstance(settings_obj, Real): + elif isinstance(settings_obj, (Real, PyNumerical)): return widget_map["float"](float(settings_val)) - elif isinstance(settings_obj, String): + elif isinstance(settings_obj, (String, PyTextual)): if allowed: options = [str(v) for v in allowed] val = str(settings_val) diff --git a/tests/test_codegen.py b/tests/test_codegen.py index b112958eb39d..5a4e64da50a7 100644 --- a/tests/test_codegen.py +++ b/tests/test_codegen.py @@ -192,6 +192,7 @@ def test_codegen_with_tui_solver_static_info(mode, monkeypatch): _static_info_type_by_rules = { "workflow": StaticInfoType.DATAMODEL_WORKFLOW, + "meshing_workflow": StaticInfoType.DATAMODEL_MESHING_WORKFLOW, "meshing": StaticInfoType.DATAMODEL_MESHING, "PartManagement": StaticInfoType.DATAMODEL_PART_MANAGEMENT, "PMFileManagement": StaticInfoType.DATAMODEL_PM_FILE_MANAGEMENT, @@ -428,6 +429,7 @@ def test_codegen_with_datamodel_static_info(monkeypatch, rules): api_tree_expected = {"": {}, "": {}} if rules in [ "workflow", + "meshing_workflow", "meshing", "PartManagement", "PMFileManagement", diff --git a/tests/test_server_meshing_workflow.py b/tests/test_server_meshing_workflow.py new file mode 100644 index 000000000000..0ff8a3854fb3 --- /dev/null +++ b/tests/test_server_meshing_workflow.py @@ -0,0 +1,766 @@ +# Copyright (C) 2021 - 2025 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import pytest + +from ansys.fluent.core import examples + + +@pytest.mark.fluent_version(">=26.1") +def test_new_watertight_workflow(new_meshing_session_wo_exit): + # Import geometry + import_file_name = examples.download_file( + "mixing_elbow.pmdb", "pyfluent/mixing_elbow" + ) + new_meshing_session_wo_exit.workflow.InitializeWorkflow( + WorkflowType="Watertight Geometry" + ) + watertight = new_meshing_session_wo_exit.meshing_workflow + watertight.task_object.import_geometry["Import Geometry"].arguments.file_name = ( + import_file_name + ) + assert ( + watertight.task_object.import_geometry[ + "Import Geometry" + ].arguments.length_unit() + == "mm" + ) + watertight.task_object.import_geometry[ + "Import Geometry" + ].arguments.length_unit.set_state("in") + assert ( + watertight.task_object.import_geometry[ + "Import Geometry" + ].arguments.length_unit.get_state() + == "in" + ) + watertight.task_object.import_geometry["Import Geometry"].execute() + + # Add local sizing + watertight.task_object.add_local_sizing_wtm["Add Local Sizing"].add_child_to_task() + watertight.task_object.add_local_sizing_wtm["Add Local Sizing"].execute() + + # Generate surface mesh + watertight.task_object.create_surface_mesh[ + "Generate the Surface Mesh" + ].arguments.cfd_surface_mesh_controls.max_size = 0.3 + assert ( + watertight.task_object.create_surface_mesh[ + "Generate the Surface Mesh" + ].arguments.cfd_surface_mesh_controls.max_size() + == 0.3 + ) + watertight.task_object.create_surface_mesh["Generate the Surface Mesh"].execute() + + # Describe geometry + watertight.task_object.describe_geometry["Describe Geometry"].update_child_tasks( + setup_type_changed=False + ) + watertight.task_object.describe_geometry[ + "Describe Geometry" + ].arguments.setup_type.set_state( + "The geometry consists of only fluid regions with no voids" + ) + watertight.task_object.describe_geometry["Describe Geometry"].update_child_tasks( + setup_type_changed=False + ) + watertight.task_object.describe_geometry["Describe Geometry"].execute() + + # Update boundaries + watertight.task_object.update_boundaries[ + "Update Boundaries" + ].arguments.boundary_label_list.set_state(["wall-inlet"]) + watertight.task_object.update_boundaries[ + "Update Boundaries" + ].arguments.boundary_label_type_list.set_state(["wall"]) + watertight.task_object.update_boundaries[ + "Update Boundaries" + ].arguments.old_boundary_label_list.set_state(["wall-inlet"]) + watertight.task_object.update_boundaries[ + "Update Boundaries" + ].arguments.old_boundary_label_type_list.set_state(["velocity-inlet"]) + watertight.task_object.update_boundaries["Update Boundaries"].execute() + + # Update regions + watertight.task_object.update_regions["Update Regions"].execute() + + # Add boundary layers + watertight.task_object.add_boundary_layers[ + "Add Boundary Layers" + ].add_child_to_task() + watertight.task_object.add_boundary_layers[ + "Add Boundary Layers" + ].arguments.control_name.set_state("smooth-transition_1") + watertight.task_object.add_boundary_layers[ + "Add Boundary Layers" + ].insert_compound_child_task() + watertight.task_object.add_boundary_layers["Add Boundary Layers"].execute() + + # Generate volume mesh + watertight.task_object.create_volume_mesh_wtm[ + "Generate the Volume Mesh" + ].arguments.volume_fill.set_state("poly-hexcore") + watertight.task_object.create_volume_mesh_wtm[ + "Generate the Volume Mesh" + ].arguments.volume_fill_controls.hex_max_cell_length = 0.3 + watertight.task_object.create_volume_mesh_wtm["Generate the Volume Mesh"].execute() + + # Switch to solution mode + solver = new_meshing_session_wo_exit.switch_to_solver() + assert solver.is_active() is True + assert new_meshing_session_wo_exit.is_active() is False + solver.exit() + assert solver.is_active() is False + + +@pytest.mark.fluent_version(">=26.1") +def test_new_fault_tolerant_workflow(new_meshing_session_wo_exit): + meshing = new_meshing_session_wo_exit + + # Import CAD and part management + import_file_name = examples.download_file( + "exhaust_system.fmd", "pyfluent/exhaust_system" + ) + new_meshing_session_wo_exit.workflow.InitializeWorkflow( + WorkflowType="Fault-tolerant Meshing" + ) + fault_tolerant = meshing.meshing_workflow + meshing.PartManagement.InputFileChanged( + FilePath=import_file_name, IgnoreSolidNames=False, PartPerBody=False + ) + meshing.PMFileManagement.FileManager.LoadFiles() + meshing.PartManagement.Node["Meshing Model"].Copy( + Paths=[ + "/dirty_manifold-for-wrapper," + "1/dirty_manifold-for-wrapper,1/main,1", + "/dirty_manifold-for-wrapper," + + "1/dirty_manifold-for-wrapper,1/flow-pipe,1", + "/dirty_manifold-for-wrapper," + + "1/dirty_manifold-for-wrapper,1/outpipe3,1", + "/dirty_manifold-for-wrapper," + "1/dirty_manifold-for-wrapper,1/object2,1", + "/dirty_manifold-for-wrapper," + "1/dirty_manifold-for-wrapper,1/object1,1", + ] + ) + meshing.PartManagement.ObjectSetting["DefaultObjectSetting"].OneZonePer.set_state( + "part" + ) + fault_tolerant.task_object.import_cad_and_part_management[ + "Import CAD and Part Management" + ].arguments.context.set_state(0) + fault_tolerant.task_object.import_cad_and_part_management[ + "Import CAD and Part Management" + ].arguments.create_object_per = "Custom" + fault_tolerant.task_object.import_cad_and_part_management[ + "Import CAD and Part Management" + ].arguments.fmd_file_name.set_state(import_file_name) + fault_tolerant.task_object.import_cad_and_part_management[ + "Import CAD and Part Management" + ].arguments.file_loaded = True + fault_tolerant.task_object.import_cad_and_part_management[ + "Import CAD and Part Management" + ].arguments.object_setting = "DefaultObjectSetting" + fault_tolerant.task_object.import_cad_and_part_management[ + "Import CAD and Part Management" + ].execute() + + # Describe geometry and flow + fault_tolerant.task_object.describe_geometry_and_flow[ + "Describe Geometry and Flow" + ].arguments.add_enclosure.set_state(False) + fault_tolerant.task_object.describe_geometry_and_flow[ + "Describe Geometry and Flow" + ].arguments.close_caps = True + fault_tolerant.task_object.describe_geometry_and_flow[ + "Describe Geometry and Flow" + ].arguments.describe_geometry_and_flow_options.advanced_options = True + fault_tolerant.task_object.describe_geometry_and_flow[ + "Describe Geometry and Flow" + ].arguments.describe_geometry_and_flow_options.extract_edge_features = True + fault_tolerant.task_object.describe_geometry_and_flow[ + "Describe Geometry and Flow" + ].arguments.flow_type = "Internal flow through the object" + fault_tolerant.task_object.describe_geometry_and_flow[ + "Describe Geometry and Flow" + ].update_child_tasks(setup_type_changed=False) + fault_tolerant.task_object.describe_geometry_and_flow[ + "Describe Geometry and Flow" + ].execute() + + # Enclose fluid regions (capping) + fault_tolerant.task_object.capping[ + "Enclose Fluid Regions (Capping)" + ].arguments.patch_name.set_state("inlet-1") + fault_tolerant.task_object.capping[ + "Enclose Fluid Regions (Capping)" + ].arguments.selection_type.set_state("zone") + fault_tolerant.task_object.capping[ + "Enclose Fluid Regions (Capping)" + ].arguments.zone_selection_list.set_state(["inlet.1"]) + fault_tolerant.task_object.capping[ + "Enclose Fluid Regions (Capping)" + ].insert_compound_child_task() + fault_tolerant.task_object.capping["inlet-1"].execute() + + fault_tolerant.task_object.capping[ + "Enclose Fluid Regions (Capping)" + ].arguments.patch_name.set_state("inlet-2") + fault_tolerant.task_object.capping[ + "Enclose Fluid Regions (Capping)" + ].arguments.selection_type.set_state("zone") + fault_tolerant.task_object.capping[ + "Enclose Fluid Regions (Capping)" + ].arguments.zone_selection_list.set_state(["inlet.2"]) + fault_tolerant.task_object.capping[ + "Enclose Fluid Regions (Capping)" + ].insert_compound_child_task() + fault_tolerant.task_object.capping["inlet-2"].execute() + + fault_tolerant.task_object.capping[ + "Enclose Fluid Regions (Capping)" + ].arguments.patch_name.set_state("inlet-3") + fault_tolerant.task_object.capping[ + "Enclose Fluid Regions (Capping)" + ].arguments.selection_type.set_state("zone") + fault_tolerant.task_object.capping[ + "Enclose Fluid Regions (Capping)" + ].arguments.zone_selection_list.set_state(["inlet"]) + fault_tolerant.task_object.capping[ + "Enclose Fluid Regions (Capping)" + ].insert_compound_child_task() + fault_tolerant.task_object.capping["inlet-3"].execute() + + fault_tolerant.task_object.capping[ + "Enclose Fluid Regions (Capping)" + ].arguments.patch_name.set_state("outlet-1") + fault_tolerant.task_object.capping[ + "Enclose Fluid Regions (Capping)" + ].arguments.selection_type.set_state("zone") + fault_tolerant.task_object.capping[ + "Enclose Fluid Regions (Capping)" + ].arguments.zone_selection_list.set_state(["outlet"]) + fault_tolerant.task_object.capping[ + "Enclose Fluid Regions (Capping)" + ].arguments.zone_type.set_state("pressure-outlet") + fault_tolerant.task_object.capping[ + "Enclose Fluid Regions (Capping)" + ].insert_compound_child_task() + fault_tolerant.task_object.capping["outlet-1"].execute() + + # Extract edge features + fault_tolerant.task_object.extract_edge_features[ + "Extract Edge Features" + ].arguments.extract_edges_name.set_state("edge-group-1") + fault_tolerant.task_object.extract_edge_features[ + "Extract Edge Features" + ].arguments.extract_method_type.set_state("Intersection Loops") + fault_tolerant.task_object.extract_edge_features[ + "Extract Edge Features" + ].arguments.object_selection_list.set_state(["flow_pipe", "main"]) + fault_tolerant.task_object.extract_edge_features[ + "Extract Edge Features" + ].insert_compound_child_task() + fault_tolerant.task_object.extract_edge_features["edge-group-1"].execute() + + # Identify regions + fault_tolerant.task_object.identify_regions[ + "Identify Regions" + ].arguments.show_coordinates = True + fault_tolerant.task_object.identify_regions[ + "Identify Regions" + ].arguments.material_points_name.set_state("fluid-region-1") + fault_tolerant.task_object.identify_regions[ + "Identify Regions" + ].arguments.selection_type.set_state("zone") + fault_tolerant.task_object.identify_regions[ + "Identify Regions" + ].arguments.x.set_state(377.322045740589) + fault_tolerant.task_object.identify_regions[ + "Identify Regions" + ].arguments.y.set_state(-176.800676988458) + fault_tolerant.task_object.identify_regions[ + "Identify Regions" + ].arguments.z.set_state(-37.0764628583475) + fault_tolerant.task_object.identify_regions[ + "Identify Regions" + ].arguments.zone_selection_list.set_state(["main.1"]) + fault_tolerant.task_object.identify_regions[ + "Identify Regions" + ].insert_compound_child_task() + fault_tolerant.task_object.identify_regions["fluid-region-1"].execute() + + fault_tolerant.task_object.identify_regions[ + "Identify Regions" + ].arguments.show_coordinates = True + fault_tolerant.task_object.identify_regions[ + "Identify Regions" + ].arguments.material_points_name.set_state("void-region-1") + fault_tolerant.task_object.identify_regions[ + "Identify Regions" + ].arguments.new_region_type.set_state("void") + fault_tolerant.task_object.identify_regions[ + "Identify Regions" + ].arguments.object_selection_list.set_state( + ["inlet-1", "inlet-2", "inlet-3", "main"] + ) + fault_tolerant.task_object.identify_regions[ + "Identify Regions" + ].arguments.x.set_state(374.722045740589) + fault_tolerant.task_object.identify_regions[ + "Identify Regions" + ].arguments.y.set_state(-278.9775145640143) + fault_tolerant.task_object.identify_regions[ + "Identify Regions" + ].arguments.z.set_state(-161.1700719416913) + fault_tolerant.task_object.identify_regions[ + "Identify Regions" + ].arguments.zone_selection_list.set_state(["main.1"]) + fault_tolerant.task_object.identify_regions[ + "Identify Regions" + ].insert_compound_child_task() + fault_tolerant.task_object.identify_regions["void-region-1"].execute() + + # Define leakage threshold + fault_tolerant.task_object.define_leakage_threshold[ + "Define Leakage Threshold" + ].arguments.set_state( + { + "add_child": "yes", + "flip_direction": True, + "leakage_name": "leakage-1", + "plane_direction": "X", + "region_selection_single": "void-region-1", + } + ) + fault_tolerant.task_object.define_leakage_threshold[ + "Define Leakage Threshold" + ].insert_compound_child_task() + fault_tolerant.task_object.define_leakage_threshold["leakage-1"].execute() + + # Update regions settings + fault_tolerant.task_object.update_region_settings[ + "Update Region Settings" + ].arguments.all_region_filter_categories.set_state(["2"] * 5 + ["1"] * 2) + fault_tolerant.task_object.update_region_settings[ + "Update Region Settings" + ].arguments.all_region_leakage_size_list.set_state(["none"] * 6 + ["6.4"]) + fault_tolerant.task_object.update_region_settings[ + "Update Region Settings" + ].arguments.all_region_linked_construction_surface_list.set_state( + ["n/a"] * 6 + ["no"] + ) + fault_tolerant.task_object.update_region_settings[ + "Update Region Settings" + ].arguments.all_region_mesh_method_list.set_state(["none"] * 6 + ["wrap"]) + fault_tolerant.task_object.update_region_settings[ + "Update Region Settings" + ].arguments.all_region_name_list.set_state( + [ + "main", + "flow_pipe", + "outpipe3", + "object2", + "object1", + "void-region-1", + "fluid-region-1", + ] + ) + fault_tolerant.task_object.update_region_settings[ + "Update Region Settings" + ].arguments.all_region_overset_componen_list.set_state(["no"] * 7) + fault_tolerant.task_object.update_region_settings[ + "Update Region Settings" + ].arguments.all_region_source_list.set_state(["object"] * 5 + ["mpt"] * 2) + fault_tolerant.task_object.update_region_settings[ + "Update Region Settings" + ].arguments.all_region_type_list.set_state(["void"] * 6 + ["fluid"]) + fault_tolerant.task_object.update_region_settings[ + "Update Region Settings" + ].arguments.all_region_volume_fill_list.set_state(["none"] * 6 + ["tet"]) + fault_tolerant.task_object.update_region_settings[ + "Update Region Settings" + ].arguments.filter_category.set_state("Identified Regions") + fault_tolerant.task_object.update_region_settings[ + "Update Region Settings" + ].arguments.all_region_leakage_size_list.set_state([""]) + fault_tolerant.task_object.update_region_settings[ + "Update Region Settings" + ].arguments.all_region_mesh_method_list.set_state(["wrap"]) + fault_tolerant.task_object.update_region_settings[ + "Update Region Settings" + ].arguments.all_region_name_list.set_state(["fluid-region-1"]) + fault_tolerant.task_object.update_region_settings[ + "Update Region Settings" + ].arguments.all_region_overset_componen_list.set_state(["no"]) + fault_tolerant.task_object.update_region_settings[ + "Update Region Settings" + ].arguments.all_region_type_list.set_state(["fluid"]) + fault_tolerant.task_object.update_region_settings[ + "Update Region Settings" + ].arguments.all_region_volume_fill_list.set_state(["hexcore"]) + fault_tolerant.task_object.update_region_settings[ + "Update Region Settings" + ].arguments.all_region_leakage_size_list.set_state([""]) + fault_tolerant.task_object.update_region_settings[ + "Update Region Settings" + ].arguments.all_region_mesh_method_list.set_state(["wrap"]) + fault_tolerant.task_object.update_region_settings[ + "Update Region Settings" + ].arguments.all_region_name_list.set_state(["fluid-region-1"]) + fault_tolerant.task_object.update_region_settings[ + "Update Region Settings" + ].arguments.all_region_overset_componen_list.set_state(["no"]) + fault_tolerant.task_object.update_region_settings[ + "Update Region Settings" + ].arguments.all_region_type_list.set_state(["fluid"]) + fault_tolerant.task_object.update_region_settings[ + "Update Region Settings" + ].arguments.all_region_volume_fill_list.set_state(["tet"]) + fault_tolerant.task_object.update_region_settings[ + "Update Region Settings" + ].execute() + + # Choose mesh control options + fault_tolerant.task_object.choose_mesh_control_options[ + "Choose Mesh Control Options" + ].execute() + + # Generate surface mesh + fault_tolerant.task_object.generate_surface_mesh[ + "Generate the Surface Mesh" + ].execute() + + # Update boundaries + fault_tolerant.task_object.update_boundaries["Update Boundaries"].execute() + + # Add boundary layers + fault_tolerant.task_object.add_boundary_layers[ + "Add Boundary Layers" + ].arguments.control_name.set_state("aspect-ratio_1") + fault_tolerant.task_object.add_boundary_layers[ + "Add Boundary Layers" + ].insert_compound_child_task() + fault_tolerant.task_object.add_boundary_layers["aspect-ratio_1"].execute() + + # Generate volume mesh + fault_tolerant.task_object.create_volume_mesh_ftm[ + "Generate the Volume Mesh" + ].arguments.all_region_name_list.set_state( + [ + "main", + "flow_pipe", + "outpipe3", + "object2", + "object1", + "void-region-1", + "fluid-region-1", + ] + ) + fault_tolerant.task_object.create_volume_mesh_ftm[ + "Generate the Volume Mesh" + ].arguments.all_region_size_list.set_state(["11.33375"] * 7) + fault_tolerant.task_object.create_volume_mesh_ftm[ + "Generate the Volume Mesh" + ].arguments.all_region_volume_fill_list.set_state(["none"] * 6 + ["tet"]) + fault_tolerant.task_object.create_volume_mesh_ftm[ + "Generate the Volume Mesh" + ].execute() + + # Generate volume mesh + solver = meshing.switch_to_solver() + assert solver.is_active() is True + assert meshing.is_active() is False + solver.exit() + assert solver.is_active() is False + + +@pytest.mark.fluent_version(">=26.1") +def test_new_2d_meshing_workflow(new_meshing_session_wo_exit): + # Import geometry + import_file_name = examples.download_file("NACA0012.fmd", "pyfluent/airfoils") + new_meshing_session_wo_exit.workflow.InitializeWorkflow(WorkflowType="2D Meshing") + + two_dim_mesh = new_meshing_session_wo_exit.meshing_workflow + + two_dim_mesh.task_object.load_cad_geometry[ + "Load CAD Geometry" + ].arguments.file_name = import_file_name + two_dim_mesh.task_object.load_cad_geometry[ + "Load CAD Geometry" + ].arguments.length_unit = "mm" + two_dim_mesh.task_object.load_cad_geometry[ + "Load CAD Geometry" + ].arguments.refaceting.refacet = False + two_dim_mesh.task_object.load_cad_geometry["Load CAD Geometry"].execute() + + # Set regions and boundaries + two_dim_mesh.task_object.update_boundaries[ + "Update Boundaries" + ].arguments.selection_type = "zone" + two_dim_mesh.task_object.update_boundaries["Update Boundaries"].execute() + + # Define global sizing + two_dim_mesh.task_object.define_global_sizing[ + "Define Global Sizing" + ].arguments.curvature_normal_angle = 20 + two_dim_mesh.task_object.define_global_sizing[ + "Define Global Sizing" + ].arguments.max_size = 2000.0 + two_dim_mesh.task_object.define_global_sizing[ + "Define Global Sizing" + ].arguments.min_size = 5.0 + two_dim_mesh.task_object.define_global_sizing[ + "Define Global Sizing" + ].arguments.size_functions = "Curvature" + two_dim_mesh.task_object.define_global_sizing["Define Global Sizing"].execute() + + # Add local sizing + two_dim_mesh.task_object.add_local_sizing_wtm[ + "Add Local Sizing" + ].arguments.add_child = True + two_dim_mesh.task_object.add_local_sizing_wtm[ + "Add Local Sizing" + ].arguments.boi_control_name = "boi_1" + two_dim_mesh.task_object.add_local_sizing_wtm[ + "Add Local Sizing" + ].arguments.boi_execution = "Body Of Influence" + two_dim_mesh.task_object.add_local_sizing_wtm[ + "Add Local Sizing" + ].arguments.boi_face_label_list = ["boi"] + two_dim_mesh.task_object.add_local_sizing_wtm[ + "Add Local Sizing" + ].arguments.boi_size = 50.0 + two_dim_mesh.task_object.add_local_sizing_wtm[ + "Add Local Sizing" + ].arguments.boi_zoneor_label = "label" + two_dim_mesh.task_object.add_local_sizing_wtm[ + "Add Local Sizing" + ].arguments.draw_size_control = True + two_dim_mesh.task_object.add_local_sizing_wtm[ + "Add Local Sizing" + ].add_child_and_update(defer_update=False) + + two_dim_mesh.task_object.add_local_sizing_wtm[ + "Add Local Sizing" + ].arguments.add_child = True + two_dim_mesh.task_object.add_local_sizing_wtm[ + "Add Local Sizing" + ].arguments.boi_control_name = "edgesize_1" + two_dim_mesh.task_object.add_local_sizing_wtm[ + "Add Local Sizing" + ].arguments.boi_execution = "Edge Size" + two_dim_mesh.task_object.add_local_sizing_wtm[ + "Add Local Sizing" + ].arguments.boi_size = 5.0 + two_dim_mesh.task_object.add_local_sizing_wtm[ + "Add Local Sizing" + ].arguments.boi_zoneor_label = "label" + two_dim_mesh.task_object.add_local_sizing_wtm[ + "Add Local Sizing" + ].arguments.draw_size_control = True + two_dim_mesh.task_object.add_local_sizing_wtm[ + "Add Local Sizing" + ].arguments.edge_label_list = ["airfoil-te"] + two_dim_mesh.task_object.add_local_sizing_wtm[ + "Add Local Sizing" + ].add_child_and_update(defer_update=False) + + two_dim_mesh.task_object.add_local_sizing_wtm[ + "Add Local Sizing" + ].arguments.add_child = True + two_dim_mesh.task_object.add_local_sizing_wtm[ + "Add Local Sizing" + ].arguments.boi_control_name = "curvature_1" + two_dim_mesh.task_object.add_local_sizing_wtm[ + "Add Local Sizing" + ].arguments.boi_curvature_normal_angle = 10 + two_dim_mesh.task_object.add_local_sizing_wtm[ + "Add Local Sizing" + ].arguments.boi_execution = "Curvature" + two_dim_mesh.task_object.add_local_sizing_wtm[ + "Add Local Sizing" + ].arguments.boi_max_size = 2 + two_dim_mesh.task_object.add_local_sizing_wtm[ + "Add Local Sizing" + ].arguments.boi_min_size = 1.5 + two_dim_mesh.task_object.add_local_sizing_wtm[ + "Add Local Sizing" + ].arguments.boi_scope_to = "edges" + two_dim_mesh.task_object.add_local_sizing_wtm[ + "Add Local Sizing" + ].arguments.boi_zoneor_label = "label" + two_dim_mesh.task_object.add_local_sizing_wtm[ + "Add Local Sizing" + ].arguments.draw_size_control = True + two_dim_mesh.task_object.add_local_sizing_wtm[ + "Add Local Sizing" + ].arguments.edge_label_list = ["airfoil"] + two_dim_mesh.task_object.add_local_sizing_wtm[ + "Add Local Sizing" + ].add_child_and_update(defer_update=False) + + # Add boundary layer + two_dim_mesh.task_object.add_2d_boundary_layers[ + "Add 2D Boundary Layers" + ].arguments.add_child = True + two_dim_mesh.task_object.add_2d_boundary_layers[ + "Add 2D Boundary Layers" + ].arguments.control_name = "aspect-ratio_1" + two_dim_mesh.task_object.add_2d_boundary_layers[ + "Add 2D Boundary Layers" + ].arguments.number_of_layers = 4 + two_dim_mesh.task_object.add_2d_boundary_layers[ + "Add 2D Boundary Layers" + ].arguments.offset_method_type = "aspect-ratio" + two_dim_mesh.task_object.add_2d_boundary_layers[ + "Add 2D Boundary Layers" + ].add_child_and_update(defer_update=False) + + # NOTE: Setting `show_advanced_options = True` is required to configure advanced preferences. + # This dependency may be removed in a future release as the API evolves. + two_dim_mesh.task_object.generate_initial_surface_mesh[ + "Generate the Surface Mesh" + ].arguments.surface_2d_preferences.show_advanced_options = True + two_dim_mesh.task_object.generate_initial_surface_mesh[ + "Generate the Surface Mesh" + ].arguments.surface_2d_preferences.merge_edge_zones_based_on_labels = False + two_dim_mesh.task_object.generate_initial_surface_mesh[ + "Generate the Surface Mesh" + ].arguments.surface_2d_preferences.merge_face_zones_based_on_labels = False + two_dim_mesh.task_object.generate_initial_surface_mesh[ + "Generate the Surface Mesh" + ].execute() + + two_dim_mesh.task_object.add_2d_boundary_layers["Add 2D Boundary Layers"].revert() + two_dim_mesh.task_object.add_2d_boundary_layers[ + "Add 2D Boundary Layers" + ].arguments.add_child = "yes" + two_dim_mesh.task_object.add_2d_boundary_layers[ + "Add 2D Boundary Layers" + ].arguments.control_name = "uniform_1" + two_dim_mesh.task_object.add_2d_boundary_layers[ + "Add 2D Boundary Layers" + ].arguments.first_layer_height = 2 + two_dim_mesh.task_object.add_2d_boundary_layers[ + "Add 2D Boundary Layers" + ].arguments.number_of_layers = 4 + two_dim_mesh.task_object.add_2d_boundary_layers[ + "Add 2D Boundary Layers" + ].arguments.offset_method_type = "uniform" + two_dim_mesh.task_object.add_2d_boundary_layers["Add 2D Boundary Layers"].execute() + + # NOTE: Setting `show_advanced_options = True` is required to configure advanced preferences. + # This dependency may be removed in a future release as the API evolves. + two_dim_mesh.task_object.generate_initial_surface_mesh[ + "Generate the Surface Mesh" + ].arguments.surface_2d_preferences.show_advanced_options = True + two_dim_mesh.task_object.generate_initial_surface_mesh[ + "Generate the Surface Mesh" + ].arguments.surface_2d_preferences.merge_edge_zones_based_on_labels = False + two_dim_mesh.task_object.generate_initial_surface_mesh[ + "Generate the Surface Mesh" + ].arguments.surface_2d_preferences.merge_face_zones_based_on_labels = False + two_dim_mesh.task_object.generate_initial_surface_mesh[ + "Generate the Surface Mesh" + ].execute() + + # Switch to solution mode + solver = new_meshing_session_wo_exit.switch_to_solver() + assert solver.is_active() is True + assert new_meshing_session_wo_exit.is_active() is False + solver.exit() + assert solver.is_active() is False + + +@pytest.mark.fluent_version(">=26.1") +def test_updating_state_in_new_meshing_workflow(new_meshing_session): + # Import geometry + import_file_name = examples.download_file( + "mixing_elbow.pmdb", "pyfluent/mixing_elbow" + ) + new_meshing_session.workflow.InitializeWorkflow(WorkflowType="Watertight Geometry") + watertight = new_meshing_session.meshing_workflow + assert ( + watertight.task_object.import_geometry[ + "Import Geometry" + ].arguments.length_unit() + == "mm" + ) + assert ( + watertight.task_object.import_geometry[ + "Import Geometry" + ].arguments.cad_import_options.feature_angle() + == 40.0 + ) + assert watertight.task_object.import_geometry[ + "Import Geometry" + ].arguments.cad_import_options.one_zone_per.allowed_values() == [ + "body", + "face", + "object", + ] + assert ( + watertight.task_object.import_geometry[ + "Import Geometry" + ].arguments.cad_import_options.one_zone_per() + == "body" + ) + watertight.task_object.import_geometry["Import Geometry"].arguments = { + "file_name": import_file_name, + "length_unit": "in", + "cad_import_options": {"feature_angle": 35, "one_zone_per": "object"}, + } + assert ( + watertight.task_object.import_geometry[ + "Import Geometry" + ].arguments.cad_import_options.feature_angle() + == 35.0 + ) + assert ( + watertight.task_object.import_geometry[ + "Import Geometry" + ].arguments.cad_import_options.one_zone_per.get_state() + == "object" + ) + assert ( + watertight.task_object.import_geometry[ + "Import Geometry" + ].arguments.length_unit.get_state() + == "in" + ) + watertight.task_object.import_geometry[ + "Import Geometry" + ].arguments.cad_import_options.feature_angle = 25.0 + assert ( + watertight.task_object.import_geometry[ + "Import Geometry" + ].arguments.cad_import_options.feature_angle() + == 25.0 + ) + watertight.task_object.import_geometry[ + "Import Geometry" + ].arguments.cad_import_options.one_zone_per = "face" + assert ( + watertight.task_object.import_geometry[ + "Import Geometry" + ].arguments.cad_import_options.one_zone_per() + == "face" + ) + watertight.task_object.import_geometry["Import Geometry"].execute()