Skip to content

Commit d7ac5ba

Browse files
Merge branch 'main' into fxc-6541/multiple-surface-outputs
2 parents edb3ff8 + 0ad7d49 commit d7ac5ba

5 files changed

Lines changed: 182 additions & 8 deletions

File tree

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
from flow360.component.simulation.meshing_param.volume_params import UniformRefinement
1313
from flow360.component.simulation.primitives import SnappyBody, Surface
1414
from flow360.component.simulation.unit_system import AngleType, LengthType
15-
from flow360.log import log
1615

1716

1817
class SnappyEntityRefinement(Flow360BaseModel, metaclass=ABCMeta):
@@ -36,11 +35,10 @@ def _check_spacing_order(self) -> Self:
3635
def _check_proximity_spacing(self) -> Self:
3736
if self.min_spacing and self.proximity_spacing:
3837
if self.proximity_spacing > self.min_spacing:
39-
log.warning(
40-
f"Proximity spacing ({self.proximity_spacing}) was set higher than the minimal spacing"
41-
+ f"({self.min_spacing}), setting proximity spacing to minimal spacing."
38+
raise ValueError(
39+
f"Proximity spacing ({self.proximity_spacing}) was set higher than the minimal spacing "
40+
+ f"({self.min_spacing})."
4241
)
43-
self.proximity_spacing = self.min_spacing
4442
return self
4543

4644

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,16 @@ def _check_body_refinements_w_defaults(self):
6767
return self
6868
for refinement in self.refinements:
6969
if isinstance(refinement, BodyRefinement):
70+
if refinement.min_spacing is None and refinement.proximity_spacing:
71+
if refinement.proximity_spacing > self.defaults.min_spacing:
72+
raise ValueError(
73+
f"Proximity spacing for a BodyRefinement ({refinement.proximity_spacing}) was set higher "
74+
+ f"than the minimal spacing ({self.defaults.min_spacing})."
75+
)
76+
7077
if refinement.min_spacing is None and refinement.max_spacing is None:
7178
continue
79+
7280
if refinement.min_spacing is None and self.defaults.min_spacing.to(
7381
"m"
7482
) > refinement.max_spacing.to("m"):

flow360/component/simulation/translator/utils.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,19 @@ def _apply_transformations_to_model(
123123
setattr(model, field_name, new_entity_list)
124124

125125
elif isinstance(field_value, (list, tuple)):
126-
new_items = [_transform_sequence_item(item, manager) for item in field_value]
127-
new_value = new_items if isinstance(field_value, list) else tuple(new_items)
128-
setattr(model, field_name, new_value)
126+
transformed_items = None
127+
for index, item in enumerate(field_value):
128+
transformed = _transform_sequence_item(item, manager)
129+
if transformed is not item:
130+
if transformed_items is None:
131+
transformed_items = list(field_value)
132+
transformed_items[index] = transformed
133+
134+
if transformed_items is not None:
135+
new_value = (
136+
transformed_items if isinstance(field_value, list) else tuple(transformed_items)
137+
)
138+
setattr(model, field_name, new_value)
129139

130140
elif isinstance(field_value, Flow360BaseModel):
131141
_apply_transformations_to_model(field_value, manager)

tests/simulation/params/meshing_validation/test_refinements_validation.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,3 +422,74 @@ def test_volume_uniform_refinement_project_to_surface_false_skips_validation():
422422
)
423423

424424
assert errors is None, "No snappy validation error expected when project_to_surface=False"
425+
426+
427+
def test_snappy_proximity_spacing_error_when_exceeds_default_min_spacing():
428+
"""When min_spacing is not set on a BodyRefinement but proximity_spacing exceeds
429+
defaults.min_spacing, a ValueError should be raised."""
430+
message = "Proximity spacing for a BodyRefinement (5.0 mm) was set higher than the minimal spacing (3.0 mm)."
431+
with SI_unit_system, pytest.raises(pd.ValidationError, match=re.escape(message)):
432+
snappy.SurfaceMeshingParams(
433+
defaults=snappy.SurfaceMeshingDefaults(
434+
min_spacing=3 * u.mm, max_spacing=10 * u.mm, gap_resolution=0.1 * u.mm
435+
),
436+
refinements=[
437+
snappy.BodyRefinement(
438+
bodies=SnappyBody(name="body1", surfaces=[Surface(name="surface")]),
439+
proximity_spacing=5 * u.mm,
440+
max_spacing=10 * u.mm,
441+
),
442+
],
443+
)
444+
445+
446+
def test_snappy_proximity_spacing_ok_when_below_default_min_spacing():
447+
"""When proximity_spacing is already <= defaults.min_spacing, no error should be raised."""
448+
with SI_unit_system:
449+
body = SnappyBody(name="body1", surfaces=[Surface(name="surface")])
450+
params = snappy.SurfaceMeshingParams(
451+
defaults=snappy.SurfaceMeshingDefaults(
452+
min_spacing=3 * u.mm, max_spacing=10 * u.mm, gap_resolution=0.1 * u.mm
453+
),
454+
refinements=[
455+
snappy.BodyRefinement(
456+
bodies=body,
457+
proximity_spacing=2 * u.mm,
458+
max_spacing=10 * u.mm,
459+
),
460+
],
461+
)
462+
assert params.refinements[0].proximity_spacing == 2 * u.mm
463+
464+
465+
def test_snappy_proximity_spacing_error_with_explicit_min_spacing():
466+
"""When both min_spacing and proximity_spacing are set on the refinement
467+
and proximity_spacing > min_spacing, the entity-level validator raises."""
468+
message = "Proximity spacing (6.0 mm) was set higher than the minimal spacing (4.0 mm)."
469+
with SI_unit_system, pytest.raises(pd.ValidationError, match=re.escape(message)):
470+
snappy.BodyRefinement(
471+
bodies=SnappyBody(name="body1", surfaces=[Surface(name="surface")]),
472+
min_spacing=4 * u.mm,
473+
proximity_spacing=6 * u.mm,
474+
max_spacing=10 * u.mm,
475+
)
476+
477+
478+
def test_snappy_proximity_spacing_ok_when_min_spacing_is_set():
479+
"""When min_spacing is explicitly set and proximity_spacing <= min_spacing, no error."""
480+
with SI_unit_system:
481+
body = SnappyBody(name="body1", surfaces=[Surface(name="surface")])
482+
params = snappy.SurfaceMeshingParams(
483+
defaults=snappy.SurfaceMeshingDefaults(
484+
min_spacing=1 * u.mm, max_spacing=10 * u.mm, gap_resolution=0.1 * u.mm
485+
),
486+
refinements=[
487+
snappy.BodyRefinement(
488+
bodies=body,
489+
min_spacing=4 * u.mm,
490+
proximity_spacing=3 * u.mm,
491+
max_spacing=10 * u.mm,
492+
),
493+
],
494+
)
495+
assert params.refinements[0].proximity_spacing == 3 * u.mm
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import flow360.component.simulation.units as u
2+
from flow360.component.simulation.draft_context.coordinate_system_manager import (
3+
CoordinateSystemAssignmentGroup,
4+
CoordinateSystemEntityRef,
5+
CoordinateSystemStatus,
6+
)
7+
from flow360.component.simulation.entity_operation import CoordinateSystem
8+
from flow360.component.simulation.framework.param_utils import AssetCache
9+
from flow360.component.simulation.models.volume_models import (
10+
BETDisk,
11+
BETDiskChord,
12+
BETDiskSectionalPolar,
13+
BETDiskTwist,
14+
)
15+
from flow360.component.simulation.primitives import Cylinder
16+
from flow360.component.simulation.simulation_params import SimulationParams
17+
from flow360.component.simulation.translator.utils import (
18+
apply_coordinate_system_transformations,
19+
)
20+
from flow360.component.simulation.unit_system import SI_unit_system
21+
22+
23+
def test_apply_coordinate_system_transformations_skips_frozen_non_entity_sequences():
24+
with SI_unit_system:
25+
cylinder = Cylinder(
26+
name="bet_disk",
27+
center=(0, 0, 0) * u.m,
28+
axis=(0, 0, 1),
29+
height=1 * u.m,
30+
outer_radius=1 * u.m,
31+
)
32+
bet_disk = BETDisk(
33+
entities=cylinder,
34+
rotation_direction_rule="leftHand",
35+
number_of_blades=3,
36+
omega=100 * u.rpm,
37+
chord_ref=1 * u.m,
38+
n_loading_nodes=20,
39+
mach_numbers=[0],
40+
reynolds_numbers=[1000000],
41+
twists=[BETDiskTwist(radius=0 * u.m, twist=0 * u.deg)],
42+
chords=[BETDiskChord(radius=0 * u.m, chord=1 * u.m)],
43+
alphas=[-2, 0, 2] * u.deg,
44+
sectional_radiuses=[0.25, 0.5] * u.m,
45+
sectional_polars=[
46+
BETDiskSectionalPolar(
47+
lift_coeffs=[[[0.1, 0.2, 0.3]]],
48+
drag_coeffs=[[[0.01, 0.02, 0.03]]],
49+
),
50+
BETDiskSectionalPolar(
51+
lift_coeffs=[[[0.15, 0.25, 0.35]]],
52+
drag_coeffs=[[[0.015, 0.025, 0.035]]],
53+
),
54+
],
55+
)
56+
coordinate_system = CoordinateSystem(name="cs", translation=(1, 2, 3) * u.m)
57+
coordinate_system_status = CoordinateSystemStatus(
58+
coordinate_systems=[coordinate_system],
59+
parents=[],
60+
assignments=[
61+
CoordinateSystemAssignmentGroup(
62+
coordinate_system_id=coordinate_system.private_attribute_id,
63+
entities=[
64+
CoordinateSystemEntityRef(
65+
entity_type="Cylinder",
66+
entity_id=cylinder.private_attribute_id,
67+
)
68+
],
69+
)
70+
],
71+
)
72+
params = SimulationParams(
73+
models=[bet_disk],
74+
private_attribute_asset_cache=AssetCache(
75+
coordinate_system_status=coordinate_system_status,
76+
),
77+
)
78+
79+
apply_coordinate_system_transformations(params)
80+
81+
transformed_bet_disk = params.models[0]
82+
transformed_cylinder = transformed_bet_disk.entities.stored_entities[0]
83+
84+
assert all(transformed_cylinder.center == [1, 2, 3] * u.m)
85+
assert transformed_bet_disk.mach_numbers == [0]
86+
assert transformed_bet_disk.twists[0].radius == 0 * u.m
87+
assert transformed_bet_disk.chords[0].chord == 1 * u.m

0 commit comments

Comments
 (0)