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
23 changes: 16 additions & 7 deletions flow360/component/simulation/meshing_param/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
BetaVolumeMeshingDefaults
)
from flow360.component.simulation.unit_system import LengthType
from flow360.component.simulation.primitives import Box, MeshZone
from flow360.component.simulation.primitives import Box, MeshZone, Cylinder

from flow360.component.simulation.validation.validation_context import (
SURFACE_MESH,
Expand Down Expand Up @@ -78,6 +78,7 @@
SnappyBodyRefinement,
SnappySurfaceEdgeRefinement,
SnappyRegionRefinement,
UniformRefinement
],
pd.Field(discriminator="refinement_type")
]
Expand Down Expand Up @@ -248,11 +249,6 @@ def automated_farfield_method(self):
if isinstance(zone, AutomatedFarfield):
return zone.method
return None

class PWSurfaceMeshingParams(Flow360BaseModel):
type: Literal["PWSurfaceMeshingParams"] = pd.Field(
"PWSurfaceMeshingParams", frozen=True
)

class SnappySurfaceMeshingParams(Flow360BaseModel):
type: Literal["SnappySurfaceMeshingParams"] = pd.Field(
Expand Down Expand Up @@ -286,6 +282,19 @@ def _check_body_refinements_w_defaults(self):
if refinement.max_spacing is None and self.defaults.max_spacing.to("m") < refinement.min_spacing.to("m"):
raise ValueError("Default maximum spacing is lower that refinement minimum spacing and maximum spacing is not provided.")
return self

@pd.model_validator(mode="after")
def _check_uniform_refinement_entities(self):
for refinement in self.refinements:
if isinstance(refinement, UniformRefinement):
for entity in refinement.entities.stored_entities:
if isinstance(entity, Box) and entity.angle_of_rotation.to("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).")

return self



class BetaVolumeMeshingParams(Flow360BaseModel):
Expand Down Expand Up @@ -409,7 +418,7 @@ def automated_farfield_method(self):
return None


SurfaceMeshingParams = Annotated[Union[SnappySurfaceMeshingParams, PWSurfaceMeshingParams], pd.Field(discriminator="type")]
SurfaceMeshingParams = Annotated[Union[SnappySurfaceMeshingParams], pd.Field(discriminator="type")]
VolumeMeshingParams = Annotated[Union[BetaVolumeMeshingParams], pd.Field(discriminator="type")]


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from flow360.component.simulation.entity_info import GeometryEntityInfo
from flow360.component.simulation.meshing_param.edge_params import SurfaceEdgeRefinement
from flow360.component.simulation.meshing_param.face_params import SurfaceRefinement
from flow360.component.simulation.primitives import Surface, SnappyBody
from flow360.component.simulation.primitives import Surface, Box, Cylinder
from flow360.component.simulation.simulation_params import SimulationParams
from flow360.component.simulation.translator.utils import (
preprocess_input,
Expand All @@ -19,6 +19,7 @@
SnappyRegionRefinement,
SnappySurfaceEdgeRefinement
)
from flow360.component.simulation.meshing_param.volume_params import UniformRefinement
from copy import deepcopy

import numpy as np
Expand Down Expand Up @@ -135,6 +136,49 @@ def apply_SnappyRegionRefinement(refinement:SnappyRegionRefinement, translated):
"max": refinement.max_spacing.value.item()
}

def apply_UniformRefinement_w_snappy(refinement:UniformRefinement, translated):
if "refinementVolumes" not in translated["geometry"]:
translated["geometry"]["refinementVolumes"] = []

for volume in refinement.entities.stored_entities:
volume_body = {
"spacing": refinement.spacing.value.item(),
"name": volume.name
}
if isinstance(volume, Box):
volume_body["type"] = "box"
volume_body["min"] = {
"x": volume.center[0].value.item() - 0.5*volume.size[0].value.item(),
"y": volume.center[1].value.item() - 0.5*volume.size[1].value.item(),
"z": volume.center[2].value.item() - 0.5*volume.size[2].value.item(),
}
volume_body["max"] = {
"x": volume.center[0].value.item() + 0.5*volume.size[0].value.item(),
"y": volume.center[1].value.item() + 0.5*volume.size[1].value.item(),
"z": volume.center[2].value.item() + 0.5*volume.size[2].value.item(),
}
elif isinstance(volume, Cylinder):
volume_body["type"] = "cylinder"
volume_body["radius"] = volume.outer_radius.value.item()
volume_body["point1"] = {
"x": volume.center[0].value.item() - 0.5*volume.axis[0]*volume.height.value.item(),
"y": volume.center[1].value.item() - 0.5*volume.axis[1]*volume.height.value.item(),
"z": volume.center[2].value.item() - 0.5*volume.axis[2]*volume.height.value.item()
}

volume_body["point2"] = {
"x": volume.center[0].value.item() + 0.5*volume.axis[0]*volume.height.value.item(),
"y": volume.center[1].value.item() + 0.5*volume.axis[1]*volume.height.value.item(),
"z": volume.center[2].value.item() + 0.5*volume.axis[2]*volume.height.value.item()
}

else:
raise Flow360TranslationError(f"Volume of type {type(volume)} cannot be used with Snappy.")

translated["geometry"]["refinementVolumes"].append(volume_body)



@preprocess_input
# pylint: disable=unused-argument
def get_surface_meshing_json(input_params: SimulationParams, mesh_units):
Expand Down Expand Up @@ -176,10 +220,14 @@ def get_surface_meshing_json(input_params: SimulationParams, mesh_units):
for refinement in surface_meshing_params.refinements:
if isinstance(refinement, SnappyBodyRefinement):
apply_SnappyBodyRefinement(refinement, translated)
if isinstance(refinement, SnappySurfaceEdgeRefinement):
elif isinstance(refinement, SnappySurfaceEdgeRefinement):
apply_SnappySurfaceEdgeRefinement(refinement, translated, surface_meshing_params.defaults)
if isinstance(refinement, SnappyRegionRefinement):
elif isinstance(refinement, SnappyRegionRefinement):
apply_SnappyRegionRefinement(refinement, translated)
elif isinstance(refinement, UniformRefinement):
apply_UniformRefinement_w_snappy(refinement, translated)
else:
raise Flow360TranslationError(f"Refinement of type {type(refinement)} cannot be used with Snappy.")

# apply settings
castellated_mesh_controls = surface_meshing_params.castellated_mesh_controls
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@
import pytest
import re

from flow360.component.simulation.meshing_param.params import ModularMeshingWorkflow
from flow360.component.simulation.meshing_param.surface_mesh_refinements import (
SnappyBodyRefinement,
SnappySurfaceEdgeRefinement,
SnappyRegionRefinement
)
from flow360.component.simulation.primitives import Surface
from flow360.component.simulation.meshing_param.params import SnappySurfaceMeshingParams, SnappySurfaceMeshingDefaults
from flow360.component.simulation.meshing_param.surface_mesh_refinements import SnappyRegionRefinement
from flow360.component.simulation.meshing_param.volume_params import UniformRefinement

from flow360.component.simulation.primitives import Surface, Box, Cylinder
from flow360.component.simulation.unit_system import SI_unit_system
import flow360.component.simulation.units as u

Expand All @@ -22,4 +20,50 @@ def test_snappy_refinements_validators():
max_spacing=2.1 * u.mm,
regions=[Surface(name="test")]
)

message = "UniformRefinement for snappy accepts only Boxes with axes aligned with the global coordinate system (angle_of_rotation=0)."
with SI_unit_system, pytest.raises(ValueError, match=re.escape(message)):
SnappySurfaceMeshingParams(
defaults=SnappySurfaceMeshingDefaults(
min_spacing=3*u.mm,
max_spacing=10*u.mm,
gap_resolution=0.1*u.mm
),
refinements=[UniformRefinement(name="unif", spacing=6*u.mm, entities=[Box(center=[2, 3, 4]*u.m,
size=[5, 6, 7]*u.m,
axis_of_rotation=[1, 3, 4],
angle_of_rotation=5*u.deg,
name="box")])]
)

SnappySurfaceMeshingParams(
defaults=SnappySurfaceMeshingDefaults(
min_spacing=3*u.mm,
max_spacing=10*u.mm,
gap_resolution=0.1*u.mm
),
refinements=[UniformRefinement(name="unif", spacing=6*u.mm, entities=[Box(center=[2, 3, 4]*u.m,
size=[5, 6, 7]*u.m,
axis_of_rotation=[1, 3, 4],
angle_of_rotation=0*u.deg,
name="box")])]
)

message = "UniformRefinement for snappy accepts only full cylinders (where inner_radius = 0)."
with SI_unit_system, pytest.raises(ValueError, match=re.escape(message)):
SnappySurfaceMeshingParams(
defaults=SnappySurfaceMeshingDefaults(
min_spacing=3*u.mm,
max_spacing=10*u.mm,
gap_resolution=0.1*u.mm
),
refinements=[UniformRefinement(name="unif", spacing=6*u.mm, entities=[Cylinder(name="cyl",
inner_radius=3*u.mm,
outer_radius=7*u.mm,
axis=[0,0,1],
center=[0, 0, 0]*u.m,
height=10*u.mm)])]
)



Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,31 @@
}
]
}
],
"refinementVolumes": [
{
"type": "box",
"min": {"x": -10, "y": 15, "z": 40},
"max": {"x": 10, "y": 45, "z": 80},
"name": "box0",
"spacing": 2
},
{
"type": "cylinder",
"point1": {"x": 10, "y": 20, "z": 0},
"point2": {"x": 10, "y": 20, "z": 60},
"radius": 20,
"name": "cyl0",
"spacing": 2
},
{
"type": "cylinder",
"point1": {"x": 21.5634892, "y": -0.0137314, "z": 49.1242323},
"point2": {"x": -1.5634892, "y": 40.0137314, "z": 10.8757677},
"radius": 34,
"name": "cyl1",
"spacing": 8
}
]
},
"mesherSettings": {
Expand Down
30 changes: 26 additions & 4 deletions tests/simulation/translator/test_surface_meshing_translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@
BetaVolumeMeshingParams,
ModularMeshingWorkflow
)
from flow360.component.simulation.meshing_param.volume_params import UniformRefinement
from flow360.component.simulation.meshing_param.meshing_specs import (
SnappySurfaceMeshingDefaults,
SnappyCastellatedMeshControls,
SnappyQualityMetrics,
SnappySnapControls,
SnappySmoothControls
)
from flow360.component.simulation.primitives import Edge, Surface, SnappyBody, Box, MeshZone
from flow360.component.simulation.primitives import Edge, Surface, SnappyBody, Box, MeshZone, Cylinder
from flow360.component.simulation.simulation_params import SimulationParams
from flow360.component.simulation.translator.surface_meshing_translator import (
get_surface_meshing_json,
Expand Down Expand Up @@ -592,7 +593,27 @@ def snappy_basic_refinements():
regions=[test_geometry["*patch1"]],
bodies=[SnappyBody(body_name="body3")],
retain_on_smoothing=False
),
UniformRefinement(
spacing=2*u.mm,
entities=[Box(name="box0",
center=[0, 30, 60]*u.mm,
size=[20, 30, 40]*u.mm),
Cylinder(name="cyl0",
axis=[0, 0, 1],
center=[10, 20, 30]*u.mm,
height=60*u.mm,
outer_radius=20*u.mm)]
),
UniformRefinement(
spacing=8*u.mm,
entities=[Cylinder(name="cyl1",
axis=[-0.26, 0.45, -0.43],
center=[10, 20, 30]*u.mm,
height=60*u.mm,
outer_radius=34*u.mm)]
)

],
smooth_controls=SnappySmoothControls()
)
Expand Down Expand Up @@ -863,7 +884,7 @@ def sort_key(item):
else:
return obj

def _translate_and_compare(param, mesh_unit, ref_json_file: str):
def _translate_and_compare(param, mesh_unit, ref_json_file: str, atol=1e-15):
translated = get_surface_meshing_json(param, mesh_unit=mesh_unit)
with open(
os.path.join(
Expand All @@ -875,7 +896,7 @@ def _translate_and_compare(param, mesh_unit, ref_json_file: str):
ref_dict, translated = deep_sort_lists(ref_dict), deep_sort_lists(translated)
# check if everything is seriazable
json.dumps(translated)
assert compare_values(ref_dict, translated)
assert compare_values(ref_dict, translated, atol=atol)


def test_om6wing_tutorial(
Expand Down Expand Up @@ -929,7 +950,8 @@ def test_snappy_basic(get_snappy_geometry, snappy_basic_refinements):
_translate_and_compare(
snappy_basic_refinements,
get_snappy_geometry.mesh_unit,
"snappy_basic_refinements.json"
"snappy_basic_refinements.json",
atol=1e-6
)

def test_snappy_multiple_regions(get_snappy_geometry, snappy_refinements_multiple_regions):
Expand Down