Skip to content

Commit 099f32d

Browse files
Merge branch 'main' into BenY/CentralizedSchema
2 parents 5ce4db6 + be082a9 commit 099f32d

20 files changed

+646
-39
lines changed

flow360/component/project_utils.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
)
1616
from flow360.component.simulation.framework.base_model import Flow360BaseModel
1717
from flow360.component.simulation.framework.entity_base import EntityList
18+
from flow360.component.simulation.framework.entity_registry import EntityRegistry
1819
from flow360.component.simulation.framework.param_utils import AssetCache
1920
from flow360.component.simulation.outputs.outputs import (
2021
SurfaceIntegralOutput,
@@ -498,6 +499,31 @@ def _set_up_default_reference_geometry(params: SimulationParams, length_unit: Le
498499
return params
499500

500501

502+
def _build_deduplicated_entity_registry_from_params(params: SimulationParams) -> EntityRegistry:
503+
"""
504+
Build a deduplicated entity registry from params' stored entities.
505+
506+
params.used_entity_registry may contain duplicates (same entity used in multiple
507+
models/outputs). This function deduplicates by (entity_type, identifier), where
508+
identifier is private_attribute_id when available, falling back to entity name
509+
to avoid collapsing distinct entities that share a None id.
510+
"""
511+
registry = EntityRegistry()
512+
seen_keys = set()
513+
for entity_type, entities in params.used_entity_registry.internal_registry.items():
514+
for entity in entities:
515+
identifier = (
516+
entity.private_attribute_id
517+
if entity.private_attribute_id is not None
518+
else entity.name
519+
)
520+
key = (entity_type, identifier)
521+
if key not in seen_keys:
522+
registry.register(entity)
523+
seen_keys.add(key)
524+
return registry
525+
526+
501527
def set_up_params_for_uploading( # pylint: disable=too-many-arguments
502528
root_asset,
503529
length_unit: LengthType,
@@ -557,12 +583,13 @@ def set_up_params_for_uploading( # pylint: disable=too-many-arguments
557583
# Legacy workflow (without DraftContext): use root_asset.entity_info
558584
# User may have made modifications to the entities which is recorded in asset's entity registry
559585
# We need to reflect these changes.
560-
root_asset.entity_info.update_persistent_entities(
561-
asset_entity_registry=root_asset.internal_registry
586+
entity_info = root_asset.entity_info
587+
entity_info.update_persistent_entities(
588+
asset_entity_registry=_build_deduplicated_entity_registry_from_params(params)
562589
)
563590

564591
# Check if there are any new draft entities that have been added in the params by the user
565-
entity_info = _set_up_params_non_persistent_entity_info(root_asset.entity_info, params)
592+
entity_info = _set_up_params_non_persistent_entity_info(entity_info, params)
566593

567594
# If the customer just load the param without re-specify the same set of entity grouping tags,
568595
# we need to update the entity grouping tags to the ones in the SimulationParams.

flow360/component/simulation/framework/updater.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -778,6 +778,17 @@ def _rename_in_entity(entity):
778778
return params_as_dict
779779

780780

781+
def _to_25_9_3(params_as_dict):
782+
"""Rename ``type_name`` to ``wall_function_type`` in ``use_wall_function`` dicts."""
783+
for model in params_as_dict.get("models", []):
784+
if model.get("type") != "Wall":
785+
continue
786+
wall_fn = model.get("use_wall_function")
787+
if isinstance(wall_fn, dict) and "type_name" in wall_fn:
788+
wall_fn["wall_function_type"] = wall_fn.pop("type_name")
789+
return params_as_dict
790+
791+
781792
VERSION_MILESTONES = [
782793
(Flow360Version("24.11.1"), _to_24_11_1),
783794
(Flow360Version("24.11.7"), _to_24_11_7),
@@ -800,6 +811,7 @@ def _rename_in_entity(entity):
800811
(Flow360Version("25.9.0"), _to_25_9_0),
801812
(Flow360Version("25.9.1"), _to_25_9_1),
802813
(Flow360Version("25.9.2"), _to_25_9_2),
814+
(Flow360Version("25.9.3"), _to_25_9_3),
803815
] # A list of the Python API version tuple with their corresponding updaters.
804816

805817

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."""

flow360/component/simulation/models/surface_models.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -341,13 +341,13 @@ class WallFunction(Flow360BaseModel):
341341
342342
>>> fl.Wall(
343343
... entities=volume_mesh["fluid/wall"],
344-
... use_wall_function=fl.WallFunction(type_name="InnerLayer"),
344+
... use_wall_function=fl.WallFunction(wall_function_type="InnerLayer"),
345345
... )
346346
347347
====
348348
"""
349349

350-
type_name: Literal["BoundaryLayer", "InnerLayer"] = pd.Field(
350+
wall_function_type: Literal["BoundaryLayer", "InnerLayer"] = pd.Field(
351351
"BoundaryLayer",
352352
description="Type of wall function model. "
353353
+ "'BoundaryLayer' uses integral flat plate boundary layer theory to predict wall shear stress. "
@@ -377,7 +377,7 @@ class Wall(BoundaryBase):
377377
378378
>>> fl.Wall(
379379
... entities=volume_mesh["8"],
380-
... use_wall_function=fl.WallFunction(type_name="InnerLayer"),
380+
... use_wall_function=fl.WallFunction(wall_function_type="InnerLayer"),
381381
... )
382382
383383
- :code:`Wall` with wall function and wall rotation:

flow360/component/simulation/translator/solver_translator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1592,7 +1592,7 @@ def boundary_spec_translator(model: SurfaceModelTypes, op_acoustic_to_static_pre
15921592
if isinstance(model, Wall):
15931593
if model.use_wall_function is not None:
15941594
boundary["type"] = "WallFunction"
1595-
boundary["wallModelType"] = model.use_wall_function.type_name
1595+
boundary["wallModelType"] = model.use_wall_function.wall_function_type
15961596
else:
15971597
boundary["type"] = "NoSlipWall"
15981598
if model.velocity is not None:

flow360/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
version
33
"""
44

5-
__version__ = "25.9.2b1"
5+
__version__ = "25.9.3b1"
66
__solver_version__ = "release-25.8"

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "flow360"
3-
version = "v25.9.2b1"
3+
version = "v25.9.3b1"
44
description = "Flow360 Python Client"
55
authors = ["Flexcompute <support@flexcompute.com>"]
66

0 commit comments

Comments
 (0)