diff --git a/doc/changelog.d/5118.added.md b/doc/changelog.d/5118.added.md new file mode 100644 index 00000000000..88057cb75df --- /dev/null +++ b/doc/changelog.d/5118.added.md @@ -0,0 +1 @@ +Meshing update diff --git a/src/ansys/fluent/core/__init__.py b/src/ansys/fluent/core/__init__.py index 9c99b922d8a..9aa1e3af506 100644 --- a/src/ansys/fluent/core/__init__.py +++ b/src/ansys/fluent/core/__init__.py @@ -32,6 +32,60 @@ # isort: on +from ansys.fluent.core.field_data_interfaces import ( # noqa: F401 + PathlinesFieldDataRequest, + ScalarFieldDataRequest, + SurfaceDataType, + SurfaceFieldDataRequest, + VectorFieldDataRequest, +) +from ansys.fluent.core.get_build_details import ( # noqa: F401 + get_build_version, + get_build_version_string, +) +from ansys.fluent.core.launcher.launch_options import ( # noqa: F401 + Dimension, + FluentLinuxGraphicsDriver, + FluentMode, + FluentWindowsGraphicsDriver, + Precision, + UIMode, +) +from ansys.fluent.core.launcher.launcher import ( # noqa: F401 + connect_to_fluent, + launch_fluent, +) +from ansys.fluent.core.meshing.meshing_workflow_new import ( # noqa: F401 + CreateWorkflow, + FaultTolerantMeshing, + LoadWorkflow, + TopologyBasedMeshing, + TwoDimensionalMeshing, + WatertightMeshing, +) +from ansys.fluent.core.parametric import LocalParametricStudy # noqa: F401 +from ansys.fluent.core.pyfluent_warnings import ( # noqa: F401 + PyFluentDeprecationWarning, + PyFluentUserWarning, + warning, +) +from ansys.fluent.core.search import search # noqa: F401 +from ansys.fluent.core.services.batch_ops import BatchOps # noqa: F401 +from ansys.fluent.core.session import BaseSession as Fluent # noqa: F401 +from ansys.fluent.core.session_utilities import ( # noqa: F401 + Meshing, + PrePost, + PureMeshing, + Solver, + SolverAero, + SolverIcing, +) +from ansys.fluent.core.streaming_services.events_streaming import * # noqa: F401, F403 +from ansys.fluent.core.streaming_services.events_streaming_v1 import * # noqa: F401, F403 +from ansys.fluent.core.utils import fldoc +from ansys.fluent.core.utils.context_managers import using # noqa: F401 +from ansys.fluent.core.utils.fluent_version import FluentVersion # noqa: F401 +from ansys.fluent.core.utils.setup_for_fluent import setup_for_fluent # noqa: F401 from ansys.fluent.core.field_data_interfaces import * from ansys.fluent.core.get_build_details import * from ansys.fluent.core.launcher.launch_options import * diff --git a/src/ansys/fluent/core/meshing/meshing_workflow_new.py b/src/ansys/fluent/core/meshing/meshing_workflow_new.py index 00763626f83..28147684bfc 100644 --- a/src/ansys/fluent/core/meshing/meshing_workflow_new.py +++ b/src/ansys/fluent/core/meshing/meshing_workflow_new.py @@ -30,118 +30,156 @@ from ansys.fluent.core._types import PathType from ansys.fluent.core.services.datamodel_se import PyMenu -from ansys.fluent.core.utils.fluent_version import FluentVersion +from ansys.fluent.core.session_base_meshing import BaseMeshing +from ansys.fluent.core.session_meshing import Meshing +from ansys.fluent.core.session_pure_meshing import PureMeshing +from ansys.fluent.core.session_shared import _make_datamodel_module from ansys.fluent.core.workflow_new import Workflow +def _get_base_meshing(session) -> "BaseMeshing": + """Extract a ``BaseMeshing`` instance from a session object. + + Parameters + ---------- + session : PureMeshing | Meshing + A meshing session or its internal ``BaseMeshing`` helper. + + Returns + ------- + BaseMeshing + The underlying ``BaseMeshing`` instance. + + Raises + ------ + TypeError + If *session* is not a recognised meshing session type. + """ + from ansys.fluent.core.session_base_meshing import BaseMeshing + + if isinstance(session, BaseMeshing): + return session + + # PureMeshing / Meshing expose _base_meshing + base = getattr(session, "_base_meshing", None) + if base is not None and isinstance(base, BaseMeshing): + return base + + raise TypeError( + f"Expected a PureMeshing, Meshing, or BaseMeshing instance, " + f"got {type(session).__name__}." + ) + + class MeshingWorkflow(Workflow): """Provides meshing specialization of the workflow wrapper that extends the core - functionality in an object-oriented manner.""" + functionality in an object-oriented manner. + + Parameters + ---------- + session : PureMeshing | Meshing + The meshing session from which the workflow is constructed. + name : str + Workflow name used to initialise the workflow + (e.g. ``"Watertight Geometry"``). + initialize : bool, optional + If ``True`` (default), the workflow is initialised immediately. + + Examples + -------- + >>> meshing = pyfluent.launch_fluent(mode=pyfluent.FluentMode.MESHING) + >>> wf = MeshingWorkflow(session=meshing, name="Watertight Geometry") + """ def __init__( self, - workflow: PyMenu, - meshing: PyMenu, + session: "PureMeshing | Meshing ", name: str, - fluent_version: FluentVersion, initialize: bool = True, ) -> None: - """Initialize MeshingWorkflow. - - Parameters - ---------- - workflow : PyMenu - Underlying workflow object. - meshing : PyMenu - Meshing object. - name: str - Workflow name to initialize it. - fluent_version: FluentVersion - Version of Fluent in this session. - initialize: bool - Flag to initialize the workflow, defaults to True. - """ + base = _get_base_meshing(session) + workflow_root = _make_datamodel_module(base, "meshing_workflow") + meshing_root = base.meshing + fluent_version = base.get_fluent_version() + super().__init__( - workflow=workflow, command_source=meshing, fluent_version=fluent_version + workflow=workflow_root, + command_source=meshing_root, + fluent_version=fluent_version, ) - self._meshing = meshing + self._meshing = meshing_root + self._base_meshing = base self._name = name if initialize: self._new_workflow(name=self._name) self._initialized = True + base._current_workflow = self class WatertightMeshingWorkflow(MeshingWorkflow): - """Provides watertight meshing specialization of the workflow wrapper.""" + """Watertight meshing workflow. + + Initialises the *Watertight Geometry* guided workflow on the connected + Fluent meshing session. + + Parameters + ---------- + session : PureMeshing | Meshing + The meshing session from which the workflow is constructed. + initialize : bool, optional + If ``True`` (default), the workflow is initialised immediately. + + Examples + -------- + >>> meshing = pyfluent.launch_fluent(mode=pyfluent.FluentMode.MESHING) + >>> watertight = WatertightMeshing(session=meshing) + """ def __init__( self, - workflow: PyMenu, - meshing: PyMenu, - fluent_version: FluentVersion, + session: "PureMeshing | Meshing ", initialize: bool = True, ) -> None: - """Initialize WatertightMeshingWorkflow. - - Parameters - ---------- - workflow : PyMenu - Underlying workflow object. - meshing : PyMenu - Meshing object. - fluent_version: FluentVersion - Version of Fluent in this session. - initialize: bool - Flag to initialize the workflow, defaults to True. - """ super().__init__( - workflow=workflow, - meshing=meshing, + session=session, name="Watertight Geometry", - fluent_version=fluent_version, initialize=initialize, ) class FaultTolerantMeshingWorkflow(MeshingWorkflow): - """Provides fault-tolerant meshing specialization of the workflow wrapper.""" + """Fault-tolerant meshing workflow. + + Initialises the *Fault-tolerant Meshing* guided workflow on the connected + Fluent meshing session. + + Parameters + ---------- + session : PureMeshing | Meshing + The meshing session from which the workflow is constructed. + initialize : bool, optional + If ``True`` (default), the workflow is initialised immediately. + + Examples + -------- + >>> meshing = pyfluent.launch_fluent(mode=pyfluent.FluentMode.MESHING) + >>> fault_tolerant = FaultTolerantMeshing(session=meshing) + """ def __init__( self, - workflow: PyMenu, - meshing: PyMenu, - part_management: PyMenu, - pm_file_management: PyMenu, - fluent_version: FluentVersion, + session: "PureMeshing | Meshing ", initialize: bool = True, ) -> None: - """Initialize FaultTolerantMeshingWorkflow. - - Parameters - ---------- - workflow : PyMenu - Underlying workflow object. - meshing : PyMenu - Meshing object. - part_management : PyMenu - Part management object. - pm_file_management : PyMenu - File management object in the part management object. - fluent_version: FluentVersion - Version of Fluent in this session. - initialize: bool - Flag to initialize the workflow, defaults to True. - """ + base = _get_base_meshing(session) super().__init__( - workflow=workflow, - meshing=meshing, + session=base, name="Fault-tolerant Meshing", - fluent_version=fluent_version, initialize=initialize, ) - self._parent_workflow = workflow - self._part_management = part_management - self._pm_file_management = pm_file_management + self._parent_workflow = self._workflow + self._part_management = base.PartManagement + self._pm_file_management = base.PMFileManagement @property def parts(self) -> PyMenu | None: @@ -191,65 +229,63 @@ def pm_file_management(self) -> PyMenu | None: class TwoDimensionalMeshingWorkflow(MeshingWorkflow): - """Provides 2D meshing specialization of the workflow wrapper.""" + """2-D meshing workflow. + + Initialises the *2D Meshing* guided workflow on the connected Fluent + meshing session. + + Parameters + ---------- + session : PureMeshing | Meshing + The meshing session from which the workflow is constructed. + initialize : bool, optional + If ``True`` (default), the workflow is initialised immediately. + + Examples + -------- + >>> meshing = pyfluent.launch_fluent(mode=pyfluent.FluentMode.MESHING) + >>> two_d = TwoDimensionalMeshing(session=meshing) + """ def __init__( self, - workflow: PyMenu, - meshing: PyMenu, - fluent_version: FluentVersion, + session: "PureMeshing | Meshing ", initialize: bool = True, ) -> None: - """Initialize TwoDimensionalMeshingWorkflow. - - Parameters - ---------- - workflow : PyMenu - Underlying workflow object. - meshing : PyMenu - Meshing object. - fluent_version: FluentVersion - Version of Fluent in this session. - initialize: bool - Flag to initialize the workflow, defaults to True. - """ super().__init__( - workflow=workflow, - meshing=meshing, + session=session, name="2D Meshing", - fluent_version=fluent_version, initialize=initialize, ) class TopologyBasedMeshingWorkflow(MeshingWorkflow): - """Provides topology-based meshing specialization of the workflow wrapper.""" + """Topology-based meshing workflow. + + Initialises the *Topology Based Meshing* guided workflow on the connected + Fluent meshing session. + + Parameters + ---------- + session : PureMeshing | Meshing + The meshing session from which the workflow is constructed. + initialize : bool, optional + If ``True`` (default), the workflow is initialised immediately. + + Examples + -------- + >>> meshing = pyfluent.launch_fluent(mode=pyfluent.FluentMode.MESHING) + >>> topo = TopologyBasedMeshing(session=meshing) + """ def __init__( self, - workflow: PyMenu, - meshing: PyMenu, - fluent_version: FluentVersion, + session: "PureMeshing | Meshing ", initialize: bool = True, ) -> None: - """Initialize TopologyBasedMeshingWorkflow. - - Parameters - ---------- - workflow : PyMenu - Underlying workflow object. - meshing : PyMenu - Meshing object. - fluent_version: FluentVersion - Version of Fluent in this session. - initialize: bool - Flag to initialize the workflow, defaults to True. - """ super().__init__( - workflow=workflow, - meshing=meshing, + session=session, name="Topology Based Meshing", - fluent_version=fluent_version, initialize=initialize, ) @@ -264,73 +300,104 @@ class WorkflowMode(Enum): class LoadWorkflow(Workflow): - """Provides a specialization of the workflow wrapper for a loaded workflow.""" + """Load a previously saved meshing workflow from a file. + + Parameters + ---------- + session : PureMeshing | Meshing + The meshing session from which the workflow is constructed. + file_path : str or PathType, optional + Path to the saved workflow file. + initialize : bool, optional + If ``True`` (default), the workflow is loaded immediately. + + Examples + -------- + >>> meshing = pyfluent.launch_fluent(mode=pyfluent.FluentMode.MESHING) + >>> loaded = LoadWorkflow(session=meshing, file_path="my_workflow.wft") + """ def __init__( self, - workflow: PyMenu, - meshing: PyMenu, - fluent_version: FluentVersion, + session: "PureMeshing | Meshing ", file_path: PathType = None, initialize: bool = True, ) -> None: - """Initialize a ``LoadWorkflow`` instance. - - Parameters - ---------- - workflow : PyMenu - Underlying workflow object. - meshing : PyMenu - Meshing object. - file_path: os.PathLike[str | bytes] | str | bytes - Path to the saved workflow file. - fluent_version: FluentVersion - Version of Fluent in this session. - initialize: bool - Flag to initialize the workflow, defaults to True. - """ + base = _get_base_meshing(session) + workflow_root = _make_datamodel_module(base, "meshing_workflow") + meshing_root = base.meshing + fluent_version = base.get_fluent_version() + super().__init__( - workflow=workflow, command_source=meshing, fluent_version=fluent_version + workflow=workflow_root, + command_source=meshing_root, + fluent_version=fluent_version, ) - self._meshing = meshing + self._meshing = meshing_root + self._base_meshing = base + self._name = "Load Workflow" if initialize: self._load_workflow(file_path=os.fspath(file_path)) + base._current_workflow = self class CreateWorkflow(Workflow): - """Provides a specialization of the workflow wrapper for a newly created - workflow.""" + """Create a new blank meshing workflow for manual task configuration. + + Parameters + ---------- + session : PureMeshing | Meshing + The meshing session from which the workflow is constructed. + initialize : bool, optional + If ``True`` (default), an empty workflow is created immediately. + + Examples + -------- + >>> meshing = pyfluent.launch_fluent(mode=pyfluent.FluentMode.MESHING) + >>> blank = CreateWorkflow(session=meshing) + """ def __init__( self, - workflow: PyMenu, - meshing: PyMenu, - fluent_version: FluentVersion, + session: "PureMeshing | Meshing ", initialize: bool = True, ) -> None: - """Initialize a ``CreateWorkflow`` instance. - - Parameters - ---------- - workflow : PyMenu - Underlying workflow object. - meshing : PyMenu - Meshing object. - fluent_version: FluentVersion - Version of Fluent in this session. - initialize: bool - Flag to initialize the workflow, defaults to True. - """ + base = _get_base_meshing(session) + workflow_root = _make_datamodel_module(base, "meshing_workflow") + meshing_root = base.meshing + fluent_version = base.get_fluent_version() + super().__init__( - workflow=workflow, command_source=meshing, fluent_version=fluent_version + workflow=workflow_root, + command_source=meshing_root, + fluent_version=fluent_version, ) - self._meshing = meshing + self._meshing = meshing_root + self._base_meshing = base + self._name = "Create New" if initialize: self._create_workflow() + base._current_workflow = self + + +# --------------------------------------------------------------------------- +# Public aliases – short, user-facing names +# --------------------------------------------------------------------------- +WatertightMeshing = WatertightMeshingWorkflow +"""Alias for :class:`WatertightMeshingWorkflow`.""" + +FaultTolerantMeshing = FaultTolerantMeshingWorkflow +"""Alias for :class:`FaultTolerantMeshingWorkflow`.""" + +TwoDimensionalMeshing = TwoDimensionalMeshingWorkflow +"""Alias for :class:`TwoDimensionalMeshingWorkflow`.""" + +TopologyBasedMeshing = TopologyBasedMeshingWorkflow +"""Alias for :class:`TopologyBasedMeshingWorkflow`.""" def _get_current_workflow(current_workflow, name: str): - if current_workflow and current_workflow._name == name: + if current_workflow and getattr(current_workflow, "_name", None) == name: return current_workflow diff --git a/src/ansys/fluent/core/session_base_meshing.py b/src/ansys/fluent/core/session_base_meshing.py index 2836908af8a..3555a84f6f9 100644 --- a/src/ansys/fluent/core/session_base_meshing.py +++ b/src/ansys/fluent/core/session_base_meshing.py @@ -252,15 +252,22 @@ def watertight_workflow( if legacy: root_module = "workflow" from ansys.fluent.core.meshing.meshing_workflow import WorkflowMode + + self._current_workflow = WorkflowMode.WATERTIGHT_MESHING_MODE.value( + _make_datamodel_module(self, root_module), + self.meshing, + self.get_fluent_version(), + initialize, + ) else: - root_module = "meshing_workflow" - from ansys.fluent.core.meshing.meshing_workflow_new import WorkflowMode - self._current_workflow = WorkflowMode.WATERTIGHT_MESHING_MODE.value( - _make_datamodel_module(self, root_module), - self.meshing, - self.get_fluent_version(), - initialize, - ) + from ansys.fluent.core.meshing.meshing_workflow_new import ( + WatertightMeshingWorkflow, + ) + + self._current_workflow = WatertightMeshingWorkflow( + session=self, + initialize=initialize, + ) return self._current_workflow def fault_tolerant_workflow( @@ -286,17 +293,24 @@ def fault_tolerant_workflow( if legacy: root_module = "workflow" from ansys.fluent.core.meshing.meshing_workflow import WorkflowMode + + self._current_workflow = WorkflowMode.FAULT_TOLERANT_MESHING_MODE.value( + _make_datamodel_module(self, root_module), + self.meshing, + self.PartManagement, + self.PMFileManagement, + self.get_fluent_version(), + initialize, + ) else: - root_module = "meshing_workflow" - from ansys.fluent.core.meshing.meshing_workflow_new import WorkflowMode - self._current_workflow = WorkflowMode.FAULT_TOLERANT_MESHING_MODE.value( - _make_datamodel_module(self, root_module), - self.meshing, - self.PartManagement, - self.PMFileManagement, - self.get_fluent_version(), - initialize, - ) + from ansys.fluent.core.meshing.meshing_workflow_new import ( + FaultTolerantMeshingWorkflow, + ) + + self._current_workflow = FaultTolerantMeshingWorkflow( + session=self, + initialize=initialize, + ) return self._current_workflow def two_dimensional_meshing_workflow( @@ -322,15 +336,22 @@ def two_dimensional_meshing_workflow( if legacy: root_module = "workflow" from ansys.fluent.core.meshing.meshing_workflow import WorkflowMode + + self._current_workflow = WorkflowMode.TWO_DIMENSIONAL_MESHING_MODE.value( + _make_datamodel_module(self, root_module), + self.meshing, + self.get_fluent_version(), + initialize, + ) else: - root_module = "meshing_workflow" - from ansys.fluent.core.meshing.meshing_workflow_new import WorkflowMode - self._current_workflow = WorkflowMode.TWO_DIMENSIONAL_MESHING_MODE.value( - _make_datamodel_module(self, root_module), - self.meshing, - self.get_fluent_version(), - initialize, - ) + from ansys.fluent.core.meshing.meshing_workflow_new import ( + TwoDimensionalMeshingWorkflow, + ) + + self._current_workflow = TwoDimensionalMeshingWorkflow( + session=self, + initialize=initialize, + ) return self._current_workflow def topology_based_meshing_workflow( @@ -356,16 +377,22 @@ def topology_based_meshing_workflow( if legacy: root_module = "workflow" from ansys.fluent.core.meshing.meshing_workflow import WorkflowMode + + self._current_workflow = WorkflowMode.TOPOLOGY_BASED_MESHING_MODE.value( + _make_datamodel_module(self, root_module), + self.meshing, + self.get_fluent_version(), + initialize, + ) else: - root_module = "meshing_workflow" - from ansys.fluent.core.meshing.meshing_workflow_new import WorkflowMode - - self._current_workflow = WorkflowMode.TOPOLOGY_BASED_MESHING_MODE.value( - _make_datamodel_module(self, root_module), - self.meshing, - self.get_fluent_version(), - initialize, - ) + from ansys.fluent.core.meshing.meshing_workflow_new import ( + TopologyBasedMeshingWorkflow, + ) + + self._current_workflow = TopologyBasedMeshingWorkflow( + session=self, + initialize=initialize, + ) return self._current_workflow def load_workflow( @@ -407,15 +434,12 @@ def load_workflow( self.get_fluent_version(), ) else: - root_module = "meshing_workflow" from ansys.fluent.core.meshing.meshing_workflow_new import LoadWorkflow self._current_workflow = LoadWorkflow( - _make_datamodel_module(self, root_module), - self.meshing, - self.get_fluent_version(), - os.fspath(file_path), - initialize, + session=self, + file_path=os.fspath(file_path), + initialize=initialize, ) return self._current_workflow @@ -444,16 +468,20 @@ def create_workflow( if legacy: root_module = "workflow" from ansys.fluent.core.meshing.meshing_workflow import CreateWorkflow + + self._current_workflow = CreateWorkflow( + _make_datamodel_module(self, root_module), + self.meshing, + self.get_fluent_version(), + initialize, + ) else: - root_module = "meshing_workflow" from ansys.fluent.core.meshing.meshing_workflow_new import CreateWorkflow - self._current_workflow = CreateWorkflow( - _make_datamodel_module(self, root_module), - self.meshing, - self.get_fluent_version(), - initialize, - ) + self._current_workflow = CreateWorkflow( + session=self, + initialize=initialize, + ) return self._current_workflow def current_workflow( diff --git a/tests/test_server_meshing_workflow.py b/tests/test_server_meshing_workflow.py index 0590e4fff73..190dbb179e6 100644 --- a/tests/test_server_meshing_workflow.py +++ b/tests/test_server_meshing_workflow.py @@ -23,7 +23,20 @@ from conftest import SKIP_UNKNOWN import pytest -from ansys.fluent.core import FluentVersion, PyFluentUserWarning, examples +from ansys.fluent.core import ( + CreateWorkflow, + FaultTolerantMeshing, + FluentVersion, + PyFluentUserWarning, + TwoDimensionalMeshing, + WatertightMeshing, + examples, +) +from ansys.fluent.core.meshing.meshing_workflow_new import ( + FaultTolerantMeshingWorkflow, + TwoDimensionalMeshingWorkflow, + WatertightMeshingWorkflow, +) from ansys.fluent.core.services.datamodel_se import PyMenu @@ -2035,6 +2048,70 @@ def test_rename(new_meshing_session): assert watertight.import_geometry["IG"] +# --------------------------------------------------------------------------- +# Direct-construction tests +# --------------------------------------------------------------------------- + + +@pytest.mark.codegen_required +@pytest.mark.fluent_version(">=26.1") +def test_direct_construction_watertight(new_meshing_session): + """WatertightMeshing(session=meshing) should work identically to + meshing.watertight().""" + + meshing = new_meshing_session + watertight = WatertightMeshing(session=meshing) + + # Correct type + assert isinstance(watertight, WatertightMeshingWorkflow) + # Session's current_workflow is kept in sync + assert meshing.current_workflow is watertight + # Workflow is usable – the first task exists + assert watertight.import_geometry + + +@pytest.mark.codegen_required +@pytest.mark.fluent_version(">=26.1") +def test_direct_construction_fault_tolerant(new_meshing_session): + """FaultTolerantMeshing(session=meshing) should set up part management.""" + meshing = new_meshing_session + fault_tolerant = FaultTolerantMeshing(session=meshing) + + assert isinstance(fault_tolerant, FaultTolerantMeshingWorkflow) + assert meshing.current_workflow is fault_tolerant + assert fault_tolerant.import_cad_and_part_management + + +@pytest.mark.codegen_required +@pytest.mark.fluent_version(">=26.1") +def test_direct_construction_2d_meshing(new_meshing_session): + """TwoDimensionalMeshing(session=meshing) should produce a 2D workflow.""" + meshing = new_meshing_session + two_d = TwoDimensionalMeshing(session=meshing) + + assert isinstance(two_d, TwoDimensionalMeshingWorkflow) + assert meshing.current_workflow is two_d + + +@pytest.mark.codegen_required +@pytest.mark.fluent_version(">=26.1") +def test_direct_construction_create_workflow(new_meshing_session): + """CreateWorkflow(session=meshing) should create a blank workflow.""" + meshing = new_meshing_session + blank = CreateWorkflow(session=meshing) + + assert meshing.current_workflow is blank + + +@pytest.mark.codegen_required +@pytest.mark.fluent_version(">=26.1") +def test_direct_construction_invalid_session(): + """Passing a non-session object should raise TypeError.""" + + with pytest.raises(TypeError): + WatertightMeshing(session="not_a_session") + + @pytest.mark.fluent_version(">=27.1") def test_workflow_getattr_suggests_close_match(new_meshing_session): """AttributeError for a near-miss name should list the closest task name."""