diff --git a/flow360/__init__.py b/flow360/__init__.py index 1fb3abf20..2b5e65742 100644 --- a/flow360/__init__.py +++ b/flow360/__init__.py @@ -32,6 +32,7 @@ AutomatedFarfield, AxisymmetricRefinement, CustomZones, + MeshSliceOutput, RotationCylinder, RotationVolume, StructuredBoxRefinement, @@ -190,6 +191,7 @@ "Accounts", "Project", "u", + "MeshSliceOutput", "SimulationParams", "SI_unit_system", "imperial_unit_system", diff --git a/flow360/component/simulation/meshing_param/params.py b/flow360/component/simulation/meshing_param/params.py index 17a3064f1..b01603fe9 100644 --- a/flow360/component/simulation/meshing_param/params.py +++ b/flow360/component/simulation/meshing_param/params.py @@ -19,6 +19,7 @@ AutomatedFarfield, AxisymmetricRefinement, CustomZones, + MeshSliceOutput, RotationCylinder, RotationVolume, StructuredBoxRefinement, @@ -60,6 +61,11 @@ pd.Field(discriminator="type"), ] +MeshOutputTypes = Annotated[ + Union[MeshSliceOutput,], + pd.Field(discriminator="output_type"), +] + class MeshingDefaults(Flow360BaseModel): """ @@ -298,6 +304,12 @@ class MeshingParams(Flow360BaseModel): default=None, description="Creation of new volume zones." ) + # Meshing outputs (for now, volume mesh slices) + outputs: List[MeshOutputTypes] = pd.Field( + default=[], + description="Mesh output settings.", + ) + @pd.field_validator("volume_zones", mode="after") @classmethod def _check_volume_zones_has_farfied(cls, v): diff --git a/flow360/component/simulation/meshing_param/volume_params.py b/flow360/component/simulation/meshing_param/volume_params.py index 34a7445fa..a16eafc8a 100644 --- a/flow360/component/simulation/meshing_param/volume_params.py +++ b/flow360/component/simulation/meshing_param/volume_params.py @@ -10,6 +10,7 @@ from flow360.component.simulation.framework.base_model import Flow360BaseModel from flow360.component.simulation.framework.entity_base import EntityList +from flow360.component.simulation.outputs.output_entities import Slice from flow360.component.simulation.primitives import ( AxisymmetricBody, Box, @@ -490,6 +491,34 @@ def symmetry_plane(self) -> GhostSurface: return GhostSurface(name="symmetric") +class MeshSliceOutput(Flow360BaseModel): + """ + :class:`MeshSliceOutput` class for mesh slice output settings. + + Example + ------- + + >>> fl.MeshSliceOutput( + ... slices=[ + ... fl.Slice( + ... name="Slice_1", + ... normal=(0, 1, 0), + ... origin=(0, 0.56, 0)*fl.u.m + ... ), + ... ], + ... ) + + ==== + """ + + name: str = pd.Field("Mesh slice output", description="Name of the `MeshSliceOutput`.") + entities: EntityList[Slice] = pd.Field( + alias="slices", + description="List of output :class:`~flow360.Slice` entities.", + ) + output_type: Literal["MeshSliceOutput"] = pd.Field("MeshSliceOutput", frozen=True) + + class CustomZones(Flow360BaseModel): """ :class:`CustomZones` class for creating volume zones from custom volumes. diff --git a/flow360/component/simulation/translator/volume_meshing_translator.py b/flow360/component/simulation/translator/volume_meshing_translator.py index 0f0a663bd..7a347549d 100644 --- a/flow360/component/simulation/translator/volume_meshing_translator.py +++ b/flow360/component/simulation/translator/volume_meshing_translator.py @@ -10,6 +10,7 @@ AutomatedFarfield, AxisymmetricRefinement, AxisymmetricRefinementBase, + MeshSliceOutput, RotationCylinder, RotationVolume, StructuredBoxRefinement, @@ -23,8 +24,10 @@ Surface, ) from flow360.component.simulation.simulation_params import SimulationParams +from flow360.component.simulation.translator.solver_translator import inject_slice_info from flow360.component.simulation.translator.utils import ( get_global_setting_from_first_instance, + has_instance_in_list, preprocess_input, translate_setting_and_apply_to_all_entities, ) @@ -227,6 +230,23 @@ def _get_custom_volumes(volume_zones: list): return custom_volumes +def translate_mesh_slice_output( + output_params: list, + output_class: Union[MeshSliceOutput], + injection_function, +): + """Translate slice or isosurface output settings.""" + translated_output = {} + translated_output["slices"] = translate_setting_and_apply_to_all_entities( + output_params, + output_class, + translation_func=lambda x: {}, + to_list=False, + entity_injection_func=injection_function, + ) + return translated_output + + @preprocess_input # pylint: disable=unused-argument,too-many-branches,too-many-statements def get_volume_meshing_json(input_params: SimulationParams, mesh_units): @@ -409,4 +429,16 @@ def get_volume_meshing_json(input_params: SimulationParams, mesh_units): if custom_volumes: translated["zones"] = custom_volumes + ##:: Step 7: Get meshing output fields + outputs = input_params.meshing.outputs + + mesh_slice_output_configs = [ + (MeshSliceOutput, "meshSliceOutput"), + ] + for output_class, output_key in mesh_slice_output_configs: + if has_instance_in_list(outputs, output_class): + slice_output = translate_mesh_slice_output(outputs, output_class, inject_slice_info) + if slice_output: + translated[output_key] = slice_output + return translated diff --git a/tests/ref/simulation/service_init_geometry.json b/tests/ref/simulation/service_init_geometry.json index e6273bb0e..ee50ddad4 100644 --- a/tests/ref/simulation/service_init_geometry.json +++ b/tests/ref/simulation/service_init_geometry.json @@ -23,6 +23,7 @@ }, "remove_non_manifold_faces": false }, + "outputs": [], "refinements": [], "volume_zones": [ { diff --git a/tests/ref/simulation/service_init_surface_mesh.json b/tests/ref/simulation/service_init_surface_mesh.json index 3edfa4a51..bcdd03121 100644 --- a/tests/ref/simulation/service_init_surface_mesh.json +++ b/tests/ref/simulation/service_init_surface_mesh.json @@ -23,6 +23,7 @@ }, "remove_non_manifold_faces": false }, + "outputs": [], "refinements": [], "volume_zones": [ { diff --git a/tests/simulation/translator/ref/volume_meshing/ref_param_to_json_inhouse.json b/tests/simulation/translator/ref/volume_meshing/ref_param_to_json_inhouse.json index 170fdcd2e..0f3757bc0 100644 --- a/tests/simulation/translator/ref/volume_meshing/ref_param_to_json_inhouse.json +++ b/tests/simulation/translator/ref/volume_meshing/ref_param_to_json_inhouse.json @@ -17,6 +17,18 @@ "passive1": {"type": "projectAnisoSpacing"}, "passive2": {"type": "none"} }, + "meshSliceOutput": { + "slices": { + "test_slice_y_normal": { + "sliceOrigin": [0.1, 0.2, 0.3], + "sliceNormal": [0.0, 1.0, 0.0] + }, + "test_slice_z_normal": { + "sliceOrigin": [0.6, 0.1, 0.4], + "sliceNormal": [0.0, 0.0, 1.0] + } + } + }, "refinement": [ { "type": "cylinder", diff --git a/tests/simulation/translator/ref/volume_meshing/ref_param_to_json_legacy.json b/tests/simulation/translator/ref/volume_meshing/ref_param_to_json_legacy.json index 1313d6b3f..b68990f27 100644 --- a/tests/simulation/translator/ref/volume_meshing/ref_param_to_json_legacy.json +++ b/tests/simulation/translator/ref/volume_meshing/ref_param_to_json_legacy.json @@ -15,6 +15,18 @@ "passive1": {"type": "projectAnisoSpacing"}, "passive2": {"type": "none"} }, + "meshSliceOutput": { + "slices": { + "test_slice_y_normal": { + "sliceOrigin": [0.1, 0.2, 0.3], + "sliceNormal": [0.0, 1.0, 0.0] + }, + "test_slice_z_normal": { + "sliceOrigin": [0.6, 0.1, 0.4], + "sliceNormal": [0.0, 0.0, 1.0] + } + } + }, "refinement": [ { "type": "cylinder", diff --git a/tests/simulation/translator/test_volume_meshing_translator.py b/tests/simulation/translator/test_volume_meshing_translator.py index fb89ae680..ef7f13ae8 100644 --- a/tests/simulation/translator/test_volume_meshing_translator.py +++ b/tests/simulation/translator/test_volume_meshing_translator.py @@ -19,11 +19,14 @@ AutomatedFarfield, AxisymmetricRefinement, CustomZones, + MeshSliceOutput, + RotationCylinder, RotationVolume, StructuredBoxRefinement, UniformRefinement, UserDefinedFarfield, ) +from flow360.component.simulation.outputs.outputs import Slice from flow360.component.simulation.primitives import ( AxisymmetricBody, Box, @@ -249,6 +252,23 @@ def get_test_param(): spacing_circumferential=20 * u.cm, ), ], + outputs=[ + MeshSliceOutput( + name="slice_output", + entities=[ + Slice( + name=f"test_slice_y_normal", + origin=(0.1, 0.2, 0.3), + normal=(0, 1, 0), + ), + Slice( + name=f"test_slice_z_normal", + origin=(0.6, 0.1, 0.4), + normal=(0, 0, 1), + ), + ], + ), + ], ), private_attribute_asset_cache=AssetCache(use_inhouse_mesher=True), )