Skip to content

Commit 89673f2

Browse files
authored
Added UniformRefinement to snappy (#1331)
1 parent 9600a3b commit 89673f2

File tree

5 files changed

+169
-21
lines changed

5 files changed

+169
-21
lines changed

flow360/component/simulation/meshing_param/params.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
BetaVolumeMeshingDefaults
3939
)
4040
from flow360.component.simulation.unit_system import LengthType
41-
from flow360.component.simulation.primitives import Box, MeshZone
41+
from flow360.component.simulation.primitives import Box, MeshZone, Cylinder
4242

4343
from flow360.component.simulation.validation.validation_context import (
4444
SURFACE_MESH,
@@ -78,6 +78,7 @@
7878
SnappyBodyRefinement,
7979
SnappySurfaceEdgeRefinement,
8080
SnappyRegionRefinement,
81+
UniformRefinement
8182
],
8283
pd.Field(discriminator="refinement_type")
8384
]
@@ -248,11 +249,6 @@ def automated_farfield_method(self):
248249
if isinstance(zone, AutomatedFarfield):
249250
return zone.method
250251
return None
251-
252-
class PWSurfaceMeshingParams(Flow360BaseModel):
253-
type: Literal["PWSurfaceMeshingParams"] = pd.Field(
254-
"PWSurfaceMeshingParams", frozen=True
255-
)
256252

257253
class SnappySurfaceMeshingParams(Flow360BaseModel):
258254
type: Literal["SnappySurfaceMeshingParams"] = pd.Field(
@@ -286,6 +282,19 @@ def _check_body_refinements_w_defaults(self):
286282
if refinement.max_spacing is None and self.defaults.max_spacing.to("m") < refinement.min_spacing.to("m"):
287283
raise ValueError("Default maximum spacing is lower that refinement minimum spacing and maximum spacing is not provided.")
288284
return self
285+
286+
@pd.model_validator(mode="after")
287+
def _check_uniform_refinement_entities(self):
288+
for refinement in self.refinements:
289+
if isinstance(refinement, UniformRefinement):
290+
for entity in refinement.entities.stored_entities:
291+
if isinstance(entity, Box) and entity.angle_of_rotation.to("deg") != 0*u.deg:
292+
raise ValueError("UniformRefinement for snappy accepts only Boxes with axes aligned with the global coordinate system (angle_of_rotation=0).")
293+
if isinstance(entity, Cylinder) and entity.inner_radius.to("m") != 0*u.m:
294+
raise ValueError("UniformRefinement for snappy accepts only full cylinders (where inner_radius = 0).")
295+
296+
return self
297+
289298

290299

291300
class BetaVolumeMeshingParams(Flow360BaseModel):
@@ -409,7 +418,7 @@ def automated_farfield_method(self):
409418
return None
410419

411420

412-
SurfaceMeshingParams = Annotated[Union[SnappySurfaceMeshingParams, PWSurfaceMeshingParams], pd.Field(discriminator="type")]
421+
SurfaceMeshingParams = Annotated[Union[SnappySurfaceMeshingParams], pd.Field(discriminator="type")]
413422
VolumeMeshingParams = Annotated[Union[BetaVolumeMeshingParams], pd.Field(discriminator="type")]
414423

415424

flow360/component/simulation/translator/surface_meshing_translator.py

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from flow360.component.simulation.entity_info import GeometryEntityInfo
66
from flow360.component.simulation.meshing_param.edge_params import SurfaceEdgeRefinement
77
from flow360.component.simulation.meshing_param.face_params import SurfaceRefinement
8-
from flow360.component.simulation.primitives import Surface, SnappyBody
8+
from flow360.component.simulation.primitives import Surface, Box, Cylinder
99
from flow360.component.simulation.simulation_params import SimulationParams
1010
from flow360.component.simulation.translator.utils import (
1111
preprocess_input,
@@ -19,6 +19,7 @@
1919
SnappyRegionRefinement,
2020
SnappySurfaceEdgeRefinement
2121
)
22+
from flow360.component.simulation.meshing_param.volume_params import UniformRefinement
2223
from copy import deepcopy
2324

2425
import numpy as np
@@ -135,6 +136,49 @@ def apply_SnappyRegionRefinement(refinement:SnappyRegionRefinement, translated):
135136
"max": refinement.max_spacing.value.item()
136137
}
137138

139+
def apply_UniformRefinement_w_snappy(refinement:UniformRefinement, translated):
140+
if "refinementVolumes" not in translated["geometry"]:
141+
translated["geometry"]["refinementVolumes"] = []
142+
143+
for volume in refinement.entities.stored_entities:
144+
volume_body = {
145+
"spacing": refinement.spacing.value.item(),
146+
"name": volume.name
147+
}
148+
if isinstance(volume, Box):
149+
volume_body["type"] = "box"
150+
volume_body["min"] = {
151+
"x": volume.center[0].value.item() - 0.5*volume.size[0].value.item(),
152+
"y": volume.center[1].value.item() - 0.5*volume.size[1].value.item(),
153+
"z": volume.center[2].value.item() - 0.5*volume.size[2].value.item(),
154+
}
155+
volume_body["max"] = {
156+
"x": volume.center[0].value.item() + 0.5*volume.size[0].value.item(),
157+
"y": volume.center[1].value.item() + 0.5*volume.size[1].value.item(),
158+
"z": volume.center[2].value.item() + 0.5*volume.size[2].value.item(),
159+
}
160+
elif isinstance(volume, Cylinder):
161+
volume_body["type"] = "cylinder"
162+
volume_body["radius"] = volume.outer_radius.value.item()
163+
volume_body["point1"] = {
164+
"x": volume.center[0].value.item() - 0.5*volume.axis[0]*volume.height.value.item(),
165+
"y": volume.center[1].value.item() - 0.5*volume.axis[1]*volume.height.value.item(),
166+
"z": volume.center[2].value.item() - 0.5*volume.axis[2]*volume.height.value.item()
167+
}
168+
169+
volume_body["point2"] = {
170+
"x": volume.center[0].value.item() + 0.5*volume.axis[0]*volume.height.value.item(),
171+
"y": volume.center[1].value.item() + 0.5*volume.axis[1]*volume.height.value.item(),
172+
"z": volume.center[2].value.item() + 0.5*volume.axis[2]*volume.height.value.item()
173+
}
174+
175+
else:
176+
raise Flow360TranslationError(f"Volume of type {type(volume)} cannot be used with Snappy.")
177+
178+
translated["geometry"]["refinementVolumes"].append(volume_body)
179+
180+
181+
138182
@preprocess_input
139183
# pylint: disable=unused-argument
140184
def get_surface_meshing_json(input_params: SimulationParams, mesh_units):
@@ -176,10 +220,14 @@ def get_surface_meshing_json(input_params: SimulationParams, mesh_units):
176220
for refinement in surface_meshing_params.refinements:
177221
if isinstance(refinement, SnappyBodyRefinement):
178222
apply_SnappyBodyRefinement(refinement, translated)
179-
if isinstance(refinement, SnappySurfaceEdgeRefinement):
223+
elif isinstance(refinement, SnappySurfaceEdgeRefinement):
180224
apply_SnappySurfaceEdgeRefinement(refinement, translated, surface_meshing_params.defaults)
181-
if isinstance(refinement, SnappyRegionRefinement):
225+
elif isinstance(refinement, SnappyRegionRefinement):
182226
apply_SnappyRegionRefinement(refinement, translated)
227+
elif isinstance(refinement, UniformRefinement):
228+
apply_UniformRefinement_w_snappy(refinement, translated)
229+
else:
230+
raise Flow360TranslationError(f"Refinement of type {type(refinement)} cannot be used with Snappy.")
183231

184232
# apply settings
185233
castellated_mesh_controls = surface_meshing_params.castellated_mesh_controls

tests/simulation/params/meshing_validation/test_refinements_validation.py

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@
22
import pytest
33
import re
44

5-
from flow360.component.simulation.meshing_param.params import ModularMeshingWorkflow
6-
from flow360.component.simulation.meshing_param.surface_mesh_refinements import (
7-
SnappyBodyRefinement,
8-
SnappySurfaceEdgeRefinement,
9-
SnappyRegionRefinement
10-
)
11-
from flow360.component.simulation.primitives import Surface
5+
from flow360.component.simulation.meshing_param.params import SnappySurfaceMeshingParams, SnappySurfaceMeshingDefaults
6+
from flow360.component.simulation.meshing_param.surface_mesh_refinements import SnappyRegionRefinement
7+
from flow360.component.simulation.meshing_param.volume_params import UniformRefinement
8+
9+
from flow360.component.simulation.primitives import Surface, Box, Cylinder
1210
from flow360.component.simulation.unit_system import SI_unit_system
1311
import flow360.component.simulation.units as u
1412

@@ -22,4 +20,50 @@ def test_snappy_refinements_validators():
2220
max_spacing=2.1 * u.mm,
2321
regions=[Surface(name="test")]
2422
)
23+
24+
message = "UniformRefinement for snappy accepts only Boxes with axes aligned with the global coordinate system (angle_of_rotation=0)."
25+
with SI_unit_system, pytest.raises(ValueError, match=re.escape(message)):
26+
SnappySurfaceMeshingParams(
27+
defaults=SnappySurfaceMeshingDefaults(
28+
min_spacing=3*u.mm,
29+
max_spacing=10*u.mm,
30+
gap_resolution=0.1*u.mm
31+
),
32+
refinements=[UniformRefinement(name="unif", spacing=6*u.mm, entities=[Box(center=[2, 3, 4]*u.m,
33+
size=[5, 6, 7]*u.m,
34+
axis_of_rotation=[1, 3, 4],
35+
angle_of_rotation=5*u.deg,
36+
name="box")])]
37+
)
38+
39+
SnappySurfaceMeshingParams(
40+
defaults=SnappySurfaceMeshingDefaults(
41+
min_spacing=3*u.mm,
42+
max_spacing=10*u.mm,
43+
gap_resolution=0.1*u.mm
44+
),
45+
refinements=[UniformRefinement(name="unif", spacing=6*u.mm, entities=[Box(center=[2, 3, 4]*u.m,
46+
size=[5, 6, 7]*u.m,
47+
axis_of_rotation=[1, 3, 4],
48+
angle_of_rotation=0*u.deg,
49+
name="box")])]
50+
)
51+
52+
message = "UniformRefinement for snappy accepts only full cylinders (where inner_radius = 0)."
53+
with SI_unit_system, pytest.raises(ValueError, match=re.escape(message)):
54+
SnappySurfaceMeshingParams(
55+
defaults=SnappySurfaceMeshingDefaults(
56+
min_spacing=3*u.mm,
57+
max_spacing=10*u.mm,
58+
gap_resolution=0.1*u.mm
59+
),
60+
refinements=[UniformRefinement(name="unif", spacing=6*u.mm, entities=[Cylinder(name="cyl",
61+
inner_radius=3*u.mm,
62+
outer_radius=7*u.mm,
63+
axis=[0,0,1],
64+
center=[0, 0, 0]*u.m,
65+
height=10*u.mm)])]
66+
)
67+
68+
2569

tests/simulation/translator/ref/surface_meshing/snappy_basic_refinements.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,31 @@
130130
}
131131
]
132132
}
133+
],
134+
"refinementVolumes": [
135+
{
136+
"type": "box",
137+
"min": {"x": -10, "y": 15, "z": 40},
138+
"max": {"x": 10, "y": 45, "z": 80},
139+
"name": "box0",
140+
"spacing": 2
141+
},
142+
{
143+
"type": "cylinder",
144+
"point1": {"x": 10, "y": 20, "z": 0},
145+
"point2": {"x": 10, "y": 20, "z": 60},
146+
"radius": 20,
147+
"name": "cyl0",
148+
"spacing": 2
149+
},
150+
{
151+
"type": "cylinder",
152+
"point1": {"x": 21.5634892, "y": -0.0137314, "z": 49.1242323},
153+
"point2": {"x": -1.5634892, "y": 40.0137314, "z": 10.8757677},
154+
"radius": 34,
155+
"name": "cyl1",
156+
"spacing": 8
157+
}
133158
]
134159
},
135160
"mesherSettings": {

tests/simulation/translator/test_surface_meshing_translator.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,15 @@
2626
BetaVolumeMeshingParams,
2727
ModularMeshingWorkflow
2828
)
29+
from flow360.component.simulation.meshing_param.volume_params import UniformRefinement
2930
from flow360.component.simulation.meshing_param.meshing_specs import (
3031
SnappySurfaceMeshingDefaults,
3132
SnappyCastellatedMeshControls,
3233
SnappyQualityMetrics,
3334
SnappySnapControls,
3435
SnappySmoothControls
3536
)
36-
from flow360.component.simulation.primitives import Edge, Surface, SnappyBody, Box, MeshZone
37+
from flow360.component.simulation.primitives import Edge, Surface, SnappyBody, Box, MeshZone, Cylinder
3738
from flow360.component.simulation.simulation_params import SimulationParams
3839
from flow360.component.simulation.translator.surface_meshing_translator import (
3940
get_surface_meshing_json,
@@ -592,7 +593,27 @@ def snappy_basic_refinements():
592593
regions=[test_geometry["*patch1"]],
593594
bodies=[SnappyBody(body_name="body3")],
594595
retain_on_smoothing=False
596+
),
597+
UniformRefinement(
598+
spacing=2*u.mm,
599+
entities=[Box(name="box0",
600+
center=[0, 30, 60]*u.mm,
601+
size=[20, 30, 40]*u.mm),
602+
Cylinder(name="cyl0",
603+
axis=[0, 0, 1],
604+
center=[10, 20, 30]*u.mm,
605+
height=60*u.mm,
606+
outer_radius=20*u.mm)]
607+
),
608+
UniformRefinement(
609+
spacing=8*u.mm,
610+
entities=[Cylinder(name="cyl1",
611+
axis=[-0.26, 0.45, -0.43],
612+
center=[10, 20, 30]*u.mm,
613+
height=60*u.mm,
614+
outer_radius=34*u.mm)]
595615
)
616+
596617
],
597618
smooth_controls=SnappySmoothControls()
598619
)
@@ -863,7 +884,7 @@ def sort_key(item):
863884
else:
864885
return obj
865886

866-
def _translate_and_compare(param, mesh_unit, ref_json_file: str):
887+
def _translate_and_compare(param, mesh_unit, ref_json_file: str, atol=1e-15):
867888
translated = get_surface_meshing_json(param, mesh_unit=mesh_unit)
868889
with open(
869890
os.path.join(
@@ -875,7 +896,7 @@ def _translate_and_compare(param, mesh_unit, ref_json_file: str):
875896
ref_dict, translated = deep_sort_lists(ref_dict), deep_sort_lists(translated)
876897
# check if everything is seriazable
877898
json.dumps(translated)
878-
assert compare_values(ref_dict, translated)
899+
assert compare_values(ref_dict, translated, atol=atol)
879900

880901

881902
def test_om6wing_tutorial(
@@ -929,7 +950,8 @@ def test_snappy_basic(get_snappy_geometry, snappy_basic_refinements):
929950
_translate_and_compare(
930951
snappy_basic_refinements,
931952
get_snappy_geometry.mesh_unit,
932-
"snappy_basic_refinements.json"
953+
"snappy_basic_refinements.json",
954+
atol=1e-6
933955
)
934956

935957
def test_snappy_multiple_regions(get_snappy_geometry, snappy_refinements_multiple_regions):

0 commit comments

Comments
 (0)