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
4 changes: 4 additions & 0 deletions flow360/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
AxisymmetricRefinement,
RotationCylinder,
RotationVolume,
StructuredBoxRefinement,
UniformRefinement,
UserDefinedFarfield,
)
Expand Down Expand Up @@ -141,6 +142,7 @@
VolumeOutput,
)
from flow360.component.simulation.primitives import (
AxisymmetricBody,
Box,
CustomVolume,
Cylinder,
Expand Down Expand Up @@ -198,13 +200,15 @@
"SurfaceRefinement",
"AutomatedFarfield",
"AxisymmetricRefinement",
"StructuredBoxRefinement",
"RotationCylinder",
"RotationVolume",
"UniformRefinement",
"SurfaceEdgeRefinement",
"HeightBasedRefinement",
"ReferenceGeometry",
"Cylinder",
"AxisymmetricBody",
"AerospaceCondition",
"ThermalState",
"LiquidOperatingCondition",
Expand Down
15 changes: 14 additions & 1 deletion flow360/component/simulation/meshing_param/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
AxisymmetricRefinement,
RotationCylinder,
RotationVolume,
StructuredBoxRefinement,
UniformRefinement,
UserDefinedFarfield,
)
Expand All @@ -42,6 +43,7 @@
BoundaryLayer,
PassiveSpacing,
UniformRefinement,
StructuredBoxRefinement,
AxisymmetricRefinement,
],
pd.Field(discriminator="refinement_type"),
Expand Down Expand Up @@ -334,6 +336,14 @@ def _check_no_reused_volume_entities(self) -> Self:
| UniformRefinement | YES | NO | NO |
+------------------------+------------------------+------------------------+------------------------+

+------------------------+------------------------+------------------------+
| |StructuredBoxRefinement | UniformRefinement |
+------------------------+------------------------+------------------------+
|StructuredBoxRefinement | NO | -- |
+------------------------+------------------------+------------------------+
| UniformRefinement | NO | NO |
+------------------------+------------------------+------------------------+

"""

usage = EntityUsageMap()
Expand All @@ -347,7 +357,10 @@ def _check_no_reused_volume_entities(self) -> Self:
]

for refinement in self.refinements if self.refinements is not None else []:
if isinstance(refinement, (UniformRefinement, AxisymmetricRefinement)):
if isinstance(
refinement,
(UniformRefinement, AxisymmetricRefinement, StructuredBoxRefinement),
):
# pylint: disable=protected-access
_ = [
usage.add_entity_usage(item, refinement.refinement_type)
Expand Down
86 changes: 83 additions & 3 deletions flow360/component/simulation/meshing_param/volume_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,63 @@ class UniformRefinement(Flow360BaseModel):
spacing: LengthType.Positive = pd.Field(description="The required refinement spacing.")


class StructuredBoxRefinement(Flow360BaseModel):
"""
- The mesh inside the :class:`StructuredBoxRefinement` is semi-structured.
- The :class:`StructuredBoxRefinement` cannot enclose/intersect with other objects.
- The spacings along the three box axes can be adjusted independently.

Example
-------

>>> StructuredBoxRefinement(
... entities=[
... Box.from_principal_axes(
... name="boxRefinement",
... center=(0, 1, 1) * fl.u.cm,
... size=(1, 2, 1) * fl.u.cm,
... axes=((2, 2, 0), (-2, 2, 0)),
... )
... ],
... spacing_axis1=7.5*u.cm,
... spacing_axis2=10*u.cm,
... spacing_normal=15*u.cm,
... )
====
"""

# pylint: disable=no-member
# pylint: disable=too-few-public-methods
name: Optional[str] = pd.Field("StructuredBoxRefinement")
refinement_type: Literal["StructuredBoxRefinement"] = pd.Field(
"StructuredBoxRefinement", frozen=True
)
entities: EntityList[Box] = pd.Field()

spacing_axis1: LengthType.Positive = pd.Field(
description="Spacing along the first axial direction."
)
spacing_axis2: LengthType.Positive = pd.Field(
description="Spacing along the second axial direction."
)
spacing_normal: LengthType.Positive = pd.Field(
description="Spacing along the normal axial direction."
)

@pd.model_validator(mode="after")
def _validate_only_in_beta_mesher(self):
"""
Ensure that StructuredBoxRefinement objects are only processed with the beta mesher.
"""
validation_info = get_validation_info()
if validation_info is None:
return self
if validation_info.is_beta_mesher:
return self

raise ValueError("`StructuredBoxRefinement` is only supported with the beta mesher.")


class AxisymmetricRefinementBase(Flow360BaseModel, metaclass=ABCMeta):
"""Base class for all refinements that requires spacing in axial, radial and circumferential directions."""

Expand Down Expand Up @@ -149,11 +206,12 @@ class RotationVolume(AxisymmetricRefinementBase):
type: Literal["RotationVolume"] = pd.Field("RotationVolume", frozen=True)
name: Optional[str] = pd.Field("Rotation Volume", description="Name to display in the GUI.")
entities: EntityList[Cylinder, AxisymmetricBody] = pd.Field()
enclosed_entities: Optional[EntityList[Cylinder, Surface, AxisymmetricBody]] = pd.Field(
enclosed_entities: Optional[EntityList[Cylinder, Surface, AxisymmetricBody, Box]] = pd.Field(
None,
description="Entities enclosed by :class:`RotationVolume`. "
+ "Can be `Surface` and/or other :class:`~flow360.Cylinder`(s)"
"and/or other :class:`~flow360.AxisymmetricBody`(s).",
"Can be `Surface` and/or other :class:`~flow360.Cylinder`(s)"
"and/or other :class:`~flow360.AxisymmetricBody`(s)"
"and/or other :class:`~flow360.Box`(s)",
)

@pd.field_validator("entities", mode="after")
Expand Down Expand Up @@ -196,6 +254,28 @@ def _validate_cylinder_name_length(cls, values):
)
return values

@pd.field_validator("enclosed_entities", mode="after")
@classmethod
def _validate_enclosed_box_only_in_beta_mesher(cls, values):
"""
Check the name length for the cylinder entities due to the 32-character
limitation of all data structure names and labels in CGNS format.
The current prefix is 'rotatingBlock-' with 14 characters.
"""
validation_info = get_validation_info()
if validation_info is None:
return values
if validation_info.is_beta_mesher:
return values

for entity in values.stored_entities:
if isinstance(entity, Box):
raise ValueError(
"`Box` entity in `RotationVolume.enclosed_entities` is only supported with the beta mesher."
)

return values

@pd.field_validator("entities", mode="after")
@classmethod
def _validate_axisymmetric_only_in_beta_mesher(cls, values):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
AxisymmetricRefinementBase,
RotationCylinder,
RotationVolume,
StructuredBoxRefinement,
UniformRefinement,
UserDefinedFarfield,
)
Expand All @@ -31,6 +32,8 @@
from flow360.component.simulation.utils import is_exact_instance
from flow360.exceptions import Flow360TranslationError

# pylint: disable=too-many-locals


def uniform_refinement_translator(obj: UniformRefinement):
"""
Expand All @@ -51,6 +54,17 @@ def cylindrical_refinement_translator(obj: AxisymmetricRefinementBase):
}


def box_refinement_translator(obj: StructuredBoxRefinement):
"""
Translate StructuredBoxRefinement spacings
"""
return {
"spacingAxis1": obj.spacing_axis1.value.item(),
"spacingAxis2": obj.spacing_axis2.value.item(),
"spacingNormal": obj.spacing_normal.value.item(),
}


def boundary_layer_translator(obj: BoundaryLayer):
"""
Translate BoundaryLayer.
Expand Down Expand Up @@ -100,6 +114,8 @@ def rotation_volume_translator(obj: RotationVolume, rotor_disk_names: list):
setting["enclosedObjects"].append("slidingInterface-" + entity.name)
elif is_exact_instance(entity, AxisymmetricBody):
setting["enclosedObjects"].append("slidingInterface-" + entity.name)
elif is_exact_instance(entity, Box):
setting["enclosedObjects"].append("structuredBox-" + entity.name)
elif is_exact_instance(entity, Surface):
setting["enclosedObjects"].append(entity.name)
return setting
Expand All @@ -126,6 +142,25 @@ def refinement_entity_injector(entity_obj):
return {}


def refinement_entity_box_with_axes_injector(entity_obj: Box):
"""Injector for Box entity in StructuredBoxRefinement."""
lengths = list(entity_obj.size.value)

axis1 = entity_obj.axes[0]
axis2 = entity_obj.axes[1]

return {
"name": entity_obj.name,
"type": "box",
"lengthAxis1": lengths[0],
"lengthAxis2": lengths[1],
"lengthNormal": lengths[2],
"axis1": list(axis1),
"axis2": list(axis2),
"center": list(entity_obj.center.value),
}


def rotor_disks_entity_injector(entity: Cylinder):
"""Injector for Cylinder entity in AxisymmetricRefinement."""

Expand All @@ -144,6 +179,7 @@ def rotation_volume_entity_injector(entity: Union[Cylinder, AxisymmetricBody]):
if isinstance(entity, Cylinder):
return {
"name": entity.name,
"type": "Cylinder",
"innerRadius": 0 if entity.inner_radius is None else entity.inner_radius.value.item(),
"outerRadius": entity.outer_radius.value.item(),
"thickness": entity.height.value.item(),
Expand All @@ -153,6 +189,7 @@ def rotation_volume_entity_injector(entity: Union[Cylinder, AxisymmetricBody]):
if isinstance(entity, AxisymmetricBody):
return {
"name": entity.name,
"type": "Axisymmetric",
"profileCurve": [list(profile_point.value) for profile_point in entity.profile_curve],
"axisOfRotation": list(entity.axis),
"center": list(entity.center.value),
Expand Down Expand Up @@ -284,6 +321,14 @@ def get_volume_meshing_json(input_params: SimulationParams, mesh_units):
to_list=True,
entity_injection_func=rotor_disks_entity_injector,
)
structured_box_refinement = translate_setting_and_apply_to_all_entities(
meshing_params.refinements,
StructuredBoxRefinement,
box_refinement_translator,
to_list=True,
entity_injection_func=refinement_entity_box_with_axes_injector,
)

if uniform_refinement_list:
translated["refinement"] = []
translated["refinement"].extend(uniform_refinement_list)
Expand All @@ -294,6 +339,10 @@ def get_volume_meshing_json(input_params: SimulationParams, mesh_units):
translated["rotorDisks"].extend(rotor_disk_refinement)
rotor_disk_names = [item["name"] for item in rotor_disk_refinement]

if structured_box_refinement:
translated["structuredRegions"] = []
translated["structuredRegions"].extend(structured_box_refinement)

faces_aniso_setting = translate_setting_and_apply_to_all_entities(
meshing_params.refinements,
BoundaryLayer,
Expand Down
Loading
Loading