Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions flow360/component/simulation/meshing_param/meshing_validators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""Shared validation helpers for meshing parameters."""

import flow360.component.simulation.units as u
from flow360.component.simulation.meshing_param.volume_params import UniformRefinement
from flow360.component.simulation.primitives import Box, Cylinder


def validate_snappy_uniform_refinement_entities(refinement: UniformRefinement):
"""Validate that a UniformRefinement's entities are compatible with snappyHexMesh.

Raises ValueError if any Box has a non-axis-aligned rotation or any Cylinder is hollow.
"""
# pylint: disable=no-member
for entity in refinement.entities.stored_entities:
if (
isinstance(entity, Box)
and entity.angle_of_rotation.to("deg") % (360 * u.deg) != 0 * u.deg
):
raise ValueError(
"UniformRefinement for snappy accepts only Boxes with axes aligned"
+ " with the global coordinate system (angle_of_rotation=0)."
)
if (
isinstance(entity, Cylinder)
and entity.inner_radius is not None
and entity.inner_radius.to("m") != 0 * u.m
):
raise ValueError(
"UniformRefinement for snappy accepts only full cylinders (where inner_radius = 0)."
)
16 changes: 16 additions & 0 deletions flow360/component/simulation/meshing_param/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
MeshingDefaults,
VolumeMeshingDefaults,
)
from flow360.component.simulation.meshing_param.meshing_validators import (
validate_snappy_uniform_refinement_entities,
)
from flow360.component.simulation.meshing_param.volume_params import (
AutomatedFarfield,
AxisymmetricRefinement,
Expand Down Expand Up @@ -589,6 +592,19 @@ def _check_sizing_against_octree_series(self, param_info: ParamsValidationInfo):

return self

@contextual_model_validator(mode="after")
def _check_snappy_uniform_refinement_entities(self, param_info: ParamsValidationInfo):
"""Validate projected UniformRefinement entities are compatible with snappyHexMesh."""
if not param_info.use_snappy:
return self
for refinement in self.refinements: # pylint: disable=not-an-iterable
if (
isinstance(refinement, UniformRefinement)
and refinement.project_to_surface is not False
):
validate_snappy_uniform_refinement_entities(refinement)
return self


SurfaceMeshingParams = Annotated[
Union[snappy.SurfaceMeshingParams], pd.Field(discriminator="type_name")
Expand Down
20 changes: 4 additions & 16 deletions flow360/component/simulation/meshing_param/snappy/snappy_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

import pydantic as pd

import flow360.component.simulation.units as u
from flow360.component.simulation.framework.base_model import Flow360BaseModel
from flow360.component.simulation.meshing_param.meshing_specs import OctreeSpacing
from flow360.component.simulation.meshing_param.meshing_validators import (
validate_snappy_uniform_refinement_entities,
)
from flow360.component.simulation.meshing_param.snappy.snappy_mesh_refinements import (
BodyRefinement,
RegionRefinement,
Expand All @@ -22,7 +24,6 @@
SurfaceMeshingDefaults,
)
from flow360.component.simulation.meshing_param.volume_params import UniformRefinement
from flow360.component.simulation.primitives import Box, Cylinder
from flow360.component.simulation.unit_system import LengthType
from flow360.component.simulation.validation.validation_context import (
ParamsValidationInfo,
Expand Down Expand Up @@ -124,20 +125,7 @@ def _check_uniform_refinement_entities(self):
return self
for refinement in self.refinements:
if isinstance(refinement, UniformRefinement):
# No expansion needed since we only allow Draft entities here.
for entity in refinement.entities.stored_entities:
if (
isinstance(entity, Box)
and entity.angle_of_rotation.to("deg") % (360 * u.deg) != 0 * u.deg
):
raise ValueError(
"UniformRefinement for snappy accepts only Boxes with axes aligned"
+ " with the global coordinate system (angle_of_rotation=0)."
)
if isinstance(entity, Cylinder) and entity.inner_radius.to("m") != 0 * u.m:
raise ValueError(
"UniformRefinement for snappy accepts only full cylinders (where inner_radius = 0)."
)
validate_snappy_uniform_refinement_entities(refinement)

return self

Expand Down
20 changes: 20 additions & 0 deletions flow360/component/simulation/meshing_param/volume_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,26 @@ def check_entities_used_with_beta_mesher(cls, values, param_info: ParamsValidati

return values

@contextual_field_validator("entities", mode="after")
@classmethod
def check_entities_used_with_snappy(cls, values, param_info: ParamsValidationInfo):
"""Check that only Box, Cylinder, and Sphere entities are used with snappyHexMesh."""

if values is None:
return values
if not param_info.use_snappy:
return values

expanded = param_info.expand_entity_list(values)
for entity in expanded:
if not isinstance(entity, (Box, Cylinder, Sphere)):
raise ValueError(
f"`{type(entity).__name__}` entity for `UniformRefinement` is not supported "
"with snappyHexMesh. Only `Box`, `Cylinder`, and `Sphere` are allowed."
)

return values

@contextual_model_validator(mode="after")
def check_project_to_surface_with_snappy(self, param_info: ParamsValidationInfo):
"""Check that project_to_surface is used only with snappy."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@
beta_mesher_context.is_beta_mesher = True
beta_mesher_context.project_length_unit = "mm"

snappy_context = ParamsValidationInfo({}, [])
snappy_context.use_snappy = True
snappy_context.is_beta_mesher = True


def test_structured_box_only_in_beta_mesher():
# raises when beta mesher is off
Expand Down Expand Up @@ -870,7 +874,20 @@ def test_sphere_in_uniform_refinement():
],
)

# raises without beta mesher
# also allowed with snappy
with ValidationContext(VOLUME_MESH, snappy_context):
with CGS_unit_system:
sphere = Sphere(
name="s_snappy",
center=(0, 0, 0),
radius=1.0,
)
UniformRefinement(
entities=[sphere],
spacing=0.1,
)

# raises without beta mesher or snappy
with pytest.raises(
pd.ValidationError,
match=r"`Sphere` entity for `UniformRefinement` is supported only with beta mesher",
Expand All @@ -888,6 +905,45 @@ def test_sphere_in_uniform_refinement():
)


def test_uniform_refinement_snappy_entity_restrictions():
"""With snappy, UniformRefinement only accepts Box, Cylinder, and Sphere entities."""
# Box, Cylinder, Sphere all allowed
with ValidationContext(VOLUME_MESH, snappy_context):
with CGS_unit_system:
UniformRefinement(
entities=[
Box(center=(0, 0, 0), size=(1, 1, 1), name="box"),
Cylinder(
name="cyl",
axis=(0, 0, 1),
center=(0, 0, 0),
height=1.0,
outer_radius=0.5,
),
Sphere(name="sph", center=(0, 0, 0), radius=1.0),
],
spacing=0.1,
)

# AxisymmetricBody rejected with snappy
with pytest.raises(
pd.ValidationError,
match=r"`AxisymmetricBody` entity for `UniformRefinement` is not supported with snappyHexMesh",
):
with ValidationContext(VOLUME_MESH, snappy_context):
with CGS_unit_system:
axisymmetric_body = AxisymmetricBody(
name="axisymm",
axis=(0, 0, 1),
center=(0, 0, 0),
profile_curve=[(-1, 0), (-1, 1), (1, 1), (1, 0)],
)
UniformRefinement(
entities=[axisymmetric_body],
spacing=0.1,
)


def test_require_mesh_zones():
with SI_unit_system:
ModularMeshingWorkflow(
Expand Down
Loading
Loading