1818 EntityListAllowingGhost ,
1919 Wall ,
2020)
21+ from flow360 .component .simulation .models .volume_models import (
22+ ActuatorDisk ,
23+ BETDisk ,
24+ PorousMedium ,
25+ )
2126from flow360 .component .simulation .outputs .output_entities import (
2227 Isosurface ,
2328 Point ,
5964 check_deleted_surface_in_entity_list ,
6065)
6166
67+ ForceOutputModelType = Annotated [
68+ Union [Wall , BETDisk , ActuatorDisk , PorousMedium ],
69+ pd .Field (discriminator = "type" ),
70+ ]
71+
6272
6373class UserDefinedField (Flow360BaseModel ):
6474 """
@@ -663,43 +673,46 @@ class ForceOutput(_OutputBase):
663673
664674 Define :class:`ForceOutput` to output total CL and CD on multiple wing surfaces.
665675
676+ >>> wall = fl.Wall(name = 'wing', surfaces=[volume_mesh['1'], volume_mesh["wing2"]])
666677 >>> fl.ForceOutput(
667678 ... name="force_output_wings",
668- ... entities=[volume_mesh["wing1"], volume_mesh["wing2"] ],
679+ ... models=[wall ],
669680 ... output_fields=["CL", "CD"]
670681 ... )
671682
672683 ====
673684 """
674685
675686 name : str = pd .Field ("Force output" , description = "Name of the force output." )
676- surface_models : List [Union [Wall , str ]] = pd .Field (
677- description = "List of surface models whose force contribution will be calculated." ,
678- )
679687 output_fields : UniqueItemList [ForceOutputCoefficientNames ] = pd .Field (
680688 description = "List of force coefficients. Including CL, CD, CFx, CFy, CFz, CMx, CMy, CMz. "
681689 "For surface forces, their SkinFriction/Pressure is also supported, such as CLSkinFriction and CLPressure."
682690 )
691+ models : List [Union [ForceOutputModelType , str ]] = pd .Field (
692+ description = "List of surface/volume models whose force contribution will be calculated." ,
693+ )
683694 moving_statistic : Optional [MovingStatistic ] = pd .Field (
684695 None , description = "The moving statistics used to monitor the output."
685696 )
686697 output_type : Literal ["ForceOutput" ] = pd .Field ("ForceOutput" , frozen = True )
687698
688- @pd .field_serializer ("surface_models " )
689- def serialize_models (self , v ):
699+ @pd .field_serializer ("models " )
700+ def serialize_models (self , value , info : pd . FieldSerializationInfo ):
690701 """Serialize only the model's id of the related object."""
702+ if isinstance (info .context , dict ) and info .context .get ("columnar_data_processor" ):
703+ return value
691704 model_ids = []
692- for model in v :
693- if isinstance (model , Wall ):
705+ for model in value :
706+ if isinstance (model , get_args ( get_args ( ForceOutputModelType )[ 0 ]) ):
694707 model_ids .append (model .private_attribute_id )
695708 continue
696709 model_ids .append (model )
697710 return model_ids
698711
699- @pd .field_validator ("surface_models " , mode = "before" )
712+ @pd .field_validator ("models " , mode = "before" )
700713 @classmethod
701- def _preprocess_models_with_id (cls , v ):
702- """Deserialize string-format surface models as Wall objects."""
714+ def _preprocess_models_with_id (cls , value ):
715+ """Deserialize string-format models as model objects."""
703716
704717 def preprocess_single_model (model , validation_info ):
705718 if not isinstance (model , str ):
@@ -710,16 +723,33 @@ def preprocess_single_model(model, validation_info):
710723 or validation_info .physics_model_dict .get (model ) is None
711724 ):
712725 raise ValueError ("The model does not exist in the models list." )
713- surface_model_dict = validation_info .physics_model_dict [model ]
714- model = Wall . model_validate ( surface_model_dict )
726+ physics_model_dict = validation_info .physics_model_dict [model ]
727+ model = pd . TypeAdapter ( ForceOutputModelType ). validate_python ( physics_model_dict )
715728 return model
716729
717730 processed_models = []
718731 validation_info = get_validation_info ()
719- for model in v :
732+ for model in value :
720733 processed_models .append (preprocess_single_model (model , validation_info ))
721734 return processed_models
722735
736+ @pd .field_validator ("models" , mode = "after" )
737+ @classmethod
738+ def _check_output_fields_with_volume_models_specified (cls , value , info : pd .ValidationInfo ):
739+ """Ensure the output field exists when volume models are specified."""
740+ if all (isinstance (model , Wall ) for model in value ):
741+ return value
742+ output_fields = info .data .get ("output_fields" , None )
743+ if all (
744+ field in ["CL" , "CD" , "CFx" , "CFy" , "CFz" , "CMx" , "CMy" , "CMz" ]
745+ for field in output_fields .items
746+ ):
747+ return value
748+ raise ValueError (
749+ "When ActuatorDisk/BETDisk/PorousMedium is specified, "
750+ "only CL, CD, CFx, CFy, CFz, CMx, CMy, CMz can be set as output_fields."
751+ )
752+
723753
724754class ProbeOutput (_OutputBase ):
725755 """
0 commit comments