1818 rotation_matrix_from_axis_and_angle ,
1919)
2020from flow360 .component .simulation .framework .base_model import Flow360BaseModel
21- from flow360 .component .simulation .framework .entity_base import EntityBase , generate_uuid
21+ from flow360 .component .simulation .framework .entity_base import (
22+ EntityBase ,
23+ EntityList ,
24+ generate_uuid ,
25+ )
2226from flow360 .component .simulation .framework .multi_constructor_model_base import (
2327 MultiConstructorBaseModel ,
2428)
2529from flow360 .component .simulation .framework .unique_list import UniqueStringList
2630from flow360 .component .simulation .unit_system import AngleType , AreaType , LengthType
2731from flow360 .component .simulation .user_code .core .types import ValueOrExpression
2832from flow360 .component .simulation .utils import BoundingBoxType , model_attribute_unlock
33+ from flow360 .component .simulation .validation .validation_context import (
34+ get_validation_info ,
35+ )
2936from flow360 .component .types import Axis
3037from flow360 .exceptions import Flow360BoundaryMissingError
3138
@@ -467,7 +474,7 @@ def _will_be_deleted_by_mesher(
467474 # pylint: disable=too-many-arguments, too-many-return-statements
468475 self ,
469476 at_least_one_body_transformed : bool ,
470- farfield_method : Optional [Literal ["auto" , "quasi-3d" ]],
477+ farfield_method : Optional [Literal ["auto" , "quasi-3d" , "user-defined" ]],
471478 global_bounding_box : Optional [BoundingBoxType ],
472479 planar_face_tolerance : Optional [float ],
473480 half_model_symmetry_plane_center_y : Optional [float ],
@@ -486,6 +493,10 @@ def _will_be_deleted_by_mesher(
486493 # VolumeMesh or Geometry/SurfaceMesh with legacy schema.
487494 return False
488495
496+ if farfield_method == "user-defined" :
497+ # Not applicable to user defined farfield
498+ return False
499+
489500 length_tolerance = global_bounding_box .largest_dimension * planar_face_tolerance
490501
491502 if farfield_method == "auto" :
@@ -635,4 +646,38 @@ def __str__(self):
635646 return "," .join (sorted ([self .pair [0 ].name , self .pair [1 ].name ]))
636647
637648
638- VolumeEntityTypes = Union [GenericVolume , Cylinder , Box , str ]
649+ @final
650+ class CustomVolume (_VolumeEntityBase ):
651+ """
652+ CustomVolume is a volume zone defined by its surrounding surfaces.
653+ It will be generated by the volume mesher.
654+ """
655+
656+ private_attribute_entity_type_name : Literal ["CustomVolume" ] = pd .Field (
657+ "CustomVolume" , frozen = True
658+ )
659+ type : Literal ["CustomVolume" ] = pd .Field ("CustomVolume" , frozen = True )
660+ boundaries : EntityList [Surface ] = pd .Field (
661+ description = "The surfaces that define the boundaries of the custom volume."
662+ )
663+ private_attribute_id : str = pd .Field (default_factory = generate_uuid , frozen = True )
664+
665+ @pd .field_validator ("boundaries" , mode = "after" )
666+ @classmethod
667+ def ensure_unique_boundary_names (cls , v ):
668+ """Check if the boundaries have different names within a CustomVolume."""
669+ if len (v .stored_entities ) != len ({boundary .name for boundary in v .stored_entities }):
670+ raise ValueError ("The boundaries of a CustomVolume must have different names." )
671+ return v
672+
673+ @pd .model_validator (mode = "after" )
674+ def ensure_usable (self ):
675+ """Check if the beta mesher is enabled and that the user is using user defined farfield."""
676+ validation_info = get_validation_info ()
677+ if validation_info is None :
678+ return self
679+ if validation_info .is_beta_mesher and validation_info .farfield_method == "user-defined" :
680+ return self
681+ raise ValueError (
682+ "CustomVolume is only supported when beta mesher and user defined farfield are enabled."
683+ )
0 commit comments