Skip to content

Commit 1e4a444

Browse files
[SNAPPY] Enable only selected shapes of UniformRefinement (#1846)
Co-authored-by: Ben <106089368+benflexcompute@users.noreply.github.com> Co-authored-by: benflexcompute <ben@flexcompute.com>
1 parent 6ef13ca commit 1e4a444

8 files changed

Lines changed: 525 additions & 18 deletions

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""Shared validation helpers for meshing parameters."""
2+
3+
import flow360.component.simulation.units as u
4+
from flow360.component.simulation.meshing_param.volume_params import UniformRefinement
5+
from flow360.component.simulation.primitives import Box, Cylinder
6+
7+
8+
def validate_snappy_uniform_refinement_entities(refinement: UniformRefinement):
9+
"""Validate that a UniformRefinement's entities are compatible with snappyHexMesh.
10+
11+
Raises ValueError if any Box has a non-axis-aligned rotation or any Cylinder is hollow.
12+
"""
13+
# pylint: disable=no-member
14+
for entity in refinement.entities.stored_entities:
15+
if (
16+
isinstance(entity, Box)
17+
and entity.angle_of_rotation.to("deg") % (360 * u.deg) != 0 * u.deg
18+
):
19+
raise ValueError(
20+
"UniformRefinement for snappy accepts only Boxes with axes aligned"
21+
+ " with the global coordinate system (angle_of_rotation=0)."
22+
)
23+
if (
24+
isinstance(entity, Cylinder)
25+
and entity.inner_radius is not None
26+
and entity.inner_radius.to("m") != 0 * u.m
27+
):
28+
raise ValueError(
29+
"UniformRefinement for snappy accepts only full cylinders (where inner_radius = 0)."
30+
)

flow360/component/simulation/meshing_param/params.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
MeshingDefaults,
2323
VolumeMeshingDefaults,
2424
)
25+
from flow360.component.simulation.meshing_param.meshing_validators import (
26+
validate_snappy_uniform_refinement_entities,
27+
)
2528
from flow360.component.simulation.meshing_param.volume_params import (
2629
AutomatedFarfield,
2730
AxisymmetricRefinement,
@@ -589,6 +592,19 @@ def _check_sizing_against_octree_series(self, param_info: ParamsValidationInfo):
589592

590593
return self
591594

595+
@contextual_model_validator(mode="after")
596+
def _check_snappy_uniform_refinement_entities(self, param_info: ParamsValidationInfo):
597+
"""Validate projected UniformRefinement entities are compatible with snappyHexMesh."""
598+
if not param_info.use_snappy:
599+
return self
600+
for refinement in self.refinements: # pylint: disable=not-an-iterable
601+
if (
602+
isinstance(refinement, UniformRefinement)
603+
and refinement.project_to_surface is not False
604+
):
605+
validate_snappy_uniform_refinement_entities(refinement)
606+
return self
607+
592608

593609
SurfaceMeshingParams = Annotated[
594610
Union[snappy.SurfaceMeshingParams], pd.Field(discriminator="type_name")

flow360/component/simulation/meshing_param/snappy/snappy_params.py

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44

55
import pydantic as pd
66

7-
import flow360.component.simulation.units as u
87
from flow360.component.simulation.framework.base_model import Flow360BaseModel
98
from flow360.component.simulation.meshing_param.meshing_specs import OctreeSpacing
9+
from flow360.component.simulation.meshing_param.meshing_validators import (
10+
validate_snappy_uniform_refinement_entities,
11+
)
1012
from flow360.component.simulation.meshing_param.snappy.snappy_mesh_refinements import (
1113
BodyRefinement,
1214
RegionRefinement,
@@ -22,7 +24,6 @@
2224
SurfaceMeshingDefaults,
2325
)
2426
from flow360.component.simulation.meshing_param.volume_params import UniformRefinement
25-
from flow360.component.simulation.primitives import Box, Cylinder
2627
from flow360.component.simulation.unit_system import LengthType
2728
from flow360.component.simulation.validation.validation_context import (
2829
ParamsValidationInfo,
@@ -124,20 +125,7 @@ def _check_uniform_refinement_entities(self):
124125
return self
125126
for refinement in self.refinements:
126127
if isinstance(refinement, UniformRefinement):
127-
# No expansion needed since we only allow Draft entities here.
128-
for entity in refinement.entities.stored_entities:
129-
if (
130-
isinstance(entity, Box)
131-
and entity.angle_of_rotation.to("deg") % (360 * u.deg) != 0 * u.deg
132-
):
133-
raise ValueError(
134-
"UniformRefinement for snappy accepts only Boxes with axes aligned"
135-
+ " with the global coordinate system (angle_of_rotation=0)."
136-
)
137-
if isinstance(entity, Cylinder) and entity.inner_radius.to("m") != 0 * u.m:
138-
raise ValueError(
139-
"UniformRefinement for snappy accepts only full cylinders (where inner_radius = 0)."
140-
)
128+
validate_snappy_uniform_refinement_entities(refinement)
141129

142130
return self
143131

flow360/component/simulation/meshing_param/volume_params.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,26 @@ def check_entities_used_with_beta_mesher(cls, values, param_info: ParamsValidati
103103

104104
return values
105105

106+
@contextual_field_validator("entities", mode="after")
107+
@classmethod
108+
def check_entities_used_with_snappy(cls, values, param_info: ParamsValidationInfo):
109+
"""Check that only Box, Cylinder, and Sphere entities are used with snappyHexMesh."""
110+
111+
if values is None:
112+
return values
113+
if not param_info.use_snappy:
114+
return values
115+
116+
expanded = param_info.expand_entity_list(values)
117+
for entity in expanded:
118+
if not isinstance(entity, (Box, Cylinder, Sphere)):
119+
raise ValueError(
120+
f"`{type(entity).__name__}` entity for `UniformRefinement` is not supported "
121+
"with snappyHexMesh. Only `Box`, `Cylinder`, and `Sphere` are allowed."
122+
)
123+
124+
return values
125+
106126
@contextual_model_validator(mode="after")
107127
def check_project_to_surface_with_snappy(self, param_info: ParamsValidationInfo):
108128
"""Check that project_to_surface is used only with snappy."""

tests/simulation/params/meshing_validation/test_meshing_param_validation.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@
6969
beta_mesher_context.is_beta_mesher = True
7070
beta_mesher_context.project_length_unit = "mm"
7171

72+
snappy_context = ParamsValidationInfo({}, [])
73+
snappy_context.use_snappy = True
74+
snappy_context.is_beta_mesher = True
75+
7276

7377
def test_structured_box_only_in_beta_mesher():
7478
# raises when beta mesher is off
@@ -870,7 +874,20 @@ def test_sphere_in_uniform_refinement():
870874
],
871875
)
872876

873-
# raises without beta mesher
877+
# also allowed with snappy
878+
with ValidationContext(VOLUME_MESH, snappy_context):
879+
with CGS_unit_system:
880+
sphere = Sphere(
881+
name="s_snappy",
882+
center=(0, 0, 0),
883+
radius=1.0,
884+
)
885+
UniformRefinement(
886+
entities=[sphere],
887+
spacing=0.1,
888+
)
889+
890+
# raises without beta mesher or snappy
874891
with pytest.raises(
875892
pd.ValidationError,
876893
match=r"`Sphere` entity for `UniformRefinement` is supported only with beta mesher",
@@ -888,6 +905,45 @@ def test_sphere_in_uniform_refinement():
888905
)
889906

890907

908+
def test_uniform_refinement_snappy_entity_restrictions():
909+
"""With snappy, UniformRefinement only accepts Box, Cylinder, and Sphere entities."""
910+
# Box, Cylinder, Sphere all allowed
911+
with ValidationContext(VOLUME_MESH, snappy_context):
912+
with CGS_unit_system:
913+
UniformRefinement(
914+
entities=[
915+
Box(center=(0, 0, 0), size=(1, 1, 1), name="box"),
916+
Cylinder(
917+
name="cyl",
918+
axis=(0, 0, 1),
919+
center=(0, 0, 0),
920+
height=1.0,
921+
outer_radius=0.5,
922+
),
923+
Sphere(name="sph", center=(0, 0, 0), radius=1.0),
924+
],
925+
spacing=0.1,
926+
)
927+
928+
# AxisymmetricBody rejected with snappy
929+
with pytest.raises(
930+
pd.ValidationError,
931+
match=r"`AxisymmetricBody` entity for `UniformRefinement` is not supported with snappyHexMesh",
932+
):
933+
with ValidationContext(VOLUME_MESH, snappy_context):
934+
with CGS_unit_system:
935+
axisymmetric_body = AxisymmetricBody(
936+
name="axisymm",
937+
axis=(0, 0, 1),
938+
center=(0, 0, 0),
939+
profile_curve=[(-1, 0), (-1, 1), (1, 1), (1, 0)],
940+
)
941+
UniformRefinement(
942+
entities=[axisymmetric_body],
943+
spacing=0.1,
944+
)
945+
946+
891947
def test_require_mesh_zones():
892948
with SI_unit_system:
893949
ModularMeshingWorkflow(

0 commit comments

Comments
 (0)