diff --git a/flow360/component/simulation/entity_info.py b/flow360/component/simulation/entity_info.py index fc003c661..c5f236535 100644 --- a/flow360/component/simulation/entity_info.py +++ b/flow360/component/simulation/entity_info.py @@ -119,20 +119,27 @@ def _get_list_of_entities( entity_attribute_names = self.edge_attribute_names entity_full_list = self.grouped_edges + # Use the supplied one if not None if attribute_name is not None: - # pylint: disable=unsupported-membership-test,unsubscriptable-object - if attribute_name in entity_attribute_names: - # pylint: disable=no-member - return entity_full_list[entity_attribute_names.index(attribute_name)] - raise ValueError( - f"The given attribute_name {attribute_name} is not found" - f" in geometry metadata. Available: {entity_attribute_names}" + specified_attribute_name = attribute_name + else: + specified_attribute_name = ( + self.face_group_tag if entity_type_name == "face" else self.edge_group_tag ) - raise ValueError("Attribute name is required to get the full list of grouped entities.") + + # pylint: disable=unsupported-membership-test,unsubscriptable-object + if specified_attribute_name in entity_attribute_names: + # pylint: disable=no-member + return entity_full_list[entity_attribute_names.index(specified_attribute_name)] + raise ValueError( + f"The given attribute_name {attribute_name} is not found" + f" in geometry metadata. Available: {entity_attribute_names}" + ) def get_boundaries(self, attribute_name: str = None) -> list: """ - Get the full list of boundary names. If it is geometry then use supplied attribute name to get the list. + Get the full list of boundaries. + If attribute_name is supplied then ignore stored face_group_tag and use supplied one. """ return self._get_list_of_entities(attribute_name, "face") @@ -144,12 +151,24 @@ class VolumeMeshEntityInfo(EntityInfoModel): zones: list[GenericVolume] = pd.Field([]) boundaries: list[Surface] = pd.Field([]) + @pd.field_validator("boundaries", mode="after") + @classmethod + def check_all_surface_has_interface_indicator(cls, value): + """private_attribute_is_interface should have been set comming from volume mesh.""" + for item in value: + if item.private_attribute_is_interface is None: + raise ValueError( + "[INTERNAL] {item.name} is missing private_attribute_is_interface attribute!." + ) + return value + # pylint: disable=arguments-differ def get_boundaries(self) -> list: """ Get the full list of boundary names. If it is geometry then use supplied attribute name to get the list. """ - return self.boundaries + # pylint: disable=not-an-iterable + return [item for item in self.boundaries if item.private_attribute_is_interface is False] class SurfaceMeshEntityInfo(EntityInfoModel): diff --git a/flow360/component/simulation/framework/base_model.py b/flow360/component/simulation/framework/base_model.py index f869fe6ef..b5caca4ac 100644 --- a/flow360/component/simulation/framework/base_model.py +++ b/flow360/component/simulation/framework/base_model.py @@ -163,6 +163,7 @@ def one_of(cls, values): # pylint: disable=no-self-argument # pylint: disable=duplicate-code @pd.model_validator(mode="before") + @classmethod def handle_conflicting_fields(cls, values): """ root validator to handle deprecated aliases and fields diff --git a/flow360/component/simulation/framework/param_utils.py b/flow360/component/simulation/framework/param_utils.py index 1979b7e2a..4c6a86fa3 100644 --- a/flow360/component/simulation/framework/param_utils.py +++ b/flow360/component/simulation/framework/param_utils.py @@ -32,6 +32,15 @@ class AssetCache(Flow360BaseModel): Union[GeometryEntityInfo, VolumeMeshEntityInfo, SurfaceMeshEntityInfo] ] = pd.Field(None, frozen=True, discriminator="type_name") + @property + def boundaries(self): + """ + Get all boundaries from the cached entity info. + """ + if self.project_entity_info is None: + return None + return self.project_entity_info.get_boundaries() + def register_entity_list(model: Flow360BaseModel, registry: EntityRegistry) -> None: """ diff --git a/flow360/component/simulation/operating_condition/operating_condition.py b/flow360/component/simulation/operating_condition/operating_condition.py index 0336b9a60..3e66050de 100644 --- a/flow360/component/simulation/operating_condition/operating_condition.py +++ b/flow360/component/simulation/operating_condition/operating_condition.py @@ -311,11 +311,15 @@ def from_mach( reference_velocity_magnitude=reference_velocity_magnitude, ) - @context_validator(context=CASE) @pd.model_validator(mode="after") + @context_validator(context=CASE) def check_valid_reference_velocity(self) -> Self: """Ensure reference velocity is provided when freestream velocity is 0.""" - if self.velocity_magnitude.value == 0 and self.reference_velocity_magnitude is None: + if ( + self.velocity_magnitude is not None + and self.velocity_magnitude.value == 0 + and self.reference_velocity_magnitude is None + ): raise ValueError( "Reference velocity magnitude/Mach must be provided when freestream velocity magnitude/Mach is 0." ) diff --git a/flow360/component/simulation/simulation_params.py b/flow360/component/simulation/simulation_params.py index e81014217..033f1c375 100644 --- a/flow360/component/simulation/simulation_params.py +++ b/flow360/component/simulation/simulation_params.py @@ -62,6 +62,7 @@ ) from flow360.component.simulation.validation.validation_simulation_params import ( _check_cht_solver_settings, + _check_complete_boundary_condition_and_unknown_surface, _check_consistency_ddes_volume_output, _check_consistency_wall_function_and_surface_output, _check_duplicate_entities_in_models, @@ -76,7 +77,13 @@ from flow360.exceptions import Flow360ConfigurationError, Flow360RuntimeError from flow360.version import __version__ -from .validation.validation_context import SURFACE_MESH, CaseField, ContextField +from .validation.validation_context import ( + CASE, + SURFACE_MESH, + CaseField, + ContextField, + context_validator, +) ModelTypes = Annotated[Union[VolumeModelTypes, SurfaceModelTypes], pd.Field(discriminator="type")] @@ -162,6 +169,7 @@ def copy(self, update=None, **kwargs) -> _ParamModelBase: return super().copy(update=update, **kwargs) +# pylint: disable=too-many-public-methods class SimulationParams(_ParamModelBase): """All-in-one class for surface meshing + volume meshing + case configurations""" @@ -273,16 +281,14 @@ def check_cht_solver_settings(self): return _check_cht_solver_settings(self) @pd.model_validator(mode="after") - @classmethod - def check_consistency_wall_function_and_surface_output(cls, v): + def check_consistency_wall_function_and_surface_output(self): """Only allow wallFunctionMetric output field when there is a Wall model with a wall function enabled""" - return _check_consistency_wall_function_and_surface_output(v) + return _check_consistency_wall_function_and_surface_output(self) @pd.model_validator(mode="after") - @classmethod - def check_consistency_ddes_volume_output(cls, v): + def check_consistency_ddes_volume_output(self): """Only allow DDES output field when there is a corresponding solver with DDES enabled in models""" - return _check_consistency_ddes_volume_output(v) + return _check_consistency_ddes_volume_output(self) @pd.model_validator(mode="after") def check_duplicate_entities_in_models(self): @@ -290,16 +296,20 @@ def check_duplicate_entities_in_models(self): return _check_duplicate_entities_in_models(self) @pd.model_validator(mode="after") - @classmethod - def check_numerical_dissipation_factor_output(cls, v): + def check_numerical_dissipation_factor_output(self): """Only allow numericalDissipationFactor output field when the NS solver has low numerical dissipation""" - return _check_numerical_dissipation_factor_output(v) + return _check_numerical_dissipation_factor_output(self) @pd.model_validator(mode="after") - @classmethod - def check_low_mach_preconditioner_output(cls, v): + def check_low_mach_preconditioner_output(self): """Only allow lowMachPreconditioner output field when the lowMachPreconditioner is enabled in the NS solver""" - return _check_low_mach_preconditioner_output(v) + return _check_low_mach_preconditioner_output(self) + + @pd.model_validator(mode="after") + @context_validator(context=CASE) + def check_complete_boundary_condition_and_unknown_surface(self): + """Make sure that all boundaries have been assigned with a boundary condition""" + return _check_complete_boundary_condition_and_unknown_surface(self) @pd.model_validator(mode="after") def check_output_fields(params): diff --git a/flow360/component/simulation/translator/surface_meshing_translator.py b/flow360/component/simulation/translator/surface_meshing_translator.py index 927b4ef7f..41b0b4d6d 100644 --- a/flow360/component/simulation/translator/surface_meshing_translator.py +++ b/flow360/component/simulation/translator/surface_meshing_translator.py @@ -127,11 +127,8 @@ def get_surface_meshing_json(input_params: SimulationParams, mesh_units): ##:: >> Step 6: Tell surface mesher how do we group boundaries. translated["boundaries"] = {} - face_group_tag = input_params.private_attribute_asset_cache.project_entity_info.face_group_tag grouped_faces: List[Surface] = ( - input_params.private_attribute_asset_cache.project_entity_info.get_boundaries( - face_group_tag - ) + input_params.private_attribute_asset_cache.project_entity_info.get_boundaries() ) for surface in grouped_faces: for face_id in surface.private_attribute_sub_components: diff --git a/flow360/component/simulation/validation/validation_simulation_params.py b/flow360/component/simulation/validation/validation_simulation_params.py index d0675fb8b..7798527e9 100644 --- a/flow360/component/simulation/validation/validation_simulation_params.py +++ b/flow360/component/simulation/validation/validation_simulation_params.py @@ -2,8 +2,10 @@ validation for SimulationParams """ +from typing import get_args + from flow360.component.simulation.models.solver_numerics import NoneSolver -from flow360.component.simulation.models.surface_models import Wall +from flow360.component.simulation.models.surface_models import SurfaceModelTypes, Wall from flow360.component.simulation.models.volume_models import ( Fluid, NavierStokesInitialCondition, @@ -18,6 +20,7 @@ VolumeOutput, ) from flow360.component.simulation.primitives import ( + GhostSurface, _SurfaceEntityBase, _VolumeEntityBase, ) @@ -272,6 +275,65 @@ def _validate_cht_has_heat_transfer(params): return params +def _check_complete_boundary_condition_and_unknown_surface(params): + ## Step 1: Get all boundaries patches from asset cache + + current_lvls = get_validation_levels() if get_validation_levels() else [] + if all(level not in current_lvls for level in (ALL, CASE)): + return params + + asset_boundary_entities = params.private_attribute_asset_cache.boundaries + + if asset_boundary_entities is None or asset_boundary_entities == []: + raise ValueError("[Internal] Failed to retrieve asset boundaries") + + asset_boundaries = {boundary.name for boundary in asset_boundary_entities} + + ## Step 2: Collect all used boundaries from the models + if len(params.models) == 1 and isinstance(params.models[0], Fluid): + raise ValueError("No boundary conditions are defined in the `models` section.") + + used_boundaries = set() + + for model in params.models: + if not isinstance(model, get_args(SurfaceModelTypes)): + continue + + entities = [] + # pylint: disable=protected-access + if hasattr(model, "entities"): + entities = model.entities._get_expanded_entities(create_hard_copy=False) + elif hasattr(model, "entity_pairs"): # Periodic BC + entities = [ + pair for surface_pair in model.entity_pairs.items for pair in surface_pair.pair + ] + + for entity in entities: + if isinstance(entity, GhostSurface): + continue + used_boundaries.add(entity.name) + + ## Step 3: Use set operations to find missing and unknown boundaries + missing_boundaries = asset_boundaries - used_boundaries + unknown_boundaries = used_boundaries - asset_boundaries + + if missing_boundaries: + missing_list = ", ".join(sorted(missing_boundaries)) + raise ValueError( + f"The following boundaries do not have a boundary condition: {missing_list}. " + "Please add them to a boundary condition model in the `models` section." + ) + + if unknown_boundaries: + unknown_list = ", ".join(sorted(unknown_boundaries)) + raise ValueError( + f"The following boundaries are not known `Surface` " + f"entities but appear in the `models` section: {unknown_list}." + ) + + return params + + def _check_parent_volume_is_rotating(models): current_lvls = get_validation_levels() if get_validation_levels() else [] diff --git a/tests/ref/simulation/simulation_json_with_multi_constructor_used.json b/tests/ref/simulation/simulation_json_with_multi_constructor_used.json index 3cfc12768..7e83541f9 100644 --- a/tests/ref/simulation/simulation_json_with_multi_constructor_used.json +++ b/tests/ref/simulation/simulation_json_with_multi_constructor_used.json @@ -186,6 +186,25 @@ ] }, "models": [ + { + "type": "Wall", + "entities": { + "stored_entities": [ + { + "private_attribute_registry_bucket_name": "SurfaceEntityType", + "private_attribute_entity_type_name": "Surface", + "name": "surface_x", + "private_attribute_is_interface": false, + "private_attribute_sub_components": [ + "face_x_1", + "face_x_2", + "face_x_3" + ] + } + ] + }, + "use_wall_function": false + }, { "material": { "dynamic_viscosity": { @@ -371,5 +390,35 @@ "unit_system": { "name": "SI" }, - "version": "24.2.0" -} + "version": "24.2.0", + "private_attribute_asset_cache": { + "project_length_unit": "m", + "project_entity_info": { + "type_name": "GeometryEntityInfo", + "face_ids": [ + "face_x_1", + "face_x_2", + "face_x_3" + ], + "face_group_tag": "some_tag", + "face_attribute_names": [ + "some_tag" + ], + "grouped_faces": [ + [ + { + "private_attribute_registry_bucket_name": "SurfaceEntityType", + "private_attribute_entity_type_name": "Surface", + "name": "surface_x", + "private_attribute_is_interface": false, + "private_attribute_sub_components": [ + "face_x_1", + "face_x_2", + "face_x_3" + ] + } + ] + ] + } + } +} \ No newline at end of file diff --git a/tests/simulation/params/test_validators_params.py b/tests/simulation/params/test_validators_params.py index d63b11c71..c8b52820b 100644 --- a/tests/simulation/params/test_validators_params.py +++ b/tests/simulation/params/test_validators_params.py @@ -1,11 +1,22 @@ +import json import re import unittest +from typing import Literal import pytest import flow360.component.simulation.units as u +from flow360.component.simulation.entity_info import VolumeMeshEntityInfo +from flow360.component.simulation.framework.param_utils import AssetCache +from flow360.component.simulation.meshing_param.volume_params import AutomatedFarfield from flow360.component.simulation.models.material import SolidMaterial, aluminum -from flow360.component.simulation.models.surface_models import SlipWall, Wall +from flow360.component.simulation.models.surface_models import ( + Freestream, + Periodic, + SlipWall, + Translational, + Wall, +) from flow360.component.simulation.models.volume_models import ( AngleExpression, Fluid, @@ -14,6 +25,9 @@ Rotation, Solid, ) +from flow360.component.simulation.operating_condition.operating_condition import ( + AerospaceCondition, +) from flow360.component.simulation.outputs.output_entities import Point, Slice from flow360.component.simulation.outputs.outputs import ( ProbeOutput, @@ -28,7 +42,10 @@ from flow360.component.simulation.time_stepping.time_stepping import Steady, Unsteady from flow360.component.simulation.unit_system import SI_unit_system from flow360.component.simulation.validation.validation_context import ( + ALL, CASE, + SURFACE_MESH, + VOLUME_MESH, ValidationLevelContext, ) @@ -307,6 +324,75 @@ def test_cht_solver_settings_validator( ) +def test_incomplete_BC(): + ##:: Construct a dummy asset cache + + wall_1 = Surface( + name="wall_1", private_attribute_is_interface=False, private_attribute_tag_key="test" + ) + periodic_1 = Surface( + name="periodic_1", private_attribute_is_interface=False, private_attribute_tag_key="test" + ) + periodic_2 = Surface( + name="periodic_2", private_attribute_is_interface=False, private_attribute_tag_key="test" + ) + i_exist = Surface( + name="i_exist", private_attribute_is_interface=False, private_attribute_tag_key="test" + ) + no_bc = Surface( + name="no_bc", private_attribute_is_interface=False, private_attribute_tag_key="test" + ) + some_interface = Surface( + name="some_interface", private_attribute_is_interface=True, private_attribute_tag_key="test" + ) + + asset_cache = AssetCache( + project_length_unit="inch", + project_entity_info=VolumeMeshEntityInfo( + boundaries=[wall_1, periodic_1, periodic_2, i_exist, some_interface, no_bc] + ), + ) + auto_farfield = AutomatedFarfield(name="my_farfield") + + with pytest.raises( + ValueError, + match=re.escape( + r"The following boundaries do not have a boundary condition: no_bc. Please add them to a boundary condition model in the `models` section." + ), + ): + with ValidationLevelContext(ALL): + with SI_unit_system: + SimulationParams( + models=[ + Fluid(), + Wall(entities=wall_1), + Periodic(surface_pairs=(periodic_1, periodic_2), spec=Translational()), + SlipWall(entities=[i_exist]), + Freestream(entities=auto_farfield.farfield), + ], + private_attribute_asset_cache=asset_cache, + ) + with pytest.raises( + ValueError, + match=re.escape( + r"The following boundaries are not known `Surface` entities but appear in the `models` section: plz_dont_do_this." + ), + ): + with ValidationLevelContext(ALL): + with SI_unit_system: + SimulationParams( + models=[ + Fluid(), + Wall(entities=[wall_1]), + Periodic(surface_pairs=(periodic_1, periodic_2), spec=Translational()), + SlipWall(entities=[i_exist]), + Freestream(entities=auto_farfield.farfield), + SlipWall(entities=[Surface(name="plz_dont_do_this"), no_bc]), + ], + private_attribute_asset_cache=asset_cache, + ) + + def test_duplicate_entities_in_models(): entity_generic_volume = GenericVolume(name="Duplicate Volume") entity_surface = Surface(name="Duplicate Surface") @@ -340,6 +426,17 @@ def test_duplicate_entities_in_models(): ) +def test_valid_reference_velocity(): + with pytest.raises( + ValueError, + match=re.escape( + "Reference velocity magnitude/Mach must be provided when freestream velocity magnitude/Mach is 0." + ), + ): + with SI_unit_system: + SimulationParams(operating_condition=AerospaceCondition(velocity_magnitude=0)) + + def test_output_fields_with_user_defined_fields(): surface_1 = Surface(name="some_random_surface") # 1: No user defined fields @@ -444,6 +541,8 @@ def test_rotation_parent_volumes(): axis=(0, 1, 2), ) + my_wall = Surface(name="my_wall", private_attribute_is_interface=False) + msg = "For model #1, the parent rotating volume (stationary_cylinder) is not " "used in any other `Rotation` model's `volumes`." with pytest.raises(ValueError, match=re.escape(msg)): @@ -463,5 +562,10 @@ def test_rotation_parent_volumes(): Fluid(), Rotation(entities=[c_1], spec=AngleExpression("1+2"), parent_volume=c_2), Rotation(entities=[c_2], spec=AngleExpression("1+5")), - ] + Wall(entities=[my_wall]), + ], + private_attribute_asset_cache=AssetCache( + project_length_unit="cm", + project_entity_info=VolumeMeshEntityInfo(boundaries=[my_wall]), + ), ) diff --git a/tests/simulation/service/test_services_v2.py b/tests/simulation/service/test_services_v2.py index 0d73c84df..586052566 100644 --- a/tests/simulation/service/test_services_v2.py +++ b/tests/simulation/service/test_services_v2.py @@ -4,8 +4,9 @@ import pytest from flow360.component.simulation import services -from flow360.component.simulation.simulation_params import SimulationParams -from flow360.component.simulation.unit_system import SI_unit_system +from flow360.component.simulation.entity_info import VolumeMeshEntityInfo +from flow360.component.simulation.framework.param_utils import AssetCache +from flow360.component.simulation.primitives import Surface from flow360.component.simulation.validation.validation_context import ( SURFACE_MESH, VOLUME_MESH, @@ -51,7 +52,43 @@ def test_validate_service(): "max_steps": 10, "CFL": {"type": "ramp", "initial": 1.5, "final": 1.5, "ramp_steps": 5}, }, + "models": [ + { + "type": "Wall", + "entities": { + "stored_entities": [ + { + "private_attribute_registry_bucket_name": "SurfaceEntityType", + "private_attribute_entity_type_name": "Surface", + "name": "Mysurface", + "private_attribute_is_interface": False, + "private_attribute_sub_components": [], + } + ] + }, + "use_wall_function": False, + } + ], "user_defined_dynamics": [], + "private_attribute_asset_cache": { + "project_length_unit": None, + "project_entity_info": { + "draft_entities": [], + "type_name": "VolumeMeshEntityInfo", + "zones": [], + "boundaries": [ + { + "private_attribute_registry_bucket_name": "SurfaceEntityType", + "private_attribute_entity_type_name": "Surface", + "name": "Mysurface", + "private_attribute_full_name": None, + "private_attribute_is_interface": False, + "private_attribute_tag_key": None, + "private_attribute_sub_components": [], + } + ], + }, + }, } params_data_from_geo = params_data_from_vm @@ -429,6 +466,51 @@ def test_front_end_JSON_with_multi_constructor(): }, "unit_system": {"name": "SI"}, "version": "24.2.0", + "private_attribute_asset_cache": { + "project_length_unit": "m", + "project_entity_info": { + "type_name": "GeometryEntityInfo", + "face_ids": ["face_x_1", "face_x_2", "face_x_3"], + "face_group_tag": "some_tag", + "face_attribute_names": ["some_tag"], + "grouped_faces": [ + [ + { + "private_attribute_registry_bucket_name": "SurfaceEntityType", + "private_attribute_entity_type_name": "Surface", + "name": "surface_x", + "private_attribute_is_interface": False, + "private_attribute_sub_components": [ + "face_x_1", + "face_x_2", + "face_x_3", + ], + } + ] + ], + }, + }, + "models": [ + { + "type": "Wall", + "entities": { + "stored_entities": [ + { + "private_attribute_registry_bucket_name": "SurfaceEntityType", + "private_attribute_entity_type_name": "Surface", + "name": "surface_x", + "private_attribute_is_interface": False, + "private_attribute_sub_components": [ + "face_x_1", + "face_x_2", + "face_x_3", + ], + } + ] + }, + "use_wall_function": False, + } + ], "operating_condition": { "type_name": "AerospaceCondition", "private_attribute_constructor": "from_mach", @@ -494,13 +576,49 @@ def test_generate_process_json(): "beta": {"value": 0.0, "units": "degree"}, # "velocity_magnitude": {"value": 0.8, "units": "km/s"}, }, + "models": [ + { + "type": "Wall", + "entities": { + "stored_entities": [ + { + "private_attribute_registry_bucket_name": "SurfaceEntityType", + "private_attribute_entity_type_name": "Surface", + "name": "surface_x", + "private_attribute_is_interface": False, + "private_attribute_sub_components": [ + "face_x_1", + "face_x_2", + "face_x_3", + ], + } + ] + }, + "use_wall_function": False, + } + ], "private_attribute_asset_cache": { "project_length_unit": "m", "project_entity_info": { "type_name": "GeometryEntityInfo", - "face_ids": ["face_x"], - "face_group_tag": "not_used", - "face_attribute_names": ["not_used"], + "face_ids": ["face_x_1", "face_x_2", "face_x_3"], + "face_group_tag": "some_tag", + "face_attribute_names": ["some_tag"], + "grouped_faces": [ + [ + { + "private_attribute_registry_bucket_name": "SurfaceEntityType", + "private_attribute_entity_type_name": "Surface", + "name": "surface_x", + "private_attribute_is_interface": False, + "private_attribute_sub_components": [ + "face_x_1", + "face_x_2", + "face_x_3", + ], + } + ] + ], }, }, } diff --git a/tests/simulation/service/test_translator_service.py b/tests/simulation/service/test_translator_service.py index df0aa2439..f553422c8 100644 --- a/tests/simulation/service/test_translator_service.py +++ b/tests/simulation/service/test_translator_service.py @@ -397,6 +397,46 @@ def test_simulation_to_case_json(): ], "unit_system": {"name": "SI"}, "version": "24.2.0", + "private_attribute_asset_cache": { + "project_length_unit": "m", + "project_entity_info": { + "type_name": "GeometryEntityInfo", + "face_ids": ["face_x_1", "face_x_2", "face_x_3"], + "face_group_tag": "some_tag", + "face_attribute_names": ["some_tag"], + "grouped_faces": [ + [ + { + "private_attribute_registry_bucket_name": "SurfaceEntityType", + "private_attribute_entity_type_name": "Surface", + "name": "1", + "private_attribute_is_interface": False, + "private_attribute_sub_components": [ + "face_x_1", + ], + }, + { + "private_attribute_registry_bucket_name": "SurfaceEntityType", + "private_attribute_entity_type_name": "Surface", + "name": "3", + "private_attribute_is_interface": False, + "private_attribute_sub_components": [ + "face_x_3", + ], + }, + { + "private_attribute_registry_bucket_name": "SurfaceEntityType", + "private_attribute_entity_type_name": "Surface", + "name": "2", + "private_attribute_is_interface": False, + "private_attribute_sub_components": [ + "face_x_2", + ], + }, + ] + ], + }, + }, } params, _, _ = validate_model(param_data, "SI", "Geometry") @@ -461,6 +501,42 @@ def test_simulation_to_case_vm_workflow(): }, "unit_system": {"name": "SI"}, "version": "24.2.0", + "models": [ + { + "type": "Wall", + "entities": { + "stored_entities": [ + { + "private_attribute_registry_bucket_name": "SurfaceEntityType", + "private_attribute_entity_type_name": "Surface", + "name": "Mysurface", + "private_attribute_is_interface": False, + "private_attribute_sub_components": [], + } + ] + }, + "use_wall_function": False, + } + ], + "private_attribute_asset_cache": { + "project_length_unit": None, + "project_entity_info": { + "draft_entities": [], + "type_name": "VolumeMeshEntityInfo", + "zones": [], + "boundaries": [ + { + "private_attribute_registry_bucket_name": "SurfaceEntityType", + "private_attribute_entity_type_name": "Surface", + "name": "Mysurface", + "private_attribute_full_name": None, + "private_attribute_is_interface": False, + "private_attribute_tag_key": None, + "private_attribute_sub_components": [], + } + ], + }, + }, } params, _, _ = validate_model(param_data, "SI", "Geometry") @@ -469,7 +545,7 @@ def test_simulation_to_case_vm_workflow(): case_json, hash = simulation_to_case_json(params, {"value": 100.0, "units": "cm"}) print(case_json) - params, _, _ = validate_model(param_data, "SI", "VolumeMesh") + params, errors, _ = validate_model(param_data, "SI", "VolumeMesh") case_json, hash = simulation_to_case_json(params, {"value": 100.0, "units": "cm"}) print(case_json) @@ -510,14 +586,50 @@ def test_simulation_to_all_translation_2(): "moment_length": {"value": 1, "units": "m"}, "area": {"value": 1, "units": "m**2"}, }, - "models": [], + "models": [ + { + "type": "Wall", + "entities": { + "stored_entities": [ + { + "private_attribute_registry_bucket_name": "SurfaceEntityType", + "private_attribute_entity_type_name": "Surface", + "name": "surface_x", + "private_attribute_is_interface": False, + "private_attribute_sub_components": [ + "face_x_1", + "face_x_2", + "face_x_3", + ], + } + ] + }, + "use_wall_function": False, + } + ], "private_attribute_asset_cache": { + "project_length_unit": "m", "project_entity_info": { "type_name": "GeometryEntityInfo", - "face_ids": ["face_x"], - "face_group_tag": "not_used", - "face_attribute_names": ["not_used"], - } + "face_ids": ["face_x_1", "face_x_2", "face_x_3"], + "face_group_tag": "some_tag", + "face_attribute_names": ["some_tag"], + "grouped_faces": [ + [ + { + "private_attribute_registry_bucket_name": "SurfaceEntityType", + "private_attribute_entity_type_name": "Surface", + "name": "surface_x", + "private_attribute_is_interface": False, + "private_attribute_sub_components": [ + "face_x_1", + "face_x_2", + "face_x_3", + ], + } + ] + ], + }, }, }